@cueframe/kernel 0.2.53 → 0.2.54

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 (40) hide show
  1. package/dist/_members/composition/ops/compositionOps.shared.d.ts +1 -0
  2. package/dist/_members/composition/validators/composition.d.ts +80 -0
  3. package/dist/_members/composition/validators/render.d.ts +40 -0
  4. package/dist/_members/composition-ops/index.d.ts +15 -0
  5. package/dist/_members/core/render/compositionAudioAndDepth.d.ts +2 -1
  6. package/dist/_members/primitives/lib/text/spec.d.ts +1 -0
  7. package/dist/authoring/build-tools.js +5 -5
  8. package/dist/authoring/context.js +4 -4
  9. package/dist/authoring/tools.js +4 -4
  10. package/dist/authoring.js +6 -6
  11. package/dist/{chunk-P3DYA5NA.js → chunk-C3ARYFN6.js} +4 -4
  12. package/dist/{chunk-D2EG7J7X.js → chunk-DFB7UFZD.js} +1 -1
  13. package/dist/{chunk-ZIPGDGBR.js → chunk-FYMXVYWS.js} +1 -1
  14. package/dist/{chunk-LPUQTBKU.js → chunk-GZMKTLO3.js} +11 -1
  15. package/dist/{chunk-5E4YP44R.js → chunk-Q2FWFTDZ.js} +1 -1
  16. package/dist/{chunk-CJMUTCJW.js → chunk-QCGT3M7M.js} +1 -1
  17. package/dist/{chunk-4DM63RTI.js → chunk-QCJUGKRC.js} +4 -4
  18. package/dist/{chunk-VTM7NEJC.js → chunk-SIGKJOSF.js} +3 -3
  19. package/dist/{chunk-TPKPU4PE.js → chunk-T7WKIMTB.js} +8 -7
  20. package/dist/{chunk-46DGDPXR.js → chunk-V6GL37RZ.js} +98 -7
  21. package/dist/{chunk-UFNIB3JL.js → chunk-VKZTGD5Z.js} +11 -1
  22. package/dist/{chunk-ZFW35DLG.js → chunk-VYSBZUXZ.js} +1 -1
  23. package/dist/{chunk-7AUHC2X7.js → chunk-ZDE5S3DE.js} +1 -1
  24. package/dist/composition/graphics-plan.js +1 -1
  25. package/dist/composition/ops.js +3 -3
  26. package/dist/composition/validators.js +2 -2
  27. package/dist/composition-ops.js +5 -5
  28. package/dist/composition.js +2 -2
  29. package/dist/{context-UAS5WJRB.js → context-I7SPXR2Z.js} +4 -4
  30. package/dist/core/render.js +1 -1
  31. package/dist/core.js +1 -1
  32. package/dist/mcp/adapter.js +2 -2
  33. package/dist/mcp/core.js +1 -1
  34. package/dist/mcp/scene-shot.js +2 -2
  35. package/dist/mcp/server.js +9 -9
  36. package/dist/mcp.js +9 -9
  37. package/dist/primitives/captions.js +115 -46
  38. package/dist/primitives/lib/text/marker.js +1 -1
  39. package/dist/render-harness.js +1 -1
  40. package/package.json +11 -11
@@ -20,7 +20,7 @@ import {
20
20
  PlanRect,
21
21
  StyleRole,
22
22
  ZPlane
23
- } from "../chunk-UFNIB3JL.js";
23
+ } from "../chunk-VKZTGD5Z.js";
24
24
  import "../chunk-WPZKOVYF.js";
25
25
  import "../chunk-B7J5UUUV.js";
26
26
  import {
@@ -20,9 +20,9 @@ import {
20
20
  selectMediaClipAt,
21
21
  selectSceneMarkers,
22
22
  selectTracksByKind
23
- } from "../chunk-TPKPU4PE.js";
24
- import "../chunk-CJMUTCJW.js";
25
- import "../chunk-UFNIB3JL.js";
23
+ } from "../chunk-T7WKIMTB.js";
24
+ import "../chunk-QCGT3M7M.js";
25
+ import "../chunk-VKZTGD5Z.js";
26
26
  import "../chunk-WPZKOVYF.js";
27
27
  import "../chunk-B7J5UUUV.js";
28
28
  import "../chunk-KYRDKK2D.js";
@@ -48,7 +48,7 @@ import {
48
48
  WebhookDeliveryStatus,
49
49
  WebhookDestination,
50
50
  YoutubeDestination
51
- } from "../chunk-CJMUTCJW.js";
51
+ } from "../chunk-QCGT3M7M.js";
52
52
  import {
53
53
  ANIMATABLE_PARAMS,
54
54
  ANIMATABLE_VALUE,
@@ -93,7 +93,7 @@ import {
93
93
  captionVisibilityIssues,
94
94
  captionWords,
95
95
  captionsAreBehindSubject
96
- } from "../chunk-UFNIB3JL.js";
96
+ } from "../chunk-VKZTGD5Z.js";
97
97
  import {
98
98
  AUTHORED_GRAPHIC_KINDS
99
99
  } from "../chunk-WPZKOVYF.js";
