@neta-art/cohub 8.24.0 → 9.0.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 (159) hide show
  1. package/dist/board/animation.d.ts +57 -181
  2. package/dist/board/animation.js +374 -220
  3. package/dist/board/core/arrow-geometry.d.ts +33 -18
  4. package/dist/board/core/arrow-geometry.js +283 -52
  5. package/dist/board/core/draw-geometry.d.ts +22 -36
  6. package/dist/board/core/draw-geometry.js +148 -85
  7. package/dist/board/core/export-assets.d.ts +3 -6
  8. package/dist/board/core/export-assets.js +0 -2
  9. package/dist/board/core/export-plan.d.ts +12 -63
  10. package/dist/board/core/export-plan.js +55 -94
  11. package/dist/board/core/palette.d.ts +4 -21
  12. package/dist/board/core/palette.js +8 -13
  13. package/dist/board/core/scene.d.ts +33 -0
  14. package/dist/board/core/scene.js +302 -0
  15. package/dist/board/core/shape-definition.d.ts +12 -24
  16. package/dist/board/core/shape-definition.js +0 -19
  17. package/dist/board/core/shape-types.d.ts +6 -120
  18. package/dist/board/core/shape-types.js +5 -5
  19. package/dist/board/core/text-metrics.d.ts +8 -11
  20. package/dist/board/core/text-metrics.js +5 -34
  21. package/dist/board/core/tool-styles.d.ts +6 -16
  22. package/dist/board/core/tool-styles.js +17 -25
  23. package/dist/board/export/index.d.ts +10 -17
  24. package/dist/board/export/index.js +22 -21
  25. package/dist/board/export/scene.d.ts +10 -16
  26. package/dist/board/export/scene.js +17 -30
  27. package/dist/board/geometry.d.ts +27 -56
  28. package/dist/board/geometry.js +7 -67
  29. package/dist/board/headless/index.d.ts +6 -27
  30. package/dist/board/headless/index.js +4 -19
  31. package/dist/board/headless/sketch.d.ts +19 -0
  32. package/dist/board/headless/sketch.js +146 -0
  33. package/dist/board/image-key.d.ts +2 -12
  34. package/dist/board/image-key.js +3 -13
  35. package/dist/board/index.d.ts +18 -20
  36. package/dist/board/index.js +17 -17
  37. package/dist/board/media-playback.d.ts +3 -6
  38. package/dist/board/media-playback.js +6 -6
  39. package/dist/board/presets.d.ts +150 -0
  40. package/dist/board/presets.js +226 -0
  41. package/dist/board/render/board-background.d.ts +5 -3
  42. package/dist/board/render/board-background.js +8 -7
  43. package/dist/board/render/clip.d.ts +14 -0
  44. package/dist/board/render/clip.js +47 -0
  45. package/dist/board/render/index.d.ts +8 -5
  46. package/dist/board/render/index.js +7 -4
  47. package/dist/board/render/media-interaction.d.ts +4 -5
  48. package/dist/board/render/media-interaction.js +1 -2
  49. package/dist/board/render/palette.d.ts +6 -2
  50. package/dist/board/render/palette.js +7 -1
  51. package/dist/board/render/renderers/arrow-card-renderer.js +69 -71
  52. package/dist/board/render/renderers/audio-card-renderer.js +3 -3
  53. package/dist/board/render/renderers/base-card-renderer.d.ts +6 -17
  54. package/dist/board/render/renderers/base-card-renderer.js +1 -24
  55. package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -50
  56. package/dist/board/render/renderers/board-renderer-registry.js +6 -7
  57. package/dist/board/render/renderers/draw-card-renderer.js +89 -56
  58. package/dist/board/render/renderers/effect-card-renderer.d.ts +6 -0
  59. package/dist/board/render/renderers/effect-card-renderer.js +197 -0
  60. package/dist/board/render/renderers/far-plate.d.ts +4 -21
  61. package/dist/board/render/renderers/far-plate.js +15 -34
  62. package/dist/board/render/renderers/file-card-renderer.d.ts +4 -25
  63. package/dist/board/render/renderers/file-card-renderer.js +8 -58
  64. package/dist/board/render/renderers/frame-card-renderer.js +28 -28
  65. package/dist/board/render/renderers/image-card-renderer.d.ts +4 -3
  66. package/dist/board/render/renderers/image-card-renderer.js +0 -9
  67. package/dist/board/render/renderers/shape-card-renderer.d.ts +9 -0
  68. package/dist/board/render/renderers/shape-card-renderer.js +240 -0
  69. package/dist/board/render/renderers/sketch-card-renderer.d.ts +5 -0
  70. package/dist/board/render/renderers/sketch-card-renderer.js +98 -0
  71. package/dist/board/render/renderers/task-card-renderer.d.ts +0 -1
  72. package/dist/board/render/renderers/task-card-renderer.js +14 -16
  73. package/dist/board/render/renderers/text-card-renderer.d.ts +1 -3
  74. package/dist/board/render/renderers/text-card-renderer.js +56 -46
  75. package/dist/board/render/renderers/unknown-card-renderer.js +4 -10
  76. package/dist/board/render/renderers/video-card-renderer.d.ts +4 -2
  77. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  78. package/dist/board/render/sketch-host.d.ts +14 -0
  79. package/dist/board/render/sketch-host.js +216 -0
  80. package/dist/board/render/stroke.d.ts +12 -0
  81. package/dist/board/render/stroke.js +90 -0
  82. package/dist/board/render/text-measurement.d.ts +6 -26
  83. package/dist/board/render/text-measurement.js +11 -30
  84. package/dist/board/replay.d.ts +6 -31
  85. package/dist/board/replay.js +45 -203
  86. package/dist/board/runtime/board-player.d.ts +24 -0
  87. package/dist/board/runtime/board-player.js +161 -0
  88. package/dist/board/task.d.ts +2 -10
  89. package/dist/board/task.js +1 -10
  90. package/dist/chunks/environment.d.ts +2291 -3864
  91. package/dist/chunks/host-bridge.d.ts +253 -0
  92. package/dist/chunks/http.d.ts +233 -58
  93. package/dist/chunks/http.js +1046 -1028
  94. package/dist/chunks/transport.js +113 -59
  95. package/dist/chunks/websocket.d.ts +1 -1
  96. package/dist/chunks/websocket.js +1 -1
  97. package/dist/host-bridge.d.ts +54 -0
  98. package/dist/host-bridge.js +322 -0
  99. package/dist/http.d.ts +3 -3
  100. package/dist/index.d.ts +74 -191
  101. package/dist/index.js +2032 -1186
  102. package/dist/protocol/dist/board-constants.d.ts +1 -29
  103. package/dist/protocol/dist/board-constants.js +2 -115
  104. package/dist/protocol/dist/board-layout.d.ts +59 -0
  105. package/dist/protocol/dist/board-layout.js +482 -0
  106. package/dist/protocol/dist/board-model.d.ts +2052 -0
  107. package/dist/protocol/dist/board-model.js +592 -0
  108. package/dist/protocol/dist/board-patch.d.ts +86 -0
  109. package/dist/protocol/dist/board-patch.js +734 -0
  110. package/dist/protocol/dist/board-schema.d.ts +33 -0
  111. package/dist/protocol/dist/board-schema.js +59 -0
  112. package/dist/protocol/dist/board-upgrade.d.ts +1 -0
  113. package/dist/protocol/dist/board-url.d.ts +1 -3
  114. package/dist/protocol/dist/board-url.js +2 -2
  115. package/dist/protocol/dist/board.d.ts +37 -232
  116. package/dist/protocol/dist/board.js +62 -160
  117. package/dist/protocol/dist/display/actions.d.ts +1 -0
  118. package/dist/protocol/dist/display/index.d.ts +2 -0
  119. package/dist/protocol/dist/index.d.ts +11 -12
  120. package/dist/protocol/dist/model/metrics.d.ts +2 -0
  121. package/dist/protocol/dist/model/session.d.ts +2 -1
  122. package/dist/protocol/dist/realtime/types.d.ts +2 -2
  123. package/dist/protocol/dist/runtime/index.d.ts +1 -0
  124. package/dist/space-picker.d.ts +1 -0
  125. package/dist/space-picker.js +1 -0
  126. package/dist/types.d.ts +2 -5
  127. package/package.json +9 -1
  128. package/dist/board/core/connections.d.ts +0 -108
  129. package/dist/board/core/connections.js +0 -455
  130. package/dist/board/mutation.d.ts +0 -6
  131. package/dist/board/mutation.js +0 -14
  132. package/dist/board/render/connection-layer.d.ts +0 -53
  133. package/dist/board/render/connection-layer.js +0 -375
  134. package/dist/board/render/renderers/geo-card-renderer.d.ts +0 -5
  135. package/dist/board/render/renderers/geo-card-renderer.js +0 -127
  136. package/dist/board/semantic-document.d.ts +0 -37
  137. package/dist/board/semantic-document.js +0 -288
  138. package/dist/board/semantic-mutation.d.ts +0 -10
  139. package/dist/board/semantic-mutation.js +0 -209
  140. package/dist/protocol/dist/board-animation.js +0 -34
  141. package/dist/protocol/dist/board-authoring.d.ts +0 -1326
  142. package/dist/protocol/dist/board-authoring.js +0 -302
  143. package/dist/protocol/dist/board-capability-registry.d.ts +0 -2
  144. package/dist/protocol/dist/board-capability-registry.js +0 -123
  145. package/dist/protocol/dist/board-codec.d.ts +0 -3
  146. package/dist/protocol/dist/board-codec.js +0 -383
  147. package/dist/protocol/dist/board-composition.d.ts +0 -383
  148. package/dist/protocol/dist/board-composition.js +0 -310
  149. package/dist/protocol/dist/board-connection.d.ts +0 -317
  150. package/dist/protocol/dist/board-connection.js +0 -219
  151. package/dist/protocol/dist/board-document.d.ts +0 -1566
  152. package/dist/protocol/dist/board-document.js +0 -456
  153. package/dist/protocol/dist/board-effect.d.ts +0 -96
  154. package/dist/protocol/dist/board-effect.js +0 -54
  155. package/dist/protocol/dist/board-geometry.d.ts +0 -15
  156. package/dist/protocol/dist/board-geometry.js +0 -137
  157. package/dist/protocol/dist/board-node.d.ts +0 -8
  158. package/dist/protocol/dist/board-node.js +0 -252
  159. /package/dist/protocol/dist/{board-animation.d.ts → host-bridge.d.ts} +0 -0
