@neta-art/cohub 8.23.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 +7 -27
  30. package/dist/board/headless/index.js +20 -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 +2296 -3864
  91. package/dist/chunks/host-bridge.d.ts +253 -0
  92. package/dist/chunks/http.d.ts +238 -58
  93. package/dist/chunks/http.js +1061 -1034
  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 +76 -193
  101. package/dist/index.js +2037 -1191
  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,249 +1,403 @@
1
- import { BOARD_BUILTIN_CAPABILITIES, DEFAULT_BOARD_RENDER_LIMITS } from "../protocol/dist/board-constants.js";
2
- import { BOARD_ANIMATION_CHANNELS, BoardCompositionSchema, BoardProceduralClipSchema, BoardTrackSchema } from "../protocol/dist/board-composition.js";
3
- import { estimateBuiltinBoardClipCost, estimateBuiltinBoardEffectCost, validateBuiltinBoardClip, validateBuiltinBoardEffect } from "../protocol/dist/board-capability-registry.js";
1
+ import { BOARD_CAMERA_TARGET } from "../protocol/dist/board-model.js";
2
+ import { resolveBoardProperty } from "../protocol/dist/board-patch.js";
4
3
  //#region src/board/animation.ts
5
- function track(input) {
6
- return BoardTrackSchema.parse(input);
4
+ const linear = (x) => x;
5
+ function cubicBezier(x1, y1, x2, y2) {
6
+ const cx = 3 * x1;
7
+ const bx = 3 * (x2 - x1) - cx;
8
+ const ax = 1 - cx - bx;
9
+ const cy = 3 * y1;
10
+ const by = 3 * (y2 - y1) - cy;
11
+ const ay = 1 - cy - by;
12
+ const sampleX = (t) => ((ax * t + bx) * t + cx) * t;
13
+ const sampleY = (t) => ((ay * t + by) * t + cy) * t;
14
+ const slopeX = (t) => (3 * ax * t + 2 * bx) * t + cx;
15
+ return (x) => {
16
+ if (x <= 0) return 0;
17
+ if (x >= 1) return 1;
18
+ let t = x;
19
+ for (let index = 0; index < 8; index += 1) {
20
+ const error = sampleX(t) - x;
21
+ if (Math.abs(error) < 1e-6) return sampleY(t);
22
+ const slope = slopeX(t);
23
+ if (Math.abs(slope) < 1e-6) break;
24
+ t -= error / slope;
25
+ }
26
+ let low = 0;
27
+ let high = 1;
28
+ t = x;
29
+ for (let index = 0; index < 32; index += 1) {
30
+ const value = sampleX(t);
31
+ if (Math.abs(value - x) < 1e-6) break;
32
+ if (value < x) low = t;
33
+ else high = t;
34
+ t = (low + high) / 2;
35
+ }
36
+ return sampleY(t);
37
+ };
7
38
  }
8
- function proceduralClip(input) {
9
- return BoardProceduralClipSchema.parse({
10
- ...input,
11
- kindVersion: input.kindVersion ?? 1
12
- });
39
+ function steps(count, position) {
40
+ const jumps = position === "jump-none" ? count - 1 : position === "jump-both" ? count + 1 : count;
41
+ const start = position === "start" || position === "jump-start" || position === "jump-both";
42
+ return (x) => {
43
+ if (x >= 1) return 1;
44
+ const step = Math.floor(x * count) + (start ? 1 : 0);
45
+ return Math.min(1, Math.max(0, step / Math.max(1, jumps)));
46
+ };
13
47
  }
14
- function composition(input) {
15
- return BoardCompositionSchema.parse(input);
16
- }
17
- function compileComposition(input) {
18
- return composition({
19
- id: input.id,
20
- name: input.name,
21
- timeline: {
22
- duration: input.duration,
23
- tracks: (input.tracks ?? []).map(track),
24
- clips: (input.clips ?? []).map(proceduralClip),
25
- markers: input.markers ?? []
26
- },
27
- playback: input.playback ?? {
28
- loop: false,
29
- endBehavior: "hold",
30
- reducedMotion: { mode: "base" }
31
- },
32
- metadata: input.metadata ?? {},
33
- revision: 0
48
+ const KEYWORD_EASES = {
49
+ linear,
50
+ ease: cubicBezier(.25, .1, .25, 1),
51
+ "ease-in": cubicBezier(.42, 0, 1, 1),
52
+ "ease-out": cubicBezier(0, 0, .58, 1),
53
+ "ease-in-out": cubicBezier(.42, 0, .58, 1),
54
+ "step-start": steps(1, "start"),
55
+ "step-end": steps(1, "end")
56
+ };
57
+ const easeCache = /* @__PURE__ */ new Map();
58
+ function boardEase(ease) {
59
+ if (!ease) return linear;
60
+ const key = ease.trim();
61
+ const keyword = KEYWORD_EASES[key];
62
+ if (keyword) return keyword;
63
+ const cached = easeCache.get(key);
64
+ if (cached) return cached;
65
+ let fn = linear;
66
+ const bezier = /^cubic-bezier\(([^)]+)\)$/.exec(key);
67
+ const stepped = /^steps\(\s*(\d+)\s*(?:,\s*([a-z-]+)\s*)?\)$/.exec(key);
68
+ if (bezier?.[1]) {
69
+ const [x1 = 0, y1 = 0, x2 = 1, y2 = 1] = bezier[1].split(",").map(Number);
70
+ fn = cubicBezier(x1, y1, x2, y2);
71
+ } else if (stepped?.[1]) fn = steps(Number(stepped[1]), stepped[2] ?? "end");
72
+ if (easeCache.size > 512) easeCache.clear();
73
+ easeCache.set(key, fn);
74
+ return fn;
75
+ }
76
+ function boardAnimationTime(header, elapsed) {
77
+ const local = Math.max(0, elapsed - header.delay);
78
+ if (header.loop || header.play === "always") return header.duration > 0 ? local % header.duration : 0;
79
+ if (local <= header.duration) return local;
80
+ return header.end === "reset" ? null : header.duration;
81
+ }
82
+ function compileTrack(animationId, id, track, targetType) {
83
+ const resolved = resolveBoardProperty(targetType, track.property);
84
+ if (!resolved.ok) return null;
85
+ const { spec } = resolved;
86
+ const times = new Float64Array(track.keyframes.length);
87
+ const values = [];
88
+ const eases = [];
89
+ track.keyframes.forEach((keyframe, index) => {
90
+ times[index] = keyframe.at;
91
+ values.push(keyframe.value);
92
+ eases.push(boardEase(keyframe.ease));
34
93
  });
94
+ return {
95
+ id,
96
+ animationId,
97
+ target: track.target,
98
+ property: track.property,
99
+ path: track.property.split("."),
100
+ kind: spec.kind,
101
+ multiplicative: Boolean(spec.multiplicative),
102
+ composite: track.composite,
103
+ spline: track.interpolation === "spline" && spec.kind === "vec2",
104
+ step: track.interpolation === "step" || spec.kind === "discrete",
105
+ orient: Boolean(track.orient) && spec.kind === "vec2" && track.property === "position",
106
+ times,
107
+ values,
108
+ eases,
109
+ start: times[0] ?? 0
110
+ };
35
111
  }
36
- const clamp01 = (value) => Math.max(0, Math.min(1, value));
37
- function sampleEasing(easing, value) {
38
- const t = clamp01(value);
39
- switch (easing) {
40
- case "ease-in-quad": return t * t;
41
- case "ease-out-quad": return 1 - (1 - t) ** 2;
42
- case "ease-in-out-quad": return t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
43
- case "ease-in-cubic": return t ** 3;
44
- case "ease-out-cubic": return 1 - (1 - t) ** 3;
45
- case "ease-in-out-cubic": return t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2;
46
- case "ease-out-quart": return 1 - (1 - t) ** 4;
47
- case "ease-out-expo": return t === 1 ? 1 : 1 - 2 ** (-10 * t);
48
- default: return t;
112
+ function compileBoardAnimations(document) {
113
+ const animations = /* @__PURE__ */ new Map();
114
+ for (const [animationId, animation] of Object.entries(document.animations)) animations.set(animationId, compileAnimation(animationId, animation, document));
115
+ const incoming = new Map([...animations.keys()].map((id) => [id, 0]));
116
+ for (const animation of animations.values()) for (const track of animation.children) incoming.set(track.target, (incoming.get(track.target) ?? 0) + 1);
117
+ const order = [...animations.keys()].filter((id) => incoming.get(id) === 0).sort();
118
+ for (let index = 0; index < order.length; index += 1) {
119
+ const animation = animations.get(order[index]);
120
+ for (const track of animation?.children ?? []) {
121
+ const count = (incoming.get(track.target) ?? 1) - 1;
122
+ incoming.set(track.target, count);
123
+ if (count === 0) order.push(track.target);
124
+ }
49
125
  }
126
+ if (order.length !== animations.size) throw new Error("Animation graph contains a cycle");
127
+ return {
128
+ animations,
129
+ document,
130
+ order
131
+ };
50
132
  }
51
- function interpolate(left, right, progress) {
52
- if (typeof left === "number" && typeof right === "number") return left + (right - left) * progress;
53
- if (left && right && typeof left === "object" && typeof right === "object" && !Array.isArray(left) && !Array.isArray(right)) {
54
- const a = left;
55
- const b = right;
56
- const result = {};
57
- for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) result[key] = interpolate(a[key], b[key], progress);
58
- return result;
59
- }
60
- return progress < 1 ? left : right;
61
- }
62
- /** Deterministically sample one validated Track without touching scene state. */
63
- function sampleTrack(trackValue, time) {
64
- const frames = trackValue.keyframes;
65
- const first = frames[0];
66
- const last = frames.at(-1);
67
- if (!first || !last) return null;
68
- if (time < first.time) {
69
- if (trackValue.fill !== "backwards" && trackValue.fill !== "both") return null;
70
- return {
71
- target: trackValue.target,
72
- channel: trackValue.channel,
73
- value: first.value
74
- };
75
- }
76
- if (time > last.time) {
77
- if (trackValue.fill !== "forwards" && trackValue.fill !== "both") return null;
78
- return {
79
- target: trackValue.target,
80
- channel: trackValue.channel,
81
- value: last.value
82
- };
133
+ function compileAnimation(animationId, animation, document) {
134
+ const { tracks, ...header } = animation;
135
+ const compiled = [];
136
+ const children = [];
137
+ for (const [id, track] of Object.entries(tracks).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
138
+ const targetType = track.target === "camera" ? BOARD_CAMERA_TARGET : document.animations[track.target] ? "animation" : document.items[track.target]?.type;
139
+ if (!targetType) continue;
140
+ const entry = compileTrack(animationId, id, track, targetType);
141
+ if (!entry) continue;
142
+ (targetType === "animation" ? children : compiled).push(entry);
83
143
  }
144
+ return {
145
+ id: animationId,
146
+ header,
147
+ tracks: compiled,
148
+ children
149
+ };
150
+ }
151
+ function keyframeIndex(times, time) {
84
152
  let low = 0;
85
- let high = frames.length;
153
+ let high = times.length - 1;
154
+ if (time < times[0]) return -1;
155
+ if (time >= times[high]) return high;
86
156
  while (low < high) {
87
- const middle = low + high >>> 1;
88
- if ((frames[middle]?.time ?? Number.POSITIVE_INFINITY) <= time) low = middle + 1;
89
- else high = middle;
157
+ const mid = low + high + 1 >> 1;
158
+ if (times[mid] <= time) low = mid;
159
+ else high = mid - 1;
90
160
  }
91
- const left = frames[Math.max(0, low - 1)] ?? first;
92
- const right = frames[Math.min(frames.length - 1, low)] ?? last;
93
- if (left === right || trackValue.interpolation === "step") return {
94
- target: trackValue.target,
95
- channel: trackValue.channel,
96
- value: left.value
161
+ return low;
162
+ }
163
+ function isVec(value) {
164
+ return Boolean(value) && typeof value === "object" && typeof value.x === "number" && typeof value.y === "number";
165
+ }
166
+ function toVec(value) {
167
+ if (typeof value === "number") return {
168
+ x: value,
169
+ y: value
97
170
  };
98
- const span = Math.max(Number.EPSILON, right.time - left.time);
99
- const progress = sampleEasing(right.easing ?? "linear", (time - left.time) / span);
100
- return {
101
- target: trackValue.target,
102
- channel: trackValue.channel,
103
- value: interpolate(left.value, right.value, progress)
171
+ return isVec(value) ? value : {
172
+ x: 0,
173
+ y: 0
104
174
  };
105
175
  }
106
- function sampleCompositionTracks(value, time) {
107
- const position = Math.max(0, Math.min(value.timeline.duration, time));
108
- return value.timeline.tracks.flatMap((item) => {
109
- const sampled = sampleTrack(item, position);
110
- return sampled ? [sampled] : [];
111
- });
176
+ const lerp = (a, b, t) => a + (b - a) * t;
177
+ function catmullRom(p0, p1, p2, p3, t) {
178
+ const t2 = t * t;
179
+ const t3 = t2 * t;
180
+ return .5 * (2 * p1 + (-p0 + p2) * t + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 + (-p0 + 3 * p1 - 3 * p2 + p3) * t3);
112
181
  }
113
- const ZERO_COST = {
114
- particles: 0,
115
- vertices: 0,
116
- dynamicVertices: 0,
117
- drawCalls: 0,
118
- filterPasses: 0,
119
- renderTexturePixels: 0,
120
- textureBytes: 0,
121
- bufferBytes: 0,
122
- simulationSteps: 0
123
- };
124
- const DEFAULT_BOARD_LIMITS = DEFAULT_BOARD_RENDER_LIMITS;
125
- function builtinDefinition(capability) {
126
- return {
127
- ...capability,
128
- validate() {
129
- return [];
130
- },
131
- estimateCost(params) {
132
- return estimateBuiltinBoardClipCost({
133
- kind: capability.id,
134
- params
135
- });
136
- }
137
- };
182
+ function hex(color) {
183
+ return `#${Math.max(0, Math.min(16777215, Math.round(color))).toString(16).padStart(6, "0")}`;
138
184
  }
139
- var BoardExtensionRegistry = class {
140
- #extensions = /* @__PURE__ */ new Map();
141
- #presets = /* @__PURE__ */ new Map();
142
- constructor() {
143
- for (const capability of BOARD_BUILTIN_CAPABILITIES) this.register(builtinDefinition(capability));
144
- }
145
- register(definition) {
146
- const key = `${definition.kind}:${definition.id}@${definition.version}`;
147
- const target = definition.kind === "preset" ? this.#presets : this.#extensions;
148
- if (target.has(key)) throw new Error(`Board extension is already registered: ${key}`);
149
- target.set(key, definition);
150
- return this;
185
+ function mixColor(a, b, t, resolve) {
186
+ const from = resolve(a);
187
+ const to = resolve(b);
188
+ if (from === null || to === null) return t < 1 ? a : b;
189
+ const channel = (shift) => Math.round(lerp(from >> shift & 255, to >> shift & 255, t));
190
+ return hex(channel(16) << 16 | channel(8) << 8 | channel(0));
191
+ }
192
+ function parseHexColor(color) {
193
+ if (typeof color !== "string") return null;
194
+ const match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());
195
+ if (!match?.[1]) return null;
196
+ const digits = match[1].length === 3 ? [...match[1]].map((digit) => digit + digit).join("") : match[1];
197
+ return Number.parseInt(digits, 16);
198
+ }
199
+ function blend(track, index, time, resolveColor) {
200
+ const { times, values } = track;
201
+ const from = values[index];
202
+ const to = values[index + 1];
203
+ const t0 = times[index];
204
+ const t1 = times[index + 1];
205
+ if (to === void 0 || t1 <= t0) return from;
206
+ const progress = track.eases[index + 1]((time - t0) / (t1 - t0));
207
+ if (track.step) return progress >= 1 ? to : from;
208
+ switch (track.kind) {
209
+ case "number": return typeof from === "number" && typeof to === "number" ? lerp(from, to, progress) : from;
210
+ case "vec2":
211
+ case "scale": {
212
+ if (track.kind === "scale" && typeof from === "number" && typeof to === "number") return lerp(from, to, progress);
213
+ const a = toVec(from);
214
+ const b = toVec(to);
215
+ if (track.spline) {
216
+ const before = toVec(values[index - 1] ?? from);
217
+ const after = toVec(values[index + 2] ?? to);
218
+ return {
219
+ x: catmullRom(before.x, a.x, b.x, after.x, progress),
220
+ y: catmullRom(before.y, a.y, b.y, after.y, progress)
221
+ };
222
+ }
223
+ return {
224
+ x: lerp(a.x, b.x, progress),
225
+ y: lerp(a.y, b.y, progress)
226
+ };
227
+ }
228
+ case "color": return mixColor(from, to, progress, resolveColor);
229
+ default: return progress >= 1 ? to : from;
151
230
  }
152
- capabilities() {
153
- return [...this.#extensions.values(), ...this.#presets.values()].map((definition) => ({
154
- kind: definition.kind,
155
- id: definition.id,
156
- version: definition.version,
157
- ...definition.digest ? { digest: definition.digest } : {},
158
- ...definition.renderers ? { renderers: definition.renderers } : {},
159
- ...definition.fallbackId ? { fallbackId: definition.fallbackId } : {},
160
- ...definition.schema ? { schema: definition.schema } : {}
161
- }));
231
+ }
232
+ function sampleBoardTrack(track, time, resolveColor = parseHexColor) {
233
+ const index = keyframeIndex(track.times, time);
234
+ if (index < 0) return track.values[0];
235
+ return blend(track, index, time, resolveColor);
236
+ }
237
+ function orientation(track, time, resolveColor) {
238
+ const span = Math.max(1, (track.times[track.times.length - 1] - track.start) / 1e3);
239
+ const a = toVec(sampleBoardTrack(track, Math.max(track.start, time - span), resolveColor));
240
+ const b = toVec(sampleBoardTrack(track, time + span, resolveColor));
241
+ if (Math.hypot(b.x - a.x, b.y - a.y) < 1e-6) return null;
242
+ return Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI;
243
+ }
244
+ function readPath(value, path) {
245
+ let current = value;
246
+ for (const segment of path) {
247
+ if (typeof current === "number" && (segment === "x" || segment === "y")) return current;
248
+ if (!current || typeof current !== "object") return void 0;
249
+ current = current[segment];
162
250
  }
163
- compilePreset(id, version, params) {
164
- const preset = this.#presets.get(`preset:${id}@${version}`);
165
- if (!preset) throw new Error(`Unknown Board preset: ${id}@${version}`);
166
- return preset.compile(params);
251
+ return current;
252
+ }
253
+ function writePath(value, path, next, depth = 0) {
254
+ const key = path[depth];
255
+ if (depth === path.length - 1) return {
256
+ ...value,
257
+ [key]: next
258
+ };
259
+ let child = value[key];
260
+ if (typeof child === "number") child = {
261
+ x: child,
262
+ y: child
263
+ };
264
+ const base = child && typeof child === "object" ? child : {};
265
+ return {
266
+ ...value,
267
+ [key]: writePath(base, path, next, depth + 1)
268
+ };
269
+ }
270
+ function combine(track, base, value) {
271
+ if (track.composite !== "add") return value;
272
+ if (track.kind === "number") {
273
+ if (typeof base !== "number" || typeof value !== "number") return value;
274
+ return track.multiplicative ? base * value : base + value;
167
275
  }
168
- validate(input) {
169
- const parsed = BoardCompositionSchema.safeParse(input.composition);
170
- if (!parsed.success) return {
171
- valid: false,
172
- diagnostics: parsed.error.issues.map((issue) => ({
173
- severity: "error",
174
- code: "INVALID_COMPOSITION",
175
- message: issue.message,
176
- path: issue.path.join(".")
177
- })),
178
- peakCost: { ...ZERO_COST }
276
+ if (track.kind === "vec2" || track.kind === "scale") {
277
+ if (track.multiplicative) {
278
+ if (typeof base === "number" && typeof value === "number") return base * value;
279
+ const a = toVec(base ?? 1);
280
+ const b = toVec(value);
281
+ return {
282
+ x: a.x * b.x,
283
+ y: a.y * b.y
284
+ };
285
+ }
286
+ const a = toVec(base);
287
+ const b = toVec(value);
288
+ return {
289
+ x: a.x + b.x,
290
+ y: a.y + b.y
179
291
  };
180
- const diagnostics = [];
181
- const composition = parsed.data;
182
- const cost = { ...ZERO_COST };
183
- for (const effect of input.effects ?? []) {
184
- diagnostics.push(...validateBuiltinBoardEffect(effect, `effects.${effect.id}`));
185
- if (!this.#extensions.has(`effect:${effect.kind}@${effect.kindVersion}`)) diagnostics.push({
186
- severity: "warning",
187
- code: "UNKNOWN_EFFECT",
188
- message: `No renderer is registered for ${effect.kind}@${effect.kindVersion}`,
189
- path: `effects.${effect.id}`
190
- });
292
+ }
293
+ return value;
294
+ }
295
+ function outranks(track, time, other, otherTime) {
296
+ const elapsed = time - track.start;
297
+ const otherElapsed = otherTime - other.start;
298
+ if (elapsed >= 0 !== otherElapsed >= 0) return elapsed >= 0;
299
+ const distance = Math.abs(elapsed);
300
+ const otherDistance = Math.abs(otherElapsed);
301
+ if (distance !== otherDistance) return distance < otherDistance;
302
+ return track.id > other.id;
303
+ }
304
+ function evaluateBoardAnimations(compiled, times, options = {}) {
305
+ const resolveColor = options.resolveColor ?? parseHexColor;
306
+ const local = new Map(times instanceof Map ? times : Object.entries(times));
307
+ for (const id of compiled.order) {
308
+ const animation = compiled.animations.get(id);
309
+ const time = local.get(id);
310
+ if (!animation || time === void 0) continue;
311
+ for (const track of animation.children) {
312
+ const value = sampleBoardTrack(track, time, resolveColor);
313
+ if (typeof value === "number") local.set(track.target, Math.max(0, value));
191
314
  }
192
- const events = [];
193
- for (const clip of composition.timeline.clips) {
194
- const definition = this.#extensions.get(`clip:${clip.kind}@${clip.kindVersion}`);
195
- if (!definition) {
196
- diagnostics.push({
197
- severity: "warning",
198
- code: "UNKNOWN_CLIP",
199
- message: `No renderer is registered for ${clip.kind}@${clip.kindVersion}`
200
- });
315
+ }
316
+ const groups = /* @__PURE__ */ new Map();
317
+ for (const [id, time] of local) {
318
+ const animation = compiled.animations.get(id);
319
+ if (!animation) continue;
320
+ for (const track of animation.tracks) {
321
+ const key = `${track.target}\u0000${track.property}`;
322
+ let group = groups.get(key);
323
+ if (!group) {
324
+ group = {
325
+ target: track.target,
326
+ path: track.path,
327
+ winnerTime: 0,
328
+ adds: []
329
+ };
330
+ groups.set(key, group);
331
+ }
332
+ if (track.composite === "add") {
333
+ group.adds.push([track, time]);
201
334
  continue;
202
335
  }
203
- diagnostics.push(...validateBuiltinBoardClip(clip, `composition.timeline.clips.${clip.id}`));
204
- diagnostics.push(...definition.validate?.(clip.params) ?? []);
205
- const estimate = definition.estimateCost(clip.params, input.profile ?? "high");
206
- const clipCost = { ...ZERO_COST };
207
- for (const key of Object.keys(clipCost)) clipCost[key] = estimate[key] ?? 0;
208
- events.push({
209
- at: clip.start,
210
- direction: 1,
211
- cost: clipCost
212
- });
213
- events.push({
214
- at: clip.start + clip.duration,
215
- direction: -1,
216
- cost: clipCost
217
- });
336
+ if (!group.winner || outranks(track, time, group.winner, group.winnerTime)) {
337
+ group.winner = track;
338
+ group.winnerTime = time;
339
+ }
218
340
  }
219
- events.sort((left, right) => left.at - right.at || left.direction - right.direction);
220
- const active = { ...ZERO_COST };
221
- for (const event of events) for (const key of Object.keys(active)) {
222
- active[key] += event.cost[key] * event.direction;
223
- cost[key] = Math.max(cost[key], active[key]);
341
+ }
342
+ const items = /* @__PURE__ */ new Map();
343
+ const camera = {};
344
+ const ordered = [...groups.values()].sort((a, b) => a.path.length - b.path.length);
345
+ for (const group of ordered) {
346
+ const isCamera = group.target === BOARD_CAMERA_TARGET;
347
+ const base = isCamera ? void 0 : items.get(group.target) ?? compiled.document.items[group.target];
348
+ if (!isCamera && !base) continue;
349
+ let value = isCamera ? camera[group.path[0]] : readPath(base, group.path);
350
+ let orientTrack = null;
351
+ if (group.winner) {
352
+ value = sampleBoardTrack(group.winner, group.winnerTime, resolveColor);
353
+ if (group.winner.orient) orientTrack = [group.winner, group.winnerTime];
224
354
  }
225
- for (const effect of input.effects ?? []) {
226
- const estimate = estimateBuiltinBoardEffectCost(effect);
227
- for (const key of Object.keys(cost)) cost[key] += estimate[key] ?? 0;
355
+ for (const [track, time] of group.adds) {
356
+ value = combine(track, value, sampleBoardTrack(track, time, resolveColor));
357
+ if (track.orient && !orientTrack) orientTrack = [track, time];
228
358
  }
229
- const limits = input.limits ?? DEFAULT_BOARD_LIMITS;
230
- for (const key of Object.keys(cost)) if (cost[key] > limits[key]) diagnostics.push({
231
- severity: "warning",
232
- code: "RENDER_BUDGET_EXCEEDED",
233
- message: `${key} peaks at ${cost[key]}, above ${limits[key]}`,
234
- path: key,
235
- adaptation: { quality: "lower" }
236
- });
237
- return {
238
- valid: !diagnostics.some((item) => item.severity === "error"),
239
- diagnostics,
240
- peakCost: cost
241
- };
359
+ if (isCamera) {
360
+ camera[group.path[0]] = value;
361
+ continue;
362
+ }
363
+ let next = writePath(base, group.path, value);
364
+ if (orientTrack) {
365
+ const angle = orientation(orientTrack[0], orientTrack[1], resolveColor);
366
+ if (angle !== null) next = {
367
+ ...next,
368
+ rotation: base.rotation + angle
369
+ };
370
+ }
371
+ items.set(group.target, next);
242
372
  }
243
- };
244
- function createBoardExtensionRegistry() {
245
- return new BoardExtensionRegistry();
373
+ return {
374
+ items,
375
+ camera,
376
+ times: local
377
+ };
378
+ }
379
+ function applyBoardEvaluation(document, evaluation) {
380
+ if (evaluation.items.size === 0) return document;
381
+ const items = { ...document.items };
382
+ for (const [id, item] of evaluation.items) items[id] = item;
383
+ return {
384
+ ...document,
385
+ items
386
+ };
387
+ }
388
+ function boardDocumentAt(document, animationId, time, options = {}) {
389
+ const compiled = compileBoardAnimations(document);
390
+ const times = /* @__PURE__ */ new Map();
391
+ for (const [id, animation] of compiled.animations) if (animation.header.play === "always") times.set(id, boardAnimationTime(animation.header, time) ?? 0);
392
+ if (animationId) {
393
+ const animation = compiled.animations.get(animationId);
394
+ if (animation) times.set(animationId, Math.min(Math.max(0, time), animation.header.duration));
395
+ }
396
+ const evaluation = evaluateBoardAnimations(compiled, times, options);
397
+ return {
398
+ document: applyBoardEvaluation(document, evaluation),
399
+ evaluation
400
+ };
246
401
  }
247
- const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
248
402
  //#endregion
249
- export { BOARD_CHANNELS, BoardExtensionRegistry, DEFAULT_BOARD_LIMITS, compileComposition, composition, createBoardExtensionRegistry, proceduralClip, sampleCompositionTracks, sampleEasing, sampleTrack, track };
403
+ export { applyBoardEvaluation, boardAnimationTime, boardDocumentAt, boardEase, compileBoardAnimations, evaluateBoardAnimations, parseHexColor, sampleBoardTrack };