@@ -20,16 +20,16 @@ import {
20
20
  replaceRegion,
21
21
  resolveClipKind,
22
22
  wrapWithOpRecorder
23
- } from "./chunk-VTM7NEJC.js";
24
- import "./chunk-LPUQTBKU.js";
23
+ } from "./chunk-SIGKJOSF.js";
24
+ import "./chunk-GZMKTLO3.js";
25
25
  import "./chunk-QH3YI3ND.js";
26
26
  import "./chunk-JPMVNMF3.js";
27
27
  import "./chunk-IMIUHG2E.js";
28
28
  import "./chunk-VZ7TYX6Z.js";
29
29
  import "./chunk-T56BK7NS.js";
30
- import "./chunk-TPKPU4PE.js";
31
- import "./chunk-CJMUTCJW.js";
32
- import "./chunk-UFNIB3JL.js";
30
+ import "./chunk-T7WKIMTB.js";
31
+ import "./chunk-QCGT3M7M.js";
32
+ import "./chunk-VKZTGD5Z.js";
33
33
  import "./chunk-WPZKOVYF.js";
34
34
  import "./chunk-B7J5UUUV.js";
35
35
  import "./chunk-KYRDKK2D.js";
@@ -73,7 +73,7 @@ import {
73
73
  WebhookDeliveryStatus,
74
74
  WebhookDestination,
75
75
  YoutubeDestination
76
- } from "./chunk-CJMUTCJW.js";
76
+ } from "./chunk-QCGT3M7M.js";
77
77
  import {
78
78
  ANIMATABLE_PARAMS,
79
79
  ANIMATABLE_VALUE,
@@ -118,7 +118,7 @@ import {
118
118
  captionVisibilityIssues,
119
119
  captionWords,
120
120
  captionsAreBehindSubject
121
- } from "./chunk-UFNIB3JL.js";
121
+ } from "./chunk-VKZTGD5Z.js";
122
122
  import {
123
123
  AUTHORED_GRAPHIC_KINDS
124
124
  } from "./chunk-WPZKOVYF.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  buildDirectorContext
3
- } from "./chunk-ZIPGDGBR.js";
4
- import "./chunk-TPKPU4PE.js";
5
- import "./chunk-CJMUTCJW.js";
6
- import "./chunk-UFNIB3JL.js";
3
+ } from "./chunk-FYMXVYWS.js";
4
+ import "./chunk-T7WKIMTB.js";
5
+ import "./chunk-QCGT3M7M.js";
6
+ import "./chunk-VKZTGD5Z.js";
7
7
  import "./chunk-WPZKOVYF.js";
8
8
  import "./chunk-B7J5UUUV.js";
9
9
  import "./chunk-KYRDKK2D.js";
@@ -69,7 +69,7 @@ import {
69
69
  splitIntoSentences,
70
70
  validateComposition,
71
71
  visiblePackedHostWindows
72
- } from "../chunk-LPUQTBKU.js";
72
+ } from "../chunk-GZMKTLO3.js";
73
73
  import {
74
74
  buildEasedZooms,
75
75
  buildPannedCenters,
package/dist/core.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  ensureEven,
11
11
  extractCaptionWordsFromDoc,
12
12
  isEmphasized
13
- } from "./chunk-LPUQTBKU.js";
13
+ } from "./chunk-GZMKTLO3.js";
14
14
  import "./chunk-QH3YI3ND.js";
15
15
  import "./chunk-JPMVNMF3.js";
16
16
  import "./chunk-IMIUHG2E.js";
@@ -2,7 +2,7 @@ import {
2
2
  querySchemaShape,
3
3
  serializeQuery,
4
4
  waitForJob
5
- } from "../chunk-ZFW35DLG.js";
5
+ } from "../chunk-VYSBZUXZ.js";
6
6
  import {
7
7
  CURATED_TOOLS
8
8
  } from "../chunk-4WBSXHQA.js";
@@ -16,7 +16,7 @@ import {
16
16
  opById,
17
17
  safeParse,
18
18
  sniffImageMediaType
19
- } from "../chunk-46DGDPXR.js";
19
+ } from "../chunk-V6GL37RZ.js";
20
20
  import "../chunk-2ESYSVXG.js";
21
21
 
22
22
  // ../mcp/src/adapter.ts
package/dist/mcp/core.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  text,
19
19
  textWithData,
20
20
  withMedia
21
- } from "../chunk-46DGDPXR.js";
21
+ } from "../chunk-V6GL37RZ.js";
22
22
  import "../chunk-2ESYSVXG.js";
23
23
  export {
24
24
  __resetPreviewCache,
@@ -5,8 +5,8 @@ import {
5
5
  SCENE_SHOT_RULES,
6
6
  sceneBrandTokens,
7
7
  sceneShotContract
8
- } from "../chunk-7AUHC2X7.js";
9
- import "../chunk-46DGDPXR.js";
8
+ } from "../chunk-ZDE5S3DE.js";
9
+ import "../chunk-V6GL37RZ.js";
10
10
  import "../chunk-2ESYSVXG.js";