@@ -1,4 +1,6 @@
1
- import { BoardDocument, BoardItem } from "../../../protocol/dist/board-document.js";
1
+ import { BoardSettings, BoardSketchItem } from "../../../protocol/dist/board-model.js";
2
+ import "../../../protocol/dist/index.js";
3
+ import { BoardScene, BoardSceneItem, SceneItem } from "../../core/scene.js";
2
4
  import { BoardShapeColors } from "../../core/palette.js";
3
5
  import { Container, Graphics, Texture } from "pixi.js";
4
6
  //#region src/board/render/renderers/board-renderer-registry.d.ts
@@ -14,74 +16,42 @@ type BoardRenderPalette = {
14
16
  epic: number;
15
17
  legendary: number;
16
18
  };
19
+ type BoardSketchHost = {
20
+ frame: (item: SceneItem<BoardSketchItem>, time: number, resolution: number) => Texture | null;
21
+ error: (id: string) => string | null;
22
+ release: (id: string) => void;
23
+ };
17
24
  type BoardRenderContext = {
18
- document: BoardDocument;
19
- /**
20
- * O(1) item lookup by id. Renderers that need to resolve references (arrow
21
- * bindings) must use this rather than scanning `document.items`, which would
22
- * be O(items) per card per frame.
23
- */
24
- getItem: (id: string) => BoardItem | null;
25
+ settings: BoardSettings;
26
+ scene: BoardScene;
25
27
  selectedIds: Set<string>;
26
28
  hoveredId: string | null;
27
- /** Nodes currently receiving a live resize preview. */
28
29
  resizingIds: Set<string>;
29
30
  palette: BoardRenderPalette;
30
- /**
31
- * Live shape colors resolved from CSS tokens (theme + space theme.css).
32
- * Prefer this over hard-coded light/dark tables at render time.
33
- */
34
31
  colors: BoardShapeColors;
35
- /** Resolved color mode, for fallback mapping when colors are unavailable. */
36
32
  colorScheme: "dark" | "light";
37
- /** Rendering backend selected by the host; Canvas uses compatibility paths. */
38
33
  rendererType: "gpu" | "canvas";
39
- /** Current camera zoom — used for text re-rasterisation buckets. */
40
34
  zoom: number;
41
- /** Stable preview texture key for an item, or null when it has no preview. */
42
- assetKey: (item: BoardItem) => string | null;
43
- /** Currently loaded texture for a key (null while loading). */
35
+ time: number;
36
+ sketches?: BoardSketchHost;
37
+ assetKey: (item: BoardSceneItem) => string | null;
44
38
  getTexture: (key: string) => Texture | null;
45
- /** Whether the preview for a key failed to load (for a failure placeholder). */
46
39
  hasError: (key: string) => boolean;
47
- /**
48
- * Whether a referenced workspace file could not be read, and why. Transient,
49
- * client-local state — never part of the document — so one client's outage is
50
- * not shown to everyone.
51
- */
52
40
  fileState: (path: string) => "ok" | "missing" | "unavailable";
53
- /** Reference-counted texture acquisition and release. */
54
41
  acquireTexture: (key: string) => void;
55
42
  releaseTexture: (key: string) => void;
56
43
  };
57
44
  type BoardCardRenderer = {
58
45
  id: string;
59
- canRender: (item: BoardItem, context: BoardRenderContext) => boolean;
60
- create: (item: BoardItem, context: BoardRenderContext) => Container;
61
- /** Efficiently sync an existing container to a (possibly changed) item. */
62
- update: (container: Container, item: BoardItem, context: BoardRenderContext) => void;
63
- /**
64
- * Draw the item as flat batched geometry into a shared `Graphics`, for the
65
- * far LOD used when too many cards are on screen to afford a container each.
66
- *
67
- * Implementing this opts the shape into the far layer: past the scene's
68
- * visible-card threshold it is drawn here instead of being materialised, so
69
- * cost stops scaling with node count. Omit it for shapes whose whole point is
70
- * their vector detail (strokes, arrows, frames) — those keep real containers.
71
- *
72
- * Must draw in world space and must not allocate per call beyond the path
73
- * itself; this runs once per far-capable item on every far-layer rebuild.
74
- */
75
- renderFar?: (graphics: Graphics, item: BoardItem, context: BoardRenderContext) => void;
46
+ canRender: (item: BoardSceneItem, context: BoardRenderContext) => boolean;
47
+ animated?: boolean;
48
+ create: (item: BoardSceneItem, context: BoardRenderContext) => Container;
49
+ update: (container: Container, item: BoardSceneItem, context: BoardRenderContext) => void;
50
+ renderFar?: (graphics: Graphics, item: BoardSceneItem, context: BoardRenderContext) => void;
76
51
  destroy?: (container: Container, context: BoardRenderContext) => void;
77
52
  };
78
- /**
79
- * Every registered renderer, for invariants that must hold across all of them —
80
- * notably that each can draw itself at far LOD, which is what keeps the far
81
- * layer's z-order correct (see board-scene).
82
- */
83
53
  declare function boardCardRenderersForTest(): readonly BoardCardRenderer[];