11
11
  export {
12
12
  SCENE_SHOT_LIMITS,
@@ -8,21 +8,21 @@ import {
8
8
  registerRawTools,
9
9
  registerSkillPrompts,
10
10
  titleCase
11
- } from "../chunk-P3DYA5NA.js";
12
- import "../chunk-7AUHC2X7.js";
13
- import "../chunk-ZFW35DLG.js";
11
+ } from "../chunk-C3ARYFN6.js";
12
+ import "../chunk-ZDE5S3DE.js";
13
+ import "../chunk-VYSBZUXZ.js";
14
14
  import "../chunk-4WBSXHQA.js";
15
- import "../chunk-46DGDPXR.js";
16
- import "../chunk-VTM7NEJC.js";
17
- import "../chunk-LPUQTBKU.js";
15
+ import "../chunk-V6GL37RZ.js";
16
+ import "../chunk-SIGKJOSF.js";
17
+ import "../chunk-GZMKTLO3.js";
18
18
  import "../chunk-QH3YI3ND.js";
19
19
  import "../chunk-JPMVNMF3.js";
20
20
  import "../chunk-IMIUHG2E.js";
21
21
  import "../chunk-VZ7TYX6Z.js";
22
22
  import "../chunk-T56BK7NS.js";
23
- import "../chunk-TPKPU4PE.js";
24
- import "../chunk-CJMUTCJW.js";
25
- import "../chunk-UFNIB3JL.js";
23
+ import "../chunk-T7WKIMTB.js";
24
+ import "../chunk-QCGT3M7M.js";
25
+ import "../chunk-VKZTGD5Z.js";
26
26
  import "../chunk-WPZKOVYF.js";
27
27
  import "../chunk-B7J5UUUV.js";
28
28
  import "../chunk-KYRDKK2D.js";
package/dist/mcp.js CHANGED
@@ -8,21 +8,21 @@ import {
8
8
  buildServer,
9
9
  registerCuratedTools,
10
10
  registerRawTools
11
- } from "./chunk-P3DYA5NA.js";
12
- import "./chunk-7AUHC2X7.js";
13
- import "./chunk-ZFW35DLG.js";
11
+ } from "./chunk-C3ARYFN6.js";
12
+ import "./chunk-ZDE5S3DE.js";
13
+ import "./chunk-VYSBZUXZ.js";
14
14
  import "./chunk-4WBSXHQA.js";
15
- import "./chunk-46DGDPXR.js";
16
- import "./chunk-VTM7NEJC.js";
17
- import "./chunk-LPUQTBKU.js";
15
+ import "./chunk-V6GL37RZ.js";
16
+ import "./chunk-SIGKJOSF.js";
17
+ import "./chunk-GZMKTLO3.js";
18
18
  import "./chunk-QH3YI3ND.js";
19
19
  import "./chunk-JPMVNMF3.js";
20
20
  import "./chunk-IMIUHG2E.js";
21
21
  import "./chunk-VZ7TYX6Z.js";
22
22
  import "./chunk-T56BK7NS.js";
23
- import "./chunk-TPKPU4PE.js";
24
- import "./chunk-CJMUTCJW.js";
25
- import "./chunk-UFNIB3JL.js";
23
+ import "./chunk-T7WKIMTB.js";
24
+ import "./chunk-QCGT3M7M.js";
25
+ import "./chunk-VKZTGD5Z.js";
26
26
  import "./chunk-WPZKOVYF.js";
27
27
  import "./chunk-B7J5UUUV.js";
28
28
  import "./chunk-KYRDKK2D.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Marker
3
- } from "../chunk-D2EG7J7X.js";
3
+ } from "../chunk-DFB7UFZD.js";
4
4
  import {
5
5
  fillStyle
6
6
  } from "../chunk-XLIPPLG3.js";
@@ -154,15 +154,16 @@ function plateStyleFor(kind, backgroundColor) {
154
154
  }
155
155
  var COMBINE_TOKENS_MS = 1500;
156
156
  var CAPTION_LINGER_MS = 600;