84
- declare function getBoardCardRenderer(item: BoardItem, context: BoardRenderContext): BoardCardRenderer;
54
+ declare function getBoardCardRenderer(item: BoardSceneItem, context: BoardRenderContext): BoardCardRenderer;
85
55
  declare function registerBoardCardRenderer(renderer: BoardCardRenderer): void;
86
56
  //#endregion
87
- export { BoardCardRenderer, BoardRenderContext, BoardRenderPalette, boardCardRenderersForTest, getBoardCardRenderer, registerBoardCardRenderer };
57
+ export { BoardCardRenderer, BoardRenderContext, BoardRenderPalette, BoardSketchHost, boardCardRenderersForTest, getBoardCardRenderer, registerBoardCardRenderer };
@@ -4,8 +4,10 @@ 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
+ import { effectCardRenderer } from "./effect-card-renderer.js";
7
8
  import { frameCardRenderer } from "./frame-card-renderer.js";
8
- import { geoCardRenderer } from "./geo-card-renderer.js";
9
+ import { shapeCardRenderer } from "./shape-card-renderer.js";
10
+ import { sketchCardRenderer } from "./sketch-card-renderer.js";
9
11
  import { imageCardRenderer } from "./image-card-renderer.js";
10
12
  import { textCardRenderer } from "./text-card-renderer.js";
11
13
  import { unknownCardRenderer } from "./unknown-card-renderer.js";
@@ -18,17 +20,14 @@ const boardCardRenderers = [
18
20
  audioCardRenderer,
19
21
  fileCardRenderer,
20
22
  taskCardRenderer,
21
- geoCardRenderer,
23
+ shapeCardRenderer,
22
24
  drawCardRenderer,
23
25
  arrowCardRenderer,
24
26
  frameCardRenderer,
27
+ effectCardRenderer,
28
+ sketchCardRenderer,
25
29
  unknownCardRenderer
26
30
  ];
27
- /**
28
- * Every registered renderer, for invariants that must hold across all of them —
29
- * notably that each can draw itself at far LOD, which is what keeps the far
30
- * layer's z-order correct (see board-scene).
31
- */
32
31
  function boardCardRenderersForTest() {
33
32
  return boardCardRenderers;
34
33
  }
@@ -1,79 +1,110 @@
1
- import { buildStrokeRibbonGeometry, computeDrawBounds } from "../../core/draw-geometry.js";
2
- import { pickBoardColor } from "../../core/palette.js";
1
+ import { applyMatrix } from "../../../protocol/dist/board-layout.js";
2
+ import { computeDrawBounds, createStrokeRibbonBuilder, trimDrawPoints } from "../../core/draw-geometry.js";
3
3
  import { positionShell } from "./base-card-renderer.js";
4
- import { drawFarStroke } from "./far-plate.js";
4
+ import { drawFarStroke, farStrokeSamples } from "./far-plate.js";
5
+ import { itemColor } from "../palette.js";
5
6
  import { Container, Graphics, Mesh, MeshGeometry, Texture } from "pixi.js";
6
7
  //#region src/board/render/renderers/draw-card-renderer.ts
7
8
  const partsByContainer = /* @__PURE__ */ new WeakMap();
8
- /** Canvas fallback for environments where Pixi intentionally has no Mesh pipe. */
9
- function createCanvasStroke(positions, indices, color, alpha) {
10
- const graphics = new Graphics();
9
+ function fillRibbon(graphics, ribbon, color) {
10
+ const { positions, indices } = ribbon;
11
11
  for (let offset = 0; offset < indices.length; offset += 3) {
12
12
  const a = (indices[offset] ?? 0) * 2;
13
13
  const b = (indices[offset + 1] ?? 0) * 2;
14
14
  const c = (indices[offset + 2] ?? 0) * 2;
15
15
  graphics.moveTo(positions[a] ?? 0, positions[a + 1] ?? 0).lineTo(positions[b] ?? 0, positions[b + 1] ?? 0).lineTo(positions[c] ?? 0, positions[c + 1] ?? 0).closePath();
16
16
  }
17
- return graphics.fill({
18
- color,
19
- alpha
20
- });
17
+ return graphics.fill({ color });
18
+ }
19
+ function replaceStroke(parts, next) {
20
+ if (parts.stroke) {
21
+ parts.root.removeChild(parts.stroke);
22
+ parts.stroke.destroy();
23
+ }
24
+ parts.stroke = next;
25
+ parts.root.addChild(next);
26
+ return next;
27
+ }
28
+ function tessellate(parts, points, size, paint) {
29
+ parts.ribbon ??= createStrokeRibbonBuilder(size);
30
+ const ribbon = parts.ribbon.build(points);
31
+ if (paint !== null) {
32
+ fillRibbon(parts.stroke instanceof Graphics ? parts.stroke.clear() : replaceStroke(parts, new Graphics()), ribbon, paint);
33
+ return;
34
+ }
35
+ if (ribbon.indices.length === 0) {
36
+ if (parts.stroke) parts.stroke.visible = false;
37
+ return;
38
+ }
39
+ if (parts.uvs.length < ribbon.positions.length) parts.uvs = new Float32Array(ribbon.positions.length * 2);
40
+ const { positions, indices } = ribbon;
41
+ const uvs = parts.uvs.subarray(0, positions.length);
42
+ if (parts.stroke instanceof Mesh) {
43
+ const geometry = parts.stroke.geometry;
44
+ geometry.positions = positions;
45
+ geometry.uvs = uvs;
46
+ geometry.indices = indices;
47
+ parts.stroke.visible = true;
48
+ return;
49
+ }
50
+ replaceStroke(parts, new Mesh({
51
+ geometry: new MeshGeometry({
52
+ positions,
53
+ uvs,
54
+ indices
55
+ }),
56
+ texture: Texture.WHITE
57
+ }));
21
58
  }
22
59
  function sync(container, item, context) {
23
60
  const parts = partsByContainer.get(container);
24
61
  if (!parts) return;
25
62
  positionShell(parts.root, item);
26
- const selected = context.selectedIds.has(item.id);
27
- const hovered = context.hoveredId === item.id;
28
- const color = pickBoardColor(context.colors, item.color, context.colorScheme);
29
- const sig = [
30
- item.points.length,
31
- item.size,
32
- item.color,
33
- selected,
34
- hovered,
35
- context.colorScheme,
36
- color.stroke
37
- ].join("|");
38
- if (sig !== parts.sig || item.points !== parts.points) {
39
- parts.sig = sig;
40
- parts.points = item.points;
41
- parts.baseWidth = computeDrawBounds(item.points, item.size).width;
42
- const ribbon = buildStrokeRibbonGeometry(item.points, item.size);
43
- const alpha = selected || hovered ? 1 : .92;
44
- const nextStroke = context.rendererType !== "canvas" ? new Mesh({
45
- geometry: new MeshGeometry({
46
- positions: ribbon.positions,
47
- indices: ribbon.indices
48
- }),
49
- texture: Texture.WHITE
50
- }) : createCanvasStroke(ribbon.positions, ribbon.indices, color.stroke, alpha);
51
- if (nextStroke instanceof Mesh) {
52
- nextStroke.tint = color.stroke;
53
- nextStroke.alpha = alpha;
54
- }
55
- const previous = parts.stroke;
56
- parts.stroke = nextStroke;
57
- parts.root.removeChild(previous);
58
- previous.destroy();
59
- parts.root.addChild(nextStroke);
63
+ const { points } = item.props;
64
+ const size = item.style.strokeWidth ?? 4;
65
+ const trim = item.style.trim;
66
+ const color = itemColor(context, item.style.stroke, "brand");
67
+ const paint = context.rendererType === "canvas" ? color : null;
68
+ if (points !== parts.points || points.length !== parts.count || size !== parts.size || trim !== parts.trim || paint !== parts.paint) {
69
+ if (size !== parts.size) parts.ribbon = null;
70
+ parts.points = points;
71
+ parts.count = points.length;
72
+ parts.size = size;
73
+ parts.trim = trim;
74
+ parts.paint = paint;
75
+ parts.box = computeDrawBounds(points, size);
76
+ tessellate(parts, trimDrawPoints(points, trim), size, paint);
60
77
  }
61
- const previewScale = item.frame.width / Math.max(1e-4, parts.baseWidth);
62
- parts.stroke.scale.set(Number.isFinite(previewScale) ? previewScale : 1);
78
+ const stroke = parts.stroke;
79
+ if (!stroke) return;
80
+ if (stroke instanceof Mesh) stroke.tint = color;
81
+ const { box } = parts;
82
+ const sx = item.frame.width / box.width;
83
+ const sy = item.frame.height / box.height;
84
+ stroke.scale.set(sx, sy);
85
+ stroke.position.set(-box.x * sx, -box.y * sy);
63
86
  }
64
87
  const drawCardRenderer = {
65
88
  id: "draw-card",
66
89
  canRender: (item) => item.type === "draw",
67
90
  create: (item, context) => {
68
91
  const root = new Container();
69
- const stroke = new Graphics();
70
- root.addChild(stroke);
71
92
  partsByContainer.set(root, {
72
93
  root,
73
- stroke,
74
- sig: "",
94
+ stroke: null,
95
+ ribbon: null,
96
+ uvs: /* @__PURE__ */ new Float32Array(0),
75
97
  points: null,
76
- baseWidth: 0
98
+ count: 0,
99
+ size: 0,
100
+ trim: void 0,
101
+ paint: null,
102
+ box: {
103
+ x: 0,
104
+ y: 0,
105
+ width: 1,
106
+ height: 1
107
+ }
77
108
  });
78
109
  if (item.type === "draw") sync(root, item, context);
79
110
  return root;
@@ -83,11 +114,13 @@ const drawCardRenderer = {
83
114
  },
84
115
  renderFar: (graphics, item, context) => {
85
116
  if (item.type !== "draw") return;
86
- const color = pickBoardColor(context.colors, item.color, context.colorScheme);
87
- drawFarStroke(graphics, item.points, {
88
- color: color.stroke,
89
- width: item.size,
90
- alpha: .92
117
+ const draw = item;
118
+ const matrix = context.scene.layout.matrix(draw.id);
119
+ const samples = farStrokeSamples(trimDrawPoints(draw.props.points, draw.style.trim));
120
+ drawFarStroke(graphics, samples.map((point) => applyMatrix(matrix, point)), {
121
+ color: itemColor(context, draw.style.stroke, "brand"),
122
+ width: draw.style.strokeWidth ?? 4,
123
+ alpha: 1
91
124
  });
92
125
  },
93
126
  destroy: (container) => {
@@ -0,0 +1,6 @@
1
+ import { BoardCardRenderer } from "./board-renderer-registry.js";
2
+ //#region src/board/render/renderers/effect-card-renderer.d.ts
3
+ declare const BOARD_EFFECT_MAX_PARTICLES = 4000;
4
+ declare const effectCardRenderer: BoardCardRenderer;
5
+ //#endregion
6
+ export { BOARD_EFFECT_MAX_PARTICLES, effectCardRenderer };
@@ -0,0 +1,197 @@
1
+ import { positionShell } from "./base-card-renderer.js";
2
+ import { drawFarPlate } from "./far-plate.js";
3
+ import { itemColor } from "../palette.js";
4
+ import { Container, Graphics } from "pixi.js";
5
+ //#region src/board/render/renderers/effect-card-renderer.ts
6
+ const BOARD_EFFECT_MAX_PARTICLES = 4e3;
7
+ const TRAIL_SAMPLES = 64;
8
+ const partsByContainer = /* @__PURE__ */ new WeakMap();
9
+ function hashString(value) {
10
+ let hash = 2166136261;
11
+ for (let index = 0; index < value.length; index += 1) {
12
+ hash ^= value.charCodeAt(index);
13
+ hash = Math.imul(hash, 16777619);
14
+ }
15
+ return hash >>> 0;
16
+ }
17
+ function random(seed, index, channel) {
18
+ let x = (seed ^ Math.imul(index + 1, 2654435761) ^ Math.imul(channel + 1, 2246822519)) >>> 0;
19
+ x ^= x >>> 16;
20
+ x = Math.imul(x, 2146121005);
21
+ x ^= x >>> 15;
22
+ x = Math.imul(x, 2221713035);
23
+ x ^= x >>> 16;
24
+ return (x >>> 0) / 4294967296;
25
+ }
26
+ function velocity(props, seed, index) {
27
+ const angle = (props.direction + (random(seed, index, 1) - .5) * props.spread) * Math.PI / 180;
28
+ const speed = props.speed * (.7 + random(seed, index, 2) * .6);
29
+ return {
30
+ vx: Math.cos(angle) * speed,
31
+ vy: Math.sin(angle) * speed
32
+ };
33
+ }
34
+ function drawParticles(graphics, props, width, height, time, seed, color) {
35
+ const rate = props.rate * props.intensity;
36
+ if (rate <= 0) return;
37
+ const life = props.life;
38
+ const interval = 1e3 / rate;
39
+ const newest = Math.floor(time / interval);
40
+ const count = Math.min(BOARD_EFFECT_MAX_PARTICLES, Math.ceil(life / interval));
41
+ for (let index = Math.max(0, newest - count + 1); index <= newest; index += 1) {
42
+ const age = (time - index * interval) / 1e3;
43
+ if (age < 0 || age * 1e3 > life) continue;
44
+ const progress = age * 1e3 / life;
45
+ const { vx, vy } = velocity(props, seed, index);
46
+ const x = random(seed, index, 3) * width + vx * age;
47
+ const y = random(seed, index, 4) * height + vy * age + .5 * props.gravity * age * age;
48
+ const size = props.particleSize * (.6 + random(seed, index, 5) * .8) * (1 - progress * .5);
49
+ graphics.circle(x, y, size / 2).fill({
50
+ color,
51
+ alpha: (1 - progress) * Math.min(1, props.intensity)
52
+ });
53
+ }
54
+ }
55
+ function drawImpact(graphics, props, width, height, time, seed, color) {
56
+ const life = props.life;
57
+ const cycle = Math.floor(time / life);
58
+ const age = (time - cycle * life) / 1e3;
59
+ const progress = age * 1e3 / life;
60
+ const count = Math.min(BOARD_EFFECT_MAX_PARTICLES, Math.round(props.rate * props.intensity));
61
+ const cx = width / 2;
62
+ const cy = height / 2;
63
+ const alpha = (1 - progress) ** 1.5 * Math.min(1, props.intensity);
64
+ const radius = Math.min(width, height) * .5 * Math.sqrt(progress);
65
+ graphics.circle(cx, cy, radius).stroke({
66
+ color,
67
+ width: Math.max(1, props.particleSize * (1 - progress)),
68
+ alpha: alpha * .8
69
+ });
70
+ const burst = hashString(`${seed}:${cycle}`);
71
+ for (let index = 0; index < count; index += 1) {
72
+ const { vx, vy } = velocity({
73
+ ...props,
74
+ spread: props.spread
75
+ }, burst, index);
76
+ const x = cx + vx * age;
77
+ const y = cy + vy * age + .5 * props.gravity * age * age;
78
+ graphics.circle(x, y, props.particleSize / 2 * (1 - progress * .6)).fill({
79
+ color,
80
+ alpha
81
+ });
82
+ }
83
+ }
84
+ function drawFlash(graphics, props, width, height, time, color) {
85
+ const alpha = (1 - time % props.life / props.life) ** 2 * Math.min(1, props.intensity);
86
+ if (alpha <= .001) return;
87
+ graphics.rect(0, 0, width, height).fill({
88
+ color,
89
+ alpha
90
+ });
91
+ }
92
+ function drawGlow(graphics, props, width, height, time, color) {
93
+ const pulse = .75 + .25 * Math.sin(time / props.life * Math.PI * 2);
94
+ const rings = 12;
95
+ const cx = width / 2;
96
+ const cy = height / 2;
97
+ for (let ring = rings; ring >= 1; ring -= 1) {
98
+ const fraction = ring / rings;
99
+ graphics.ellipse(cx, cy, width / 2 * fraction, height / 2 * fraction).fill({
100
+ color,
101
+ alpha: Math.min(1, props.intensity) * pulse * (1 - fraction) * 2 / rings
102
+ });
103
+ }
104
+ }
105
+ function drawTrail(graphics, parts, item, context, color) {
106
+ const target = item.props.follow ? context.scene.get(item.props.follow) : void 0;
107
+ const time = context.time;
108
+ if (!target) {
109
+ parts.trail.length = 0;
110
+ return;
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
116
+ };
117
+ const last = parts.trail.at(-1);
118
+ if (last && (time < last.t || time - last.t > item.props.life)) parts.trail.length = 0;
119
+ if (!last || Math.hypot(head.x - last.x, head.y - last.y) > .5) parts.trail.push(head);
120
+ while (parts.trail.length > TRAIL_SAMPLES || parts.trail[0] && time - parts.trail[0].t > item.props.life) parts.trail.shift();
121
+ for (let index = 1; index < parts.trail.length; index += 1) {
122
+ const a = parts.trail[index - 1];
123
+ const b = parts.trail[index];
124
+ const fade = 1 - (time - b.t) / item.props.life;
125
+ graphics.moveTo(a.x, a.y).lineTo(b.x, b.y).stroke({
126
+ color,
127
+ width: item.props.particleSize * fade,
128
+ alpha: fade * Math.min(1, item.props.intensity),
129
+ cap: "round"
130
+ });
131
+ }
132
+ }
133
+ function sync(container, item, context) {
134
+ const parts = partsByContainer.get(container);
135
+ if (!parts) return;
136
+ positionShell(parts.root, item);
137
+ const { width, height } = item.frame;
138
+ const { props } = item;
139
+ const color = itemColor(context, item.style.fill ?? item.style.stroke, "brand", "fill");
140
+ const seed = hashString(props.seed ?? item.id);
141
+ const time = Math.max(0, context.time);
142
+ const graphics = parts.graphics;
143
+ graphics.clear();
144
+ switch (props.kind) {
145
+ case "particles":
146
+ drawParticles(graphics, props, width, height, time, seed, color);
147
+ break;
148
+ case "impact":
149
+ drawImpact(graphics, props, width, height, time, seed, color);
150
+ break;
151
+ case "flash":
152
+ drawFlash(graphics, props, width, height, time, color);
153
+ break;
154
+ case "glow":
155
+ drawGlow(graphics, props, width, height, time, color);
156
+ break;
157
+ case "trail": drawTrail(graphics, parts, item, context, color);
158
+ }
159
+ if (context.selectedIds.has(item.id) || context.hoveredId === item.id) graphics.rect(0, 0, width, height).stroke({
160
+ color: context.palette.muted,
161
+ width: 1,
162
+ alpha: .6
163
+ });
164
+ }
165
+ const effectCardRenderer = {
166
+ id: "effect-card",
167
+ canRender: (item) => item.type === "effect",
168
+ animated: true,
169
+ create: (item, context) => {
170
+ const root = new Container();
171
+ const graphics = new Graphics();
172
+ root.addChild(graphics);
173
+ partsByContainer.set(root, {
174
+ root,
175
+ graphics,
176
+ trail: []
177
+ });
178
+ if (item.type === "effect") sync(root, item, context);
179
+ return root;
180
+ },
181
+ update: (container, item, context) => {
182
+ if (item.type === "effect") sync(container, item, context);
183
+ },
184
+ renderFar: (graphics, item, context) => {
185
+ if (item.type !== "effect") return;
186
+ drawFarPlate(graphics, item.frame, {
187
+ fill: itemColor(context, item.style.fill, "brand", "fill"),
188
+ fillAlpha: .08
189
+ });
190
+ },
191
+ destroy: (container) => {
192
+ partsByContainer.get(container)?.root.destroy({ children: true });
193
+ partsByContainer.delete(container);
194
+ }
195
+ };
196
+ //#endregion
197
+ export { BOARD_EFFECT_MAX_PARTICLES, effectCardRenderer };
@@ -1,32 +1,15 @@
1
- import { BoardFrame } from "../../../protocol/dist/board-document.js";
1
+ import { BoardFrame } from "../../../protocol/dist/board-layout.js";
2
+ import "../../../protocol/dist/index.js";
2
3
  import { Graphics } from "pixi.js";
3
4
  //#region src/board/render/renderers/far-plate.d.ts
4
5
  type FarPlateStyle = {
5
- /** Plate body fill. */
6
6
  fill: number;
7
7
  fillAlpha?: number;
8
- /** Optional accent band along the top edge, identifying the content kind. */
9
8
  accent?: number;
10
9
  accentAlpha?: number;
11
10
  };
12
- /**
13
- * Draw one card as a far-LOD plate: a filled body plus an optional top accent
14
- * band. Selection and hover are intentionally not drawn here — the interaction
15
- * overlay already renders selection in world space, above this layer.
16
- */
17
11
  declare function drawFarPlate(graphics: Graphics, frame: BoardFrame, style: FarPlateStyle): void;
18
- /**
19
- * Draw one card as a far-LOD polyline.
20
- *
21
- * The counterpart to `drawFarPlate` for shapes whose meaning is a path rather than
22
- * a region — strokes and arrows. It stays in the shared `Graphics`, so these keep
23
- * their place in the batch and therefore in document order: an unbatched shape
24
- * would be a live container, drawn above every plate no matter where the document
25
- * puts it.
26
- *
27
- * Points are decimated to a bound, because a freehand stroke can hold thousands of
28
- * samples that are sub-pixel apart at this zoom.
29
- */
12
+ declare function farStrokeSamples<T>(points: readonly T[]): readonly T[];
30
13
  declare function drawFarStroke(graphics: Graphics, points: ReadonlyArray<{
31
14
  x: number;
32
15
  y: number;
@@ -36,4 +19,4 @@ declare function drawFarStroke(graphics: Graphics, points: ReadonlyArray<{
36
19
  alpha?: number;
37
20
  }): void;
38
21
  //#endregion
39
- export { FarPlateStyle, drawFarPlate, drawFarStroke };
22
+ export { FarPlateStyle, drawFarPlate, drawFarStroke, farStrokeSamples };
@@ -1,16 +1,9 @@
1
1
  import { degToRad } from "../../geometry.js";
2
2
  //#region src/board/render/renderers/far-plate.ts
3
- /** Upper bound on points kept when a stroke is drawn at far LOD. */
4
3
  const FAR_STROKE_MAX_POINTS = 24;
5
- /** Accent band height as a fraction of the plate, clamped to sane world units. */
6
4
  const ACCENT_RATIO = .14;
7
5
  const ACCENT_MIN = 2;
8
6
  const ACCENT_MAX = 10;
9
- /**
10
- * Trace a frame's rectangle, honouring rotation. Unrotated frames take the
11
- * fast `rect` path; rotated ones emit four explicit corners (still one path,
12
- * no arcs) so the far layer never needs a per-card transform.
13
- */
14
7
  function traceFrame(graphics, frame, inset = 0, heightOverride) {
15
8
  const x = frame.x + inset;
16
9
  const y = frame.y + inset;
@@ -39,11 +32,6 @@ function traceFrame(graphics, frame, inset = 0, heightOverride) {
39
32
  const d = corner(x, y + height);
40
33
  graphics.moveTo(a.x, a.y).lineTo(b.x, b.y).lineTo(c.x, c.y).lineTo(d.x, d.y).closePath();
41
34
  }
42
- /**
43
- * Draw one card as a far-LOD plate: a filled body plus an optional top accent
44
- * band. Selection and hover are intentionally not drawn here — the interaction
45
- * overlay already renders selection in world space, above this layer.
46
- */
47
35
  function drawFarPlate(graphics, frame, style) {
48
36
  if (frame.width <= 0 || frame.height <= 0) return;
49
37
  traceFrame(graphics, frame);
@@ -58,30 +46,23 @@ function drawFarPlate(graphics, frame, style) {
58
46
  alpha: style.accentAlpha ?? .9
59
47
  });
60
48
  }
61
- /**
62
- * Draw one card as a far-LOD polyline.
63
- *
64
- * The counterpart to `drawFarPlate` for shapes whose meaning is a path rather than
65
- * a region — strokes and arrows. It stays in the shared `Graphics`, so these keep
66
- * their place in the batch and therefore in document order: an unbatched shape
67
- * would be a live container, drawn above every plate no matter where the document
68
- * puts it.
69
- *
70
- * Points are decimated to a bound, because a freehand stroke can hold thousands of
71
- * samples that are sub-pixel apart at this zoom.
72
- */
49
+ function farStrokeSamples(points) {
50
+ if (points.length <= FAR_STROKE_MAX_POINTS) return points;
51
+ const step = Math.ceil(points.length / FAR_STROKE_MAX_POINTS);
52
+ const out = [];
53
+ for (let i = 0; i < points.length - 1; i += step) out.push(points[i]);
54
+ out.push(points[points.length - 1]);
55
+ return out;
56
+ }
73
57
  function drawFarStroke(graphics, points, style) {
74
- if (points.length < 2) return;
75
- const step = Math.max(1, Math.ceil(points.length / FAR_STROKE_MAX_POINTS));
76
- const first = points[0];
77
- if (!first) return;
58
+ const samples = farStrokeSamples(points);
59
+ if (samples.length < 2) return;
60
+ const first = samples[0];
78
61
  graphics.moveTo(first.x, first.y);
79
- for (let i = step; i < points.length; i += step) {
80
- const point = points[i];
81
- if (point) graphics.lineTo(point.x, point.y);
62
+ for (let i = 1; i < samples.length; i += 1) {
63
+ const point = samples[i];
64
+ graphics.lineTo(point.x, point.y);
82
65
  }
83
- const last = points[points.length - 1];
84
- if (last) graphics.lineTo(last.x, last.y);
85
66
  graphics.stroke({
86
67
  color: style.color,
87
68
  width: style.width,
@@ -89,4 +70,4 @@ function drawFarStroke(graphics, points, style) {
89
70
  });
90
71
  }
91
72
  //#endregion
92
- export { drawFarPlate, drawFarStroke };
73
+ export { drawFarPlate, drawFarStroke, farStrokeSamples };