157
- function italicAwareTextBox(maxWidth, maxLines) {
157
+ function measuredTextBox(maxWidth, maxLines, gapPx) {
158
158
  const lineWidths = [0];
159
159
  return {
160
160
  add: (word) => {
161
161
  const widthOf = (text) => measureText({ ...word, text }).width;
162
162
  const current = lineWidths.length - 1;
163
163
  const width = widthOf(lineWidths[current] === 0 ? word.text.trimStart() : word.text);
164
- if (Math.ceil(lineWidths[current] + width) < maxWidth) {
165
- lineWidths[current] += width;
164
+ const nextWidth = lineWidths[current] + (lineWidths[current] ? gapPx : 0) + width;
165
+ if (Math.ceil(nextWidth) < maxWidth) {
166
+ lineWidths[current] = nextWidth;
166
167
  return { exceedsBox: false, newLine: false };
167
168
  }
168
169
  const firstWidth = widthOf(word.text.trimStart());
@@ -174,10 +175,11 @@ function italicAwareTextBox(maxWidth, maxLines) {
174
175
  }
175
176
  };
176
177
  }
177
- function tokenBox(maxWidth, maxLines, emphasis) {
178
- return emphasis?.emphasisFontStyle === "italic" ? italicAwareTextBox(maxWidth, maxLines) : fillTextBox({ maxBoxWidth: maxWidth, maxLines });
178
+ function tokenBox(maxWidth, maxLines, fontSize, letterSpacing, wordGapEm, textTransform, emphasis) {
179
+ const needsMeasured = emphasis?.emphasisFontStyle === "italic" || letterSpacing !== 0 || (emphasis?.emphasisLetterSpacing ?? 0) !== 0 || wordGapEm !== void 0 || textTransform !== void 0 && textTransform !== "none" || emphasis?.emphasisTextTransform !== void 0 && emphasis.emphasisTextTransform !== "none";
180
+ return needsMeasured ? measuredTextBox(maxWidth, maxLines, fontSize * (wordGapEm ?? 0.35)) : fillTextBox({ maxBoxWidth: maxWidth, maxLines });
179
181
  }
180
- function addTokenToBox(box, token, fontFamily, fontSize, fontWeight, emphasis) {
182
+ function addTokenToBox(box, token, fontFamily, fontSize, fontWeight, letterSpacing, textTransform, emphasis) {
181
183
  const isEmp = emphasis?.emphasisSet.has(token.fromMs) ?? false;
182
184
  const tokenSize = isEmp ? Math.round(fontSize * (emphasis?.emphasisScale ?? 1.2)) : fontSize;
183
185
  const tokenWeight = emphasis && isEmp ? emphasis.emphasisFontWeight : fontWeight;
@@ -187,22 +189,24 @@ function addTokenToBox(box, token, fontFamily, fontSize, fontWeight, emphasis) {
187
189
  fontFamily: tokenFamily,
188
190
  fontSize: tokenSize,
189
191
  fontWeight: String(tokenWeight),
192
+ ...(isEmp ? emphasis?.emphasisLetterSpacing : letterSpacing) ? { letterSpacing: `${isEmp ? emphasis?.emphasisLetterSpacing : letterSpacing}px` } : {},
193
+ ...isEmp ? { textTransform: emphasis?.emphasisTextTransform } : { textTransform },
190
194
  ...isEmp && emphasis?.emphasisFontStyle === "italic" ? { additionalStyles: { fontStyle: "italic" } } : {}
191
195
  });
192
196
  }
193
- function makeBoxFactory(maxWidth, maxLines, fontFamily, fontSize, fontWeight, emphasis) {
197
+ function makeBoxFactory(maxWidth, maxLines, fontFamily, fontSize, fontWeight, letterSpacing, wordGapEm, textTransform, emphasis) {
194
198
  return () => {
195
- const box = tokenBox(maxWidth, maxLines, emphasis);
199
+ const box = tokenBox(maxWidth, maxLines, fontSize, letterSpacing, wordGapEm, textTransform, emphasis);
196
200
  return {
197
- add: (token) => !addTokenToBox(box, token, fontFamily, fontSize, fontWeight, emphasis).exceedsBox
201
+ add: (token) => !addTokenToBox(box, token, fontFamily, fontSize, fontWeight, letterSpacing, textTransform, emphasis).exceedsBox
198
202
  };
199
203
  };
200
204
  }
201
- function layoutTokensIntoLines(tokens, maxWidth, maxLines, fontFamily, fontSize, fontWeight, emphasis) {
202
- const box = tokenBox(maxWidth, maxLines, emphasis);
205
+ function layoutTokensIntoLines(tokens, maxWidth, maxLines, fontFamily, fontSize, fontWeight, letterSpacing, wordGapEm, textTransform, emphasis) {
206
+ const box = tokenBox(maxWidth, maxLines, fontSize, letterSpacing, wordGapEm, textTransform, emphasis);
203
207
  const lines = [[]];
204
208
  for (const token of tokens) {
205
- const result = addTokenToBox(box, token, fontFamily, fontSize, fontWeight, emphasis);
209
+ const result = addTokenToBox(box, token, fontFamily, fontSize, fontWeight, letterSpacing, textTransform, emphasis);
206
210
  if (result.newLine) {
207
211
  lines.push([token]);
208
212
  } else {
@@ -211,8 +215,10 @@ function layoutTokensIntoLines(tokens, maxWidth, maxLines, fontFamily, fontSize,
211
215
  }
212
216
  return lines.filter((line) => line.length > 0);
213
217
  }
214
- function layoutFitError(label) {
215
- return new Error(`Caption layout${label ? ` ${label}` : " word range"} does not fit one page in its region; enlarge the region or split the range`);
218
+ function layoutFitError(label, detail) {
219
+ const error = new Error(`Caption layout${label ? ` ${label}` : " word range"} does not fit one page in its region${detail ? ` (${detail})` : ""}; enlarge the region or split the range`);
220
+ error.name = "CaptionLayoutFitError";
221
+ return error;
216
222
  }
217
223
  function tokenRuns(tokens, emphasisSet, emphasisPlate) {
218
224
  const runs = [];
@@ -224,34 +230,38 @@ function tokenRuns(tokens, emphasisSet, emphasisPlate) {
224
230
  }
225
231
  return runs;
226
232
  }
233
+ function measureVisualRun(run, fontSize, style, faces) {
234
+ const plate = style.emphasisPlate ?? false;
235
+ const emph = run.emph;
236
+ const text = emph && plate ? run.tokens.map((token) => token.text.trim()).join(" ") : plate ? run.tokens[0].text.trim() : run.tokens[0].text;
237
+ const size = emph ? Math.round(fontSize * (style.emphasisScale ?? 1.08)) : fontSize;
238
+ const transform = emph ? style.emphasisTextTransform ?? style.textTransform : style.textTransform;
239
+ const letterSpacing = emph ? style.emphasisLetterSpacing ?? style.letterSpacing : style.letterSpacing;
240
+ return measureText({
241
+ text,
242
+ fontFamily: emph ? faces.emphasis.stack : faces.base.stack,
243
+ fontSize: size,
244
+ fontWeight: String(emph ? faces.emphasis.weight : faces.base.weight),
245
+ ...transform ? { textTransform: transform } : {},
246
+ ...letterSpacing !== void 0 ? { letterSpacing: `${letterSpacing}px` } : {},
247
+ ...emph && faces.emphasis.style === "italic" ? { additionalStyles: { fontStyle: "italic" } } : {}
248
+ }).width + (emph && plate ? 2 * Math.round(size * (style.emphasisPlatePadX ?? 0.16)) : 0);
249
+ }
227
250
  function layoutAuthoredTokensIntoLines(tokens, maxWidth, maxLines, fontSize, style, emphasisSet, faces, label) {
228
251
  const lines = [[]];
229
252
  let lineWidth = 0;
230
253
  const plate = style.emphasisPlate ?? false;
231
- const runGap = fontSize * 0.35;
254
+ const runGap = fontSize * (style.wordGapEm ?? 0.35);
232
255
  for (const run of tokenRuns(tokens, emphasisSet, plate)) {
233
- const emph = run.emph;
234
- const text = emph && plate ? run.tokens.map((token) => token.text.trim()).join(" ") : plate ? run.tokens[0].text.trim() : run.tokens[0].text;
235
- const size = emph ? Math.round(fontSize * (style.emphasisScale ?? 1.08)) : fontSize;
236
- const transform = emph ? style.emphasisTextTransform ?? style.textTransform : style.textTransform;
237
- const letterSpacing = emph ? style.emphasisLetterSpacing : void 0;
238
- const width = measureText({
239
- text,
240
- fontFamily: emph ? faces.emphasis.stack : faces.base.stack,
241
- fontSize: size,
242
- fontWeight: String(emph ? faces.emphasis.weight : faces.base.weight),
243
- ...transform ? { textTransform: transform } : {},
244
- ...letterSpacing ? { letterSpacing: `${letterSpacing}px` } : {},
245
- ...emph && faces.emphasis.style === "italic" ? { additionalStyles: { fontStyle: "italic" } } : {}
246
- }).width + (emph && plate ? 2 * Math.round(size * (style.emphasisPlatePadX ?? 0.16)) : 0);
256
+ const width = measureVisualRun(run, fontSize, style, faces);
247
257
  if (Math.ceil(width) > maxWidth) {
248
- throw layoutFitError(label);
258
+ throw layoutFitError(label, `run needs ${Math.ceil(width)}px, box has ${Math.floor(maxWidth)}px`);
249
259
  }
250
260
  const current = lines[lines.length - 1];
251
261
  const nextWidth = lineWidth + (current.length ? runGap : 0) + width;
252
262
  if (Math.ceil(nextWidth) > maxWidth) {
253
263
  if (lines.length >= maxLines) {
254
- throw layoutFitError(label);
264
+ throw layoutFitError(label, `needs ${lines.length + 1} lines, box allows ${maxLines}`);
255
265
  }
256
266
  lines.push([...run.tokens]);
257
267
  lineWidth = width;
@@ -287,7 +297,8 @@ function Captions({
287
297
  const emphasisFontFamily = faces.emphasis.stack;
288
298
  const emphasisFontWeight = faces.emphasis.weight;
289
299
  const emphasisTextTransform = style.emphasisTextTransform ?? style.textTransform;
290
- const emphasisLetterSpacing = style.emphasisLetterSpacing ?? 0;
300
+ const letterSpacing = style.letterSpacing ?? 0;
301
+ const emphasisLetterSpacing = style.emphasisLetterSpacing ?? letterSpacing;
291
302
  const emphasisFontStyle = faces.emphasis.style;
292
303
  const entrance = style.entrance ?? "none";
293
304
  const entranceFrames = Math.max(1, Math.round((style.entranceMs ?? 160) / 1e3 * fps));
@@ -301,18 +312,25 @@ function Captions({
301
312
  const plateRadius = style.emphasisPlateRadius ?? 6;
302
313
  const platePadX = style.emphasisPlatePadX ?? 0.16;
303
314
  const platePadY = style.emphasisPlatePadY ?? 0.04;
315
+ const plateReveal = style.emphasisPlateReveal ?? false;
304
316
  const annotationColor = style.annotationColor ?? VIOLET;
317
+ const annotationStrokeWidth = style.annotationStrokeWidth ?? 4;
305
318
  const maxWidthPercent = style.maxWidthPercent ?? 80;
306
319
  const plateStyle = plateStyleFor(style.plate ?? "glass", style.backgroundColor);
307
320
  const fontSize = Math.round(height * (style.fontSize / 100));
321
+ const wordGapPx = fontSize * (style.wordGapEm ?? 0.35);
322
+ const lineGapPx = fontSize * (style.lineGapEm ?? 0.25);
308
323
  const authoredRegion = userStyle?.region;
309
324
  const regionWidth = authoredRegion ? width * authoredRegion.w : width;
310
325
  const maxTextWidth = authoredRegion ? Math.min(width * (maxWidthPercent / 100), regionWidth - fontSize * 1.6) : width * (maxWidthPercent / 100);
311
326
  const availableHeight = authoredRegion ? height * authoredRegion.h - fontSize * 0.8 : Infinity;
312
327
  const lineHeight = fontSize * Math.max(1, emphasisScale) * 1.2;
313
- const maxLines = authoredRegion ? Math.min(3, Math.floor((availableHeight + fontSize * 0.25) / (lineHeight + fontSize * 0.25))) : 3;
328
+ const maxLines = authoredRegion ? Math.min(3, Math.floor((availableHeight + lineGapPx) / (lineHeight + lineGapPx))) : 3;
314
329
  if (authoredRegion && (maxTextWidth <= 0 || maxLines < 1)) {
315
- if (singlePageRequired) throw layoutFitError(layoutLabel);
330
+ if (singlePageRequired) throw layoutFitError(
331
+ layoutLabel,
332
+ maxTextWidth <= 0 ? `box has ${Math.floor(maxTextWidth)}px text width` : "box allows no complete line"
333
+ );
316
334
  throw new Error("Caption style.region is too small for one caption line at the authored font size");
317
335
  }
318
336
  const { emphasisStartMs, annotateKind } = useMemo(() => {
@@ -325,12 +343,38 @@ function Captions({
325
343
  return { emphasisStartMs: emph, annotateKind: ann };
326
344
  }, [segments]);
327
345
  const emphasisLayout = useMemo(
328
- () => emphasisStartMs.size > 0 ? { emphasisSet: emphasisStartMs, emphasisScale, emphasisFontFamily, emphasisFontWeight, emphasisFontStyle } : void 0,
329
- [emphasisStartMs, emphasisScale, emphasisFontFamily, emphasisFontWeight, emphasisFontStyle]
346
+ () => emphasisStartMs.size > 0 ? {
347
+ emphasisSet: emphasisStartMs,
348
+ emphasisScale,
349
+ emphasisFontFamily,
350
+ emphasisFontWeight,
351
+ emphasisFontStyle,
352
+ emphasisLetterSpacing,
353
+ emphasisTextTransform
354
+ } : void 0,
355
+ [
356
+ emphasisStartMs,
357
+ emphasisScale,
358
+ emphasisFontFamily,
359
+ emphasisFontWeight,
360
+ emphasisFontStyle,
361
+ emphasisLetterSpacing,
362
+ emphasisTextTransform
363
+ ]
330
364
  );
331
365
  const pages = useMemo(
332
366
  () => {
333
- const makeBox = makeBoxFactory(maxTextWidth, maxLines, style.fontFamily, fontSize, style.fontWeight, emphasisLayout);
367
+ const makeBox = makeBoxFactory(
368
+ maxTextWidth,
369
+ maxLines,
370
+ style.fontFamily,
371
+ fontSize,
372
+ style.fontWeight,
373
+ letterSpacing,
374
+ style.wordGapEm,
375
+ style.textTransform,
376
+ emphasisLayout
377
+ );
334
378
  const paginated = paginateCaptionSegments(segments, {
335
379
  combineMs: singlePageRequired ? Number.MAX_SAFE_INTEGER : COMBINE_TOKENS_MS,
336
380
  makeBox: singlePageRequired ? () => ({ add: () => true }) : makeBox,
@@ -357,7 +401,20 @@ function Captions({
357
401
  }
358
402
  return paginated;
359
403
  },
360
- [segments, maxTextWidth, maxLines, fontSize, style, emphasisStartMs, faces, emphasisLayout, holdUntilMs, singlePageRequired, layoutLabel]
404
+ [
405
+ segments,
406
+ maxTextWidth,
407
+ maxLines,
408
+ fontSize,
409
+ style,
410
+ emphasisStartMs,
411
+ faces,
412
+ emphasisLayout,
413
+ letterSpacing,
414
+ holdUntilMs,
415
+ singlePageRequired,
416
+ layoutLabel
417
+ ]
361
418
  );
362
419
  if (pages.length === 0) return null;
363
420
  const currentTimeMs = frame / fps * 1e3;
@@ -398,6 +455,9 @@ function Captions({
398
455
  style.fontFamily,
399
456
  fontSize,
400
457
  style.fontWeight,
458
+ letterSpacing,
459
+ style.wordGapEm,
460
+ style.textTransform,
401
461
  emphasisLayout
402
462
  );
403
463
  const wrapperStyle = resolveCaptionPlacement(userStyle?.position, region, authoredRegion);
@@ -413,7 +473,7 @@ function Captions({
413
473
  display: "flex",
414
474
  flexDirection: "column",
415
475
  alignItems: authoredRegion ? "flex-start" : "center",
416
- gap: `${fontSize * 0.25}px`,
476
+ gap: `${lineGapPx}px`,
417
477
  padding: `${fontSize * 0.4}px ${fontSize * 0.8}px`,
418
478
  borderRadius: fontSize * 0.5,
419
479
  ...plateStyle,
@@ -451,7 +511,7 @@ function Captions({
451
511
  };
452
512
  return lines.map((line, lineIdx) => {
453
513
  const segs = tokenRuns(line, emphasisStartMs, emphasisPlate);
454
- return /* @__PURE__ */ React.createElement("div", { key: lineIdx, style: { display: "flex", justifyContent: authoredRegion ? "flex-start" : "center", alignItems: "baseline", gap: `${fontSize * 0.35}px` } }, segs.map((seg, si) => {
514
+ return /* @__PURE__ */ React.createElement("div", { key: lineIdx, style: { display: "flex", justifyContent: authoredRegion ? "flex-start" : "center", alignItems: "baseline", gap: `${wordGapPx}px` } }, segs.map((seg, si) => {
455
515
  const first = seg.tokens[0];
456
516
  const entered = entranceOf(first, seg.emph);
457
517
  const opacity = syncWordVisibility && currentTimeMs < first.fromMs ? 0 : entered.opacity;
@@ -469,7 +529,16 @@ function Captions({
469
529
  const bg = idx % 2 === 0 ? plateColor : plateAltColor;
470
530
  const fg = idx % 2 === 0 ? plateTextColor : plateAltTextColor;
471
531
  const efs = Math.round(fontSize * emphasisScale);
472
- return /* @__PURE__ */ React.createElement("span", { key: si, style: { position: "relative", display: "inline-block", opacity, transform: entranceTransform, transformOrigin: "center" } }, /* @__PURE__ */ React.createElement(
532
+ const plateTokens = plateReveal ? seg.tokens.filter((token2) => currentTimeMs >= token2.fromMs) : seg.tokens;
533
+ const reservedWidth = plateReveal ? measureVisualRun(seg, fontSize, style, faces) : void 0;
534
+ return /* @__PURE__ */ React.createElement("span", { key: si, style: {
535
+ position: "relative",
536
+ display: "inline-block",
537
+ ...reservedWidth !== void 0 ? { width: reservedWidth } : {},
538
+ opacity,
539
+ transform: entranceTransform,
540
+ transformOrigin: "center"
541
+ } }, /* @__PURE__ */ React.createElement(
473
542
  "span",
474
543
  {
475
544
  style: {
@@ -488,10 +557,10 @@ function Captions({
488
557
  whiteSpace: "nowrap"
489
558
  }
490
559
  },
491
- seg.tokens.map((token2, tokenIndex) => /* @__PURE__ */ React.createElement("span", { key: token2.fromMs, style: {
560
+ plateTokens.map((token2, tokenIndex) => /* @__PURE__ */ React.createElement("span", { key: token2.fromMs, style: {
492
561
  opacity: syncWordVisibility && tokenIndex > 0 ? currentTimeMs < token2.fromMs ? 0 : entranceOf(token2, true).opacity : 1
493
562
  } }, tokenIndex > 0 ? " " : "", token2.text.trim()))
494
- ), annKind && annotatedToken && currentTimeMs >= annotatedToken.fromMs ? /* @__PURE__ */ React.createElement(Marker, { spec: { kind: annKind, color: annotationColor }, progress: markerP }) : null);
563
+ ), annKind && annotatedToken && currentTimeMs >= annotatedToken.fromMs ? /* @__PURE__ */ React.createElement(Marker, { spec: { kind: annKind, color: annotationColor, strokeWidth: annotationStrokeWidth }, progress: markerP }) : null);
495
564
  }
496
565
  const token = first;
497
566
  const isActive = token === activeToken;
@@ -500,7 +569,7 @@ function Captions({
500
569
  const tokenWeight = isEmphasis ? emphasisFontWeight : style.fontWeight;
501
570
  const tokenFamily = isEmphasis ? emphasisFontFamily : style.fontFamily;
502
571
  const tokenTransform = isEmphasis ? emphasisTextTransform : style.textTransform;
503
- const tokenLetterSpacing = isEmphasis && emphasisLetterSpacing ? `${emphasisLetterSpacing}px` : void 0;
572
+ const tokenLetterSpacing = isEmphasis ? emphasisLetterSpacing ? `${emphasisLetterSpacing}px` : void 0 : letterSpacing ? `${letterSpacing}px` : void 0;
504
573
  const tokenFontStyle = isEmphasis ? emphasisFontStyle : "normal";
505
574
  let tokenColor = style.color;
506
575
  if (isActive && !emphasisPlate) tokenColor = style.highlightColor;
@@ -523,7 +592,7 @@ function Captions({
523
592
  },
524
593
  emphasisPlate ? token.text.trim() : token.text
525
594
  );
526
- return /* @__PURE__ */ React.createElement("span", { key: si, style: { position: "relative", display: "inline-block", opacity, transform: entranceTransform, transformOrigin: "center" } }, inner, annKind && annotatedToken && currentTimeMs >= annotatedToken.fromMs ? /* @__PURE__ */ React.createElement(Marker, { spec: { kind: annKind, color: annotationColor }, progress: markerP }) : null);
595
+ return /* @__PURE__ */ React.createElement("span", { key: si, style: { position: "relative", display: "inline-block", opacity, transform: entranceTransform, transformOrigin: "center" } }, inner, annKind && annotatedToken && currentTimeMs >= annotatedToken.fromMs ? /* @__PURE__ */ React.createElement(Marker, { spec: { kind: annKind, color: annotationColor, strokeWidth: annotationStrokeWidth }, progress: markerP }) : null);
527
596
  }));
528
597
  });
529
598
  })()
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Marker
3
- } from "../../../chunk-D2EG7J7X.js";
3
+ } from "../../../chunk-DFB7UFZD.js";
4
4
  import "../../../chunk-2ESYSVXG.js";
5
5
  export {
6
6
  Marker
@@ -46,7 +46,7 @@ import {
46
46
  parseSceneComponentCaptureIntentV1,
47
47
  prepareSceneShotRenderInput,
48
48
  readSceneShotManifest
49
- } from "./chunk-LPUQTBKU.js";
49
+ } from "./chunk-GZMKTLO3.js";
50
50
  import "./chunk-QH3YI3ND.js";
51
51
  import "./chunk-JPMVNMF3.js";
52
52
  import "./chunk-IMIUHG2E.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueframe/kernel",
3
- "version": "0.2.53",
3
+ "version": "0.2.54",
4
4
  "type": "module",
5
5
  "description": "Publish-only aggregate of CueFrame's isomorphic kernel packages — composition, primitives, authoring, MCP, and friends shipped as one npm artifact. Members remain separate workspace packages; this only changes what ships.",
6
6
  "files": [
@@ -16,26 +16,26 @@
16
16
  "devDependencies": {
17
17
  "tsup": "^8.3.5",
18
18
  "typescript": "^5.9.3",
19
- "@cueframe/brand-derivation": "0.1.0",
20
19
  "@cueframe/animate": "0.1.0",
21
- "@cueframe/cloud-import": "0.1.0",
22
- "@cueframe/account-model": "0.1.0",
23
20
  "@cueframe/authoring": "0.1.0",
21
+ "@cueframe/account-model": "0.1.0",
22
+ "@cueframe/cloud-import": "0.1.0",
24
23
  "@cueframe/compose": "0.1.0",
25
24
  "@cueframe/composition": "0.1.0",
25
+ "@cueframe/composition-ops": "0.1.0",
26
26
  "@cueframe/component-runtime": "0.1.0",
27
- "@cueframe/director": "0.1.0",
27
+ "@cueframe/brand-derivation": "0.1.0",
28
28
  "@cueframe/core": "0.1.0",
29
- "@cueframe/mcp": "0.1.0",
30
- "@cueframe/composition-ops": "0.1.0",
31
- "@cueframe/foundation": "0.1.0",
29
+ "@cueframe/director": "0.1.0",
32
30
  "@cueframe/errors": "0.1.0",
31
+ "@cueframe/foundation": "0.1.0",
32
+ "@cueframe/nle-export": "0.1.0",
33
33
  "@cueframe/media-model": "0.1.0",
34
34
  "@cueframe/perception": "0.1.0",
35
- "@cueframe/nle-export": "0.1.0",
36
- "@cueframe/plane": "0.1.0",
35
+ "@cueframe/mcp": "0.1.0",
36
+ "@cueframe/primitives": "0.1.0",
37
37
  "@cueframe/render-harness": "0.1.0",
38
- "@cueframe/primitives": "0.1.0"
38
+ "@cueframe/plane": "0.1.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "gsap": "3.13.0",