@jokerized/decksmith 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli.js +679 -132
- package/dist/ds-morph.js +1 -0
- package/dist/index.js +661 -130
- package/dist/mcp.js +374 -39
- package/dist/types/emit/archetypes/equation-morph.d.ts +2 -0
- package/dist/types/emit/archetypes/equation-walk.d.ts +63 -1
- package/dist/types/emit/archetypes/index.d.ts +1 -1
- package/dist/types/emit/archetypes/stack.d.ts +9 -0
- package/dist/types/emit/depth.d.ts +95 -0
- package/dist/types/emit/kit.d.ts +7 -0
- package/dist/types/emit/morph-runtime.d.ts +178 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/mcp/tools.d.ts +12 -0
- package/dist/types/plan/arc.d.ts +82 -0
- package/dist/types/plan/codex.d.ts +6 -0
- package/dist/types/prefs.d.ts +1 -0
- package/dist/types/render/ffmpeg.d.ts +0 -14
- package/dist/types/types.d.ts +412 -0
- package/dist/types/verify/apparent.d.ts +123 -0
- package/dist/types/verify/index.d.ts +64 -0
- package/dist/types/verify/typefloor.d.ts +3 -1
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -127,6 +127,13 @@ var equationWalkParamsSchema = z.object({
|
|
|
127
127
|
/** Walked in order, one hold-point each. */
|
|
128
128
|
terms: z.array(termSchema).min(1).max(4)
|
|
129
129
|
});
|
|
130
|
+
var equationMorphParamsSchema = z.object({
|
|
131
|
+
eyebrow: z.string().optional(),
|
|
132
|
+
headline: z.string(),
|
|
133
|
+
fromId: z.string(),
|
|
134
|
+
toId: z.string(),
|
|
135
|
+
terms: z.array(termSchema).min(1).max(4)
|
|
136
|
+
});
|
|
130
137
|
var dataTableParamsSchema = z.object({
|
|
131
138
|
eyebrow: z.string().optional(),
|
|
132
139
|
headline: z.string(),
|
|
@@ -255,7 +262,22 @@ var stackParamsSchema = z.object({
|
|
|
255
262
|
headline: z.string(),
|
|
256
263
|
/** Drawn bottom-up as offset planes, revealed in order. */
|
|
257
264
|
layers: z.array(z.object({ label: z.string(), note: z.string().optional() })).min(2).max(7),
|
|
258
|
-
note: z.string().optional()
|
|
265
|
+
note: z.string().optional(),
|
|
266
|
+
/**
|
|
267
|
+
* Tilt the slab stack away from the viewer, so the planes read as stacked in
|
|
268
|
+
* depth rather than merely offset up the page.
|
|
269
|
+
*
|
|
270
|
+
* OPTIONAL, and absent means flat — every storyboard written before this
|
|
271
|
+
* existed still parses, and every deck that does not ask for it emits exactly
|
|
272
|
+
* the bytes it did before.
|
|
273
|
+
*
|
|
274
|
+
* Degrees, and bounded at 18 rather than by taste: the tilt is paid for in
|
|
275
|
+
* declared type, because perspective shrinks the far half of the plane and
|
|
276
|
+
* invariant 5 is about what the audience SEES. At 18 degrees a 40px floor
|
|
277
|
+
* already needs 53.8px declared (`src/emit/depth.ts`), and past that a
|
|
278
|
+
* headline cannot spend enough and still fit its own line.
|
|
279
|
+
*/
|
|
280
|
+
tilt: z.number().min(0).max(18).optional()
|
|
259
281
|
});
|
|
260
282
|
var splitSideSchema = z.object({
|
|
261
283
|
label: z.string(),
|
|
@@ -288,10 +310,16 @@ var insideSchema = z.object({
|
|
|
288
310
|
*/
|
|
289
311
|
label: z.string().optional()
|
|
290
312
|
});
|
|
313
|
+
var beatRoleSchema = z.enum(["intro", "background", "limitations", "conclusion"]);
|
|
291
314
|
var beatCore = {
|
|
292
315
|
id: z.string(),
|
|
293
316
|
/** What the viewer should understand after this beat. */
|
|
294
317
|
intent: z.string(),
|
|
318
|
+
/**
|
|
319
|
+
* OPTIONAL, and only ever present when `prefs.genre` is `paper`. The
|
|
320
|
+
* structural job this beat does; see `beatRoleSchema`.
|
|
321
|
+
*/
|
|
322
|
+
role: beatRoleSchema.optional(),
|
|
295
323
|
/** Optional: this beat happens inside a named part of the beat before it. */
|
|
296
324
|
inside: insideSchema.optional(),
|
|
297
325
|
/** The source sentence or equation this beat is accountable to. */
|
|
@@ -320,6 +348,12 @@ var beatSchema = z.discriminatedUnion("archetype", [
|
|
|
320
348
|
params: equationWalkParamsSchema,
|
|
321
349
|
...beatTail
|
|
322
350
|
}),
|
|
351
|
+
z.object({
|
|
352
|
+
...beatCore,
|
|
353
|
+
archetype: z.literal("equation-morph"),
|
|
354
|
+
params: equationMorphParamsSchema,
|
|
355
|
+
...beatTail
|
|
356
|
+
}),
|
|
323
357
|
z.object({
|
|
324
358
|
...beatCore,
|
|
325
359
|
archetype: z.literal("data-table"),
|
|
@@ -377,7 +411,8 @@ var ARCHETYPE_FAMILY = {
|
|
|
377
411
|
"bar-compare": "quantity",
|
|
378
412
|
"line-chart": "quantity",
|
|
379
413
|
"data-table": "quantity",
|
|
380
|
-
"equation-walk": "formal"
|
|
414
|
+
"equation-walk": "formal",
|
|
415
|
+
"equation-morph": "formal"
|
|
381
416
|
};
|
|
382
417
|
var storyboardSchema = z.object({
|
|
383
418
|
sourceId: z.string(),
|
|
@@ -419,6 +454,26 @@ var prefsSchema = z.object({
|
|
|
419
454
|
tone: z.enum(["plain", "academic", "conversational", "punchy"]).default("plain"),
|
|
420
455
|
/** How much text a slide may carry before it should have been a diagram. */
|
|
421
456
|
density: z.enum(["sparse", "normal", "dense"]).default("normal"),
|
|
457
|
+
/**
|
|
458
|
+
* What kind of document is being explained, DECLARED and never sniffed.
|
|
459
|
+
*
|
|
460
|
+
* `paper` asks the planner for the shape a research talk has: open on the
|
|
461
|
+
* problem and the ground the work stands on, close on what it does not do and
|
|
462
|
+
* then what to take away. `general` is every deck built before this existed
|
|
463
|
+
* and changes nothing — no prompt block, no `role` in the planner's schema, no
|
|
464
|
+
* scan.
|
|
465
|
+
*
|
|
466
|
+
* WHY DECLARED. A ten-role heading lexicon (en/ko/ja/zh, numbered-prefix
|
|
467
|
+
* tolerant) run over all 351 markdown files in this repository scored 345 of
|
|
468
|
+
* them at zero role hits and none at three or more. `src/source/markdown.ts`
|
|
469
|
+
* says why in its first line: the input is a hypepaper-style ANALYSIS of a
|
|
470
|
+
* paper, a rewrite that has already discarded the headings a detector would
|
|
471
|
+
* key on. A classifier here would be a guess with a confidence score attached,
|
|
472
|
+
* and it would guess wrong on the Korean fixture. So the author says so once —
|
|
473
|
+
* `--genre paper`, or one line in a `decksmith.config.json` above a directory
|
|
474
|
+
* of papers — and every run under it costs no further typing.
|
|
475
|
+
*/
|
|
476
|
+
genre: z.enum(["general", "paper"]).default("general"),
|
|
422
477
|
/**
|
|
423
478
|
* How long the finished thing should run, in seconds. Optional: absent means
|
|
424
479
|
* "as long as it takes", which is what every deck built before this did.
|
|
@@ -676,7 +731,10 @@ function selectBeats(storyboard, budget2, seconds = {}) {
|
|
|
676
731
|
const protectedIds = protect(live, len, cap);
|
|
677
732
|
const keep = knapsack(live, len, cap, protectedIds);
|
|
678
733
|
if (!keep) {
|
|
679
|
-
const
|
|
734
|
+
const ends = new Set(
|
|
735
|
+
[live[0]?.id, live[live.length - 1]?.id].filter((id2) => !!id2)
|
|
736
|
+
);
|
|
737
|
+
const all = knapsack(live, len, cap, ends) ?? knapsack(live, len, cap, /* @__PURE__ */ new Set());
|
|
680
738
|
const chosen = all ?? [live[0]];
|
|
681
739
|
return budgetDrops(live, chosen, dropped, storyboard, len, cap, budget2, false);
|
|
682
740
|
}
|
|
@@ -708,8 +766,17 @@ function protect(live, len, cap) {
|
|
|
708
766
|
}
|
|
709
767
|
const picture = cheapest && !ids.has(cheapest.id) ? [cheapest] : [];
|
|
710
768
|
for (const b of picture) ids.add(b.id);
|
|
769
|
+
const byId = new Map(live.map((b) => [b.id, b]));
|
|
770
|
+
const roled = [];
|
|
771
|
+
for (const b of live) {
|
|
772
|
+
if (!b.role) continue;
|
|
773
|
+
for (let hop = b; hop && !ids.has(hop.id); hop = byId.get(hop.inside?.beat ?? "")) {
|
|
774
|
+
ids.add(hop.id);
|
|
775
|
+
roled.push(hop);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
711
778
|
const ends = /* @__PURE__ */ new Set([first?.id, last?.id]);
|
|
712
|
-
const releasable = [...tier, ...coverage, ...picture].filter((b) => !ends.has(b.id)).sort((a, b) => rate(a) - rate(b) || len(b) - len(a));
|
|
779
|
+
const releasable = [...tier, ...coverage, ...picture, ...roled].filter((b) => !ends.has(b.id)).sort((a, b) => rate(a) - rate(b) || len(b) - len(a));
|
|
713
780
|
const cost = () => live.filter((b) => ids.has(b.id)).reduce((s, b) => s + len(b), 0);
|
|
714
781
|
for (const b of releasable) {
|
|
715
782
|
if (cost() <= cap) break;
|
|
@@ -3082,7 +3149,7 @@ function locate(tex, term) {
|
|
|
3082
3149
|
end = Math.max(end, hay.map[lastNorm + 1] ?? lastOrig + 1);
|
|
3083
3150
|
return { start, end: Math.min(end, tex.length) };
|
|
3084
3151
|
}
|
|
3085
|
-
function wrapTerms(tex, terms, beatId) {
|
|
3152
|
+
function wrapTerms(tex, terms, beatId, cls = (t2) => `term t-${t2.tone}`) {
|
|
3086
3153
|
let parts = [{ text: tex, raw: true }];
|
|
3087
3154
|
const used = [];
|
|
3088
3155
|
const missing = [];
|
|
@@ -3098,7 +3165,7 @@ function wrapTerms(tex, terms, beatId) {
|
|
|
3098
3165
|
1,
|
|
3099
3166
|
{ text: part.text.slice(0, at.start), raw: true },
|
|
3100
3167
|
{
|
|
3101
|
-
text: `\\htmlClass{
|
|
3168
|
+
text: `\\htmlClass{${cls(term)}}{${part.text.slice(at.start, at.end)}}`,
|
|
3102
3169
|
raw: false
|
|
3103
3170
|
},
|
|
3104
3171
|
{ text: part.text.slice(at.end), raw: true }
|
|
@@ -3131,6 +3198,24 @@ function statements(tex, stacked) {
|
|
|
3131
3198
|
const parts = tex.split(/\\qquad|\\quad|\\\\/).map((s) => s.trim()).filter(Boolean);
|
|
3132
3199
|
return parts.length > 0 ? parts : [tex];
|
|
3133
3200
|
}
|
|
3201
|
+
function legendRows(sid, terms, theme) {
|
|
3202
|
+
return terms.map(
|
|
3203
|
+
(t2) => `<div class="leg" id="${sid}-leg-${t2.tone}"><span class="chip" id="${sid}-chip-${t2.tone}" style="color:${theme.tones[t2.tone]}"></span><span>${esc(t2.label)}</span></div>`
|
|
3204
|
+
).join("\n ");
|
|
3205
|
+
}
|
|
3206
|
+
function legendCss(theme) {
|
|
3207
|
+
return [
|
|
3208
|
+
// `width:fit-content` + auto margins, not `align-items:center`: centring
|
|
3209
|
+
// each row individually gave the legend a ragged left edge, because a short
|
|
3210
|
+
// label indented its own chip further than a long one did. The column is
|
|
3211
|
+
// centred as one block and the rows start on a shared spine.
|
|
3212
|
+
".legend{display:flex;flex-direction:column;gap:30px;width:fit-content;margin-inline:auto}",
|
|
3213
|
+
`.leg{display:flex;gap:26px;align-items:baseline;max-width:1400px;font-size:48px;color:${theme.muted}}`,
|
|
3214
|
+
// A common chip width, so the labels share a spine too — the glyphs inside
|
|
3215
|
+
// are one symbol each and their natural widths differ by a few pixels.
|
|
3216
|
+
`.chip{display:inline-block;min-width:72px;text-align:center;background:${theme.panel};border-radius:10px;padding:2px 20px;white-space:nowrap;font-weight:700}`
|
|
3217
|
+
].join("\n");
|
|
3218
|
+
}
|
|
3134
3219
|
var equationWalk = (beat, ctx) => {
|
|
3135
3220
|
const { sid, theme } = ctx;
|
|
3136
3221
|
const p = beat.params;
|
|
@@ -3142,9 +3227,7 @@ var equationWalk = (beat, ctx) => {
|
|
|
3142
3227
|
}
|
|
3143
3228
|
const walk = wrapTerms(eq.tex, p.terms, beat.id);
|
|
3144
3229
|
const terms = walk.used;
|
|
3145
|
-
const legend = terms
|
|
3146
|
-
(t2) => `<div class="leg" id="${sid}-leg-${t2.tone}"><span class="chip" id="${sid}-chip-${t2.tone}" style="color:${theme.tones[t2.tone]}"></span><span>${esc(t2.label)}</span></div>`
|
|
3147
|
-
).join("\n ");
|
|
3230
|
+
const legend = legendRows(sid, terms, theme);
|
|
3148
3231
|
const stacked = isPortrait(ctx.format);
|
|
3149
3232
|
const raw2 = statements(eq.tex, stacked);
|
|
3150
3233
|
const shown = statements(walk.tex, stacked);
|
|
@@ -3243,15 +3326,7 @@ var equationWalk = (beat, ctx) => {
|
|
|
3243
3326
|
".eqstack{display:flex;flex-direction:column;gap:32px}",
|
|
3244
3327
|
// Transforms do not apply to inline boxes, and KaTeX spans are inline.
|
|
3245
3328
|
".term{display:inline-block}",
|
|
3246
|
-
|
|
3247
|
-
// each row individually gave the legend a ragged left edge, because a short
|
|
3248
|
-
// label indented its own chip further than a long one did. The column is
|
|
3249
|
-
// centred as one block and the rows start on a shared spine.
|
|
3250
|
-
".legend{display:flex;flex-direction:column;gap:30px;width:fit-content;margin-inline:auto}",
|
|
3251
|
-
`.leg{display:flex;gap:26px;align-items:baseline;max-width:1400px;font-size:48px;color:${theme.muted}}`,
|
|
3252
|
-
// A common chip width, so the labels share a spine too — the glyphs inside
|
|
3253
|
-
// are one symbol each and their natural widths differ by a few pixels.
|
|
3254
|
-
`.chip{display:inline-block;min-width:72px;text-align:center;background:${theme.panel};border-radius:10px;padding:2px 20px;white-space:nowrap;font-weight:700}`,
|
|
3329
|
+
legendCss(theme),
|
|
3255
3330
|
// The block, not the term under discussion: which term that is, is a fact
|
|
3256
3331
|
// about the paused timeline, and CSS cannot see it. The terms are also the
|
|
3257
3332
|
// one thing here GSAP tints and swells, so a rule on them would win the
|
|
@@ -3261,6 +3336,117 @@ var equationWalk = (beat, ctx) => {
|
|
|
3261
3336
|
};
|
|
3262
3337
|
};
|
|
3263
3338
|
|
|
3339
|
+
// src/emit/archetypes/equation-morph.ts
|
|
3340
|
+
var MORPH_SECONDS = 1.6;
|
|
3341
|
+
var equationMorph = (beat, ctx) => {
|
|
3342
|
+
const { sid, theme } = ctx;
|
|
3343
|
+
const p = beat.params;
|
|
3344
|
+
const find2 = (id2) => {
|
|
3345
|
+
const eq = ctx.source.equations.find((e) => e.id === id2);
|
|
3346
|
+
if (!eq)
|
|
3347
|
+
throw new Error(`equation-morph ${beat.id}: no equation "${id2}" in source ${ctx.source.id}`);
|
|
3348
|
+
return eq;
|
|
3349
|
+
};
|
|
3350
|
+
const a = find2(p.fromId);
|
|
3351
|
+
const b = find2(p.toId);
|
|
3352
|
+
const both = p.terms.filter((t2) => locate(a.tex, t2.tex) && locate(b.tex, t2.tex));
|
|
3353
|
+
if (both.length === 0) {
|
|
3354
|
+
throw new Error(
|
|
3355
|
+
`equation-morph ${beat.id}: none of its ${p.terms.length} term(s) occur in both equations. Terms: ${p.terms.map((t2) => JSON.stringify(t2.tex)).join(", ")}. From: ${JSON.stringify(a.tex)}. To: ${JSON.stringify(b.tex)}`
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
const cls = (t2) => `term t-${t2.tone} ds-k-${t2.tone}`;
|
|
3359
|
+
const wa = wrapTerms(a.tex, both, beat.id, cls).tex;
|
|
3360
|
+
const wb = wrapTerms(b.tex, both, beat.id, cls).tex;
|
|
3361
|
+
const size = Math.max(
|
|
3362
|
+
MIN_FONT,
|
|
3363
|
+
Math.min(
|
|
3364
|
+
equationSize(a.tex.length > b.tex.length ? a.tex : b.tex),
|
|
3365
|
+
Math.floor(contentW(ctx.format) / Math.max(texUnits(a.tex), texUnits(b.tex)))
|
|
3366
|
+
)
|
|
3367
|
+
);
|
|
3368
|
+
const html = `${chrome(sid, p.eyebrow, p.headline, contentW(ctx.format))}
|
|
3369
|
+
<div class="eqslide">
|
|
3370
|
+
<div class="morph" id="${sid}-morph" style="font-size:${size}px">
|
|
3371
|
+
<div class="side" data-morph="a" id="${sid}-eqa"></div>
|
|
3372
|
+
<div class="side" data-morph="b" id="${sid}-eqb"></div>
|
|
3373
|
+
</div>
|
|
3374
|
+
<div class="legend">
|
|
3375
|
+
${legendRows(sid, both, theme)}
|
|
3376
|
+
</div>
|
|
3377
|
+
</div>`;
|
|
3378
|
+
const setup = [
|
|
3379
|
+
`var OPTS = ${OPTS};`,
|
|
3380
|
+
`katex.render('${js(wa)}', document.getElementById("${sid}-eqa"), OPTS);`,
|
|
3381
|
+
`katex.render('${js(wb)}', document.getElementById("${sid}-eqb"), OPTS);`,
|
|
3382
|
+
...both.map(
|
|
3383
|
+
(t2) => `katex.render('${js(t2.tex)}', document.getElementById("${sid}-chip-${t2.tone}"), ${INLINE_OPTS});`
|
|
3384
|
+
)
|
|
3385
|
+
];
|
|
3386
|
+
const first = 1.8;
|
|
3387
|
+
const at = Math.round(
|
|
3388
|
+
Math.max(2.6, Math.min(beat.seconds - MORPH_SECONDS - 0.9, beat.seconds * 0.45)) * 100
|
|
3389
|
+
) / 100;
|
|
3390
|
+
const tl = [
|
|
3391
|
+
...chromeIn(sid, p.eyebrow !== void 0),
|
|
3392
|
+
tween(`#${sid}-morph`, { opacity: 0, y: 22 }, { opacity: 1, y: 0, duration: 0.7 }, 0.8),
|
|
3393
|
+
...both.map(
|
|
3394
|
+
(t2, i) => tween(
|
|
3395
|
+
`#${sid}-leg-${t2.tone}`,
|
|
3396
|
+
{ opacity: 0, x: -18 },
|
|
3397
|
+
{ opacity: 1, x: 0, duration: 0.5 },
|
|
3398
|
+
1 + i * 0.15
|
|
3399
|
+
)
|
|
3400
|
+
),
|
|
3401
|
+
// ONE tween, on the host, driving the plugin. Its ease is "none" because the
|
|
3402
|
+
// plan carries its own eases per segment; `pace` scales this duration and
|
|
3403
|
+
// the plan, being in fractions of it, scales with it.
|
|
3404
|
+
tween(
|
|
3405
|
+
`#${sid}-morph`,
|
|
3406
|
+
{ dsMorph: 0 },
|
|
3407
|
+
{ dsMorph: 1, duration: MORPH_SECONDS, ease: "none" },
|
|
3408
|
+
at
|
|
3409
|
+
)
|
|
3410
|
+
];
|
|
3411
|
+
return {
|
|
3412
|
+
html,
|
|
3413
|
+
tl,
|
|
3414
|
+
setup,
|
|
3415
|
+
// SEAM B: the plan is browser geometry after fonts, so it is built inside
|
|
3416
|
+
// the ready gate, and the plugin tween above finds it on the host.
|
|
3417
|
+
measure: [`DSMorph.build(document.getElementById("${sid}-morph"));`],
|
|
3418
|
+
plugins: ["dsMorph"],
|
|
3419
|
+
holds: holdsWithin([first, at + MORPH_SECONDS + 0.4], beat.seconds),
|
|
3420
|
+
css: [
|
|
3421
|
+
chromeCss(theme),
|
|
3422
|
+
".eqslide{display:flex;flex-direction:column;justify-content:space-evenly;gap:64px;flex:1;min-height:0}",
|
|
3423
|
+
".katex-display{margin:0 !important}",
|
|
3424
|
+
// Both sides in one grid cell, so the host is as tall as the taller line
|
|
3425
|
+
// and neither needs a guessed height; the overlay is absolute over it.
|
|
3426
|
+
// Padded by the room an arc needs, so a bowing glyph stays inside its
|
|
3427
|
+
// offset parent and the layout gate's `escaped_container` stays quiet; the
|
|
3428
|
+
// bow is capped to the same 0.8em in `plan`.
|
|
3429
|
+
`.morph{position:relative;display:grid;place-items:center;text-align:center;padding:0.8em 0.5em;color:${theme.fg}}`,
|
|
3430
|
+
".side{grid-area:1/1}",
|
|
3431
|
+
// B is measured, never seen: the runtime lifts its glyphs into the overlay
|
|
3432
|
+
// and drives them from there. Hidden by the sheet so nothing is captured
|
|
3433
|
+
// before the gate has built the plan.
|
|
3434
|
+
'.side[data-morph="b"]{visibility:hidden}',
|
|
3435
|
+
".ds-morph-layer{position:absolute;inset:0}",
|
|
3436
|
+
".term{display:inline-block}",
|
|
3437
|
+
// Keys are tinted from the start, on BOTH lines — the colour is what lets
|
|
3438
|
+
// a viewer follow a body across the move. Scoped under `.morph` because
|
|
3439
|
+
// `equation-walk` tweens `.t-<tone>` from the foreground colour, and a
|
|
3440
|
+
// bare rule on the class would win that cascade and cancel its walk.
|
|
3441
|
+
...["a", "b", "c", "d"].map(
|
|
3442
|
+
(tone2) => `.morph .t-${tone2}{color:${theme.tones[tone2]}}`
|
|
3443
|
+
),
|
|
3444
|
+
legendCss(theme),
|
|
3445
|
+
ambient(sid, "-morph", BREATHE)
|
|
3446
|
+
].join("\n")
|
|
3447
|
+
};
|
|
3448
|
+
};
|
|
3449
|
+
|
|
3264
3450
|
// src/emit/archetypes/grid.ts
|
|
3265
3451
|
var LABEL = 42;
|
|
3266
3452
|
var LH = 1.25;
|
|
@@ -4544,9 +4730,36 @@ var splitCompare = (beat, ctx) => {
|
|
|
4544
4730
|
};
|
|
4545
4731
|
};
|
|
4546
4732
|
|
|
4733
|
+
// src/emit/depth.ts
|
|
4734
|
+
var DEFAULT_POSE = { rotateX: 12, perspective: 1400 };
|
|
4735
|
+
function scaleAt(pose, dy) {
|
|
4736
|
+
const t2 = pose.rotateX * Math.PI / 180;
|
|
4737
|
+
const denom = pose.perspective - dy * Math.sin(t2);
|
|
4738
|
+
if (denom <= 0) return 0;
|
|
4739
|
+
return Math.cos(t2) * (pose.perspective / denom) ** 2;
|
|
4740
|
+
}
|
|
4741
|
+
var MODEL_SLACK = 0.98;
|
|
4742
|
+
function worstScale(pose, height) {
|
|
4743
|
+
if (scaleAt(pose, height / 2) <= 0) return 0;
|
|
4744
|
+
return scaleAt(pose, -height / 2) * MODEL_SLACK;
|
|
4745
|
+
}
|
|
4746
|
+
function tiltedFloor(pose, height, floor) {
|
|
4747
|
+
const s = worstScale(pose, height);
|
|
4748
|
+
return s > 0 ? floor / s : Number.POSITIVE_INFINITY;
|
|
4749
|
+
}
|
|
4750
|
+
function depthCss(sid, pose, part) {
|
|
4751
|
+
return [
|
|
4752
|
+
`#${sid} { perspective: ${pose.perspective}px; }`,
|
|
4753
|
+
`#${sid} ${part} { transform: rotateX(${pose.rotateX}deg); transform-origin: 50% 50%; }`
|
|
4754
|
+
].join("\n");
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4547
4757
|
// src/emit/archetypes/stack.ts
|
|
4548
4758
|
var GAP3 = 44;
|
|
4549
4759
|
var NUM_X = 48;
|
|
4760
|
+
function numSpine(floor) {
|
|
4761
|
+
return Math.round(NUM_X * floor * 100 / MIN_FONT) / 100;
|
|
4762
|
+
}
|
|
4550
4763
|
var PROBE_X = 16;
|
|
4551
4764
|
var PROBE_H = 46;
|
|
4552
4765
|
var NUM_W = 70;
|
|
@@ -4567,15 +4780,20 @@ function stackLayout(p, format, face = "latin") {
|
|
|
4567
4780
|
function labelWeight(i, count) {
|
|
4568
4781
|
return i === count - 1 ? 700 : 600;
|
|
4569
4782
|
}
|
|
4783
|
+
function floorFor(p, format) {
|
|
4784
|
+
if (!p.tilt) return MIN_FONT;
|
|
4785
|
+
return tiltedFloor({ ...DEFAULT_POSE, rotateX: p.tilt }, contentH(format), MIN_FONT);
|
|
4786
|
+
}
|
|
4570
4787
|
function solve2(p, format, inline, face) {
|
|
4571
4788
|
const width = contentW(format);
|
|
4572
4789
|
const boxH = contentH(format);
|
|
4790
|
+
const floor = floorFor(p, format);
|
|
4573
4791
|
const count = p.layers.length;
|
|
4574
4792
|
const k = isPortrait(format) ? "tall" : "wide";
|
|
4575
4793
|
const riseMax = RISE_MAX[k];
|
|
4576
4794
|
const syMax = SY_MAX[k];
|
|
4577
4795
|
const tMax = T_MAX[k];
|
|
4578
|
-
const noteW = (l) => inline && l.note !== void 0 ? textWidth(l.note,
|
|
4796
|
+
const noteW = (l) => inline && l.note !== void 0 ? textWidth(l.note, floor, 400, 0, false, face) + 28 : 0;
|
|
4579
4797
|
const want = Math.max(
|
|
4580
4798
|
...p.layers.map(
|
|
4581
4799
|
(l, i) => textWidth(l.label, LABEL_SIZE2, labelWeight(i, count), 0, false, face) + noteW(l)
|
|
@@ -4590,7 +4808,7 @@ function solve2(p, format, inline, face) {
|
|
|
4590
4808
|
(l, i) => (colW - noteW(l)) / Math.max(1, textWidth(l.label, 1, labelWeight(i, count), 0, false, face))
|
|
4591
4809
|
)
|
|
4592
4810
|
);
|
|
4593
|
-
const labelSize = Math.max(
|
|
4811
|
+
const labelSize = Math.max(floor, Math.min(LABEL_SIZE2, labelRoom));
|
|
4594
4812
|
const lines = p.layers.map((l, i) => {
|
|
4595
4813
|
const nw = noteW(l);
|
|
4596
4814
|
const labelMaxW = Math.max(labelSize, colW - nw);
|
|
@@ -4598,14 +4816,14 @@ function solve2(p, format, inline, face) {
|
|
|
4598
4816
|
label: wrap(l.label, labelSize, labelMaxW, labelWeight(i, count), 0, face),
|
|
4599
4817
|
// Inline notes stay on one line by contract — the schema calls a note "one
|
|
4600
4818
|
// short line" — and wrapping one would put its second line under the label.
|
|
4601
|
-
note: l.note === void 0 ? [] : inline ? [l.note] : wrap(l.note,
|
|
4819
|
+
note: l.note === void 0 ? [] : inline ? [l.note] : wrap(l.note, floor, colW, 400, 0, face),
|
|
4602
4820
|
noteW: nw,
|
|
4603
4821
|
labelMaxW
|
|
4604
4822
|
};
|
|
4605
4823
|
});
|
|
4606
4824
|
const blockH = Math.max(
|
|
4607
4825
|
...lines.map(
|
|
4608
|
-
(l) => inline ? Math.max(l.label.length * labelSize, l.note.length *
|
|
4826
|
+
(l) => inline ? Math.max(l.label.length * labelSize, l.note.length * floor) * 1.16 : l.label.length * labelSize * 1.16 + (l.note.length > 0 ? 6 + l.note.length * floor * 1.16 : 0)
|
|
4609
4827
|
)
|
|
4610
4828
|
);
|
|
4611
4829
|
const pad = Math.max(EDGE2, blockH / 2);
|
|
@@ -4625,6 +4843,7 @@ function solve2(p, format, inline, face) {
|
|
|
4625
4843
|
// BOTH directions. A layout that fits the height and not the width is not a
|
|
4626
4844
|
// layout that fits; it is one whose overflow is in the axis nothing measured.
|
|
4627
4845
|
fits: room >= blockH + 10 && height <= free && wide,
|
|
4846
|
+
floor,
|
|
4628
4847
|
wide,
|
|
4629
4848
|
inline,
|
|
4630
4849
|
width,
|
|
@@ -4664,7 +4883,7 @@ var stack = (beat, ctx) => {
|
|
|
4664
4883
|
const last = count - 1;
|
|
4665
4884
|
if (!L.fits) {
|
|
4666
4885
|
throw new Error(
|
|
4667
|
-
`stack ${beat.id}: ${count} layers with ${p.layers.filter((l) => l.note).length} note(s) need ${Math.round(L.blockH)}px of label block against ${Math.round(L.avail)}px of room, at the ${
|
|
4886
|
+
`stack ${beat.id}: ${count} layers with ${p.layers.filter((l) => l.note).length} note(s) need ${Math.round(L.blockH)}px of label block against ${Math.round(L.avail)}px of room, at the ${Math.round(L.floor)}px floor and with the notes already moved beside their labels. Nothing here may be set smaller, so the lever is upstream of this beat: drop a layer, or shorten the notes.`
|
|
4668
4887
|
);
|
|
4669
4888
|
}
|
|
4670
4889
|
const parts = {};
|
|
@@ -4686,7 +4905,7 @@ var stack = (beat, ctx) => {
|
|
|
4686
4905
|
const dot = circle({ x: L.x0 + L.w + L.sx / 2 + 8, y: mid }, 6, { fill: tint });
|
|
4687
4906
|
const block = L.lines[i] ?? { label: [], note: [], noteW: 0, labelMaxW: L.colW };
|
|
4688
4907
|
const labelH = block.label.length * L.labelSize * 1.16;
|
|
4689
|
-
const noteH = block.note.length > 0 ? 6 + block.note.length *
|
|
4908
|
+
const noteH = block.note.length > 0 ? 6 + block.note.length * L.floor * 1.16 : 0;
|
|
4690
4909
|
const label = text(
|
|
4691
4910
|
layer.label,
|
|
4692
4911
|
{ x: L.labelX, y: L.inline ? mid : mid - noteH / 2 },
|
|
@@ -4712,7 +4931,7 @@ var stack = (beat, ctx) => {
|
|
|
4712
4931
|
{ x: L.labelX, y: mid + labelH / 2 + 3 }
|
|
4713
4932
|
),
|
|
4714
4933
|
{
|
|
4715
|
-
size:
|
|
4934
|
+
size: L.floor,
|
|
4716
4935
|
fill: theme.muted,
|
|
4717
4936
|
anchor: L.inline ? "end" : "start",
|
|
4718
4937
|
maxWidth: L.inline ? void 0 : L.colW,
|
|
@@ -4723,8 +4942,8 @@ var stack = (beat, ctx) => {
|
|
|
4723
4942
|
);
|
|
4724
4943
|
const num2 = text(
|
|
4725
4944
|
String(i + 1),
|
|
4726
|
-
{ x:
|
|
4727
|
-
{ size:
|
|
4945
|
+
{ x: numSpine(L.floor), y: mid },
|
|
4946
|
+
{ size: L.floor, weight: 600, fill: theme.dim, anchor: "end", vAlign: "middle" }
|
|
4728
4947
|
);
|
|
4729
4948
|
return group(slab(L.x0, y0, L, tint, lift2, stroke), { id: id(sid, "lay", i), class: "lay" }) + group(num2 + leader + dot + label + note, { id: id(sid, "cap", i), class: "cap" });
|
|
4730
4949
|
}).join("");
|
|
@@ -4746,6 +4965,8 @@ var stack = (beat, ctx) => {
|
|
|
4746
4965
|
<div class="stnote" id="${sid}-note">${esc(p.note)}</div>` : "";
|
|
4747
4966
|
const html = `${chrome(sid, p.eyebrow, p.headline, contentW(ctx.format))}
|
|
4748
4967
|
<div class="stackwrap">${svg(id(sid, "stack"), L.width, L.height, body + probe3)}</div>${noteHtml}`;
|
|
4968
|
+
const centre2 = (count - 1) / 2;
|
|
4969
|
+
const enterFrom = (i) => p.tilt ? (i - centre2) * L.rise : 34;
|
|
4749
4970
|
const first = 0.9;
|
|
4750
4971
|
const step = Math.min(0.8, Math.max(0.4, (beat.seconds - first - 1.5) / count));
|
|
4751
4972
|
const tl = [...chromeIn(sid, p.eyebrow !== void 0)];
|
|
@@ -4756,7 +4977,7 @@ var stack = (beat, ctx) => {
|
|
|
4756
4977
|
tl.push(
|
|
4757
4978
|
tween(
|
|
4758
4979
|
`#${sid}-lay${i}`,
|
|
4759
|
-
{ opacity: 0, y:
|
|
4980
|
+
{ opacity: 0, y: enterFrom(i) },
|
|
4760
4981
|
{ opacity: 1, y: 0, duration: 0.55, ease: "power2.out" },
|
|
4761
4982
|
at
|
|
4762
4983
|
)
|
|
@@ -4799,8 +5020,12 @@ var stack = (beat, ctx) => {
|
|
|
4799
5020
|
// The top plane is the focal point — last built, differently toned, and the
|
|
4800
5021
|
// one the final hold sits on. Its entrance owns `opacity` and `transform`,
|
|
4801
5022
|
// so the breath takes `filter`, the property nothing else writes.
|
|
4802
|
-
ambient(sid, `-lay${last}`, BREATHE)
|
|
4803
|
-
|
|
5023
|
+
ambient(sid, `-lay${last}`, BREATHE),
|
|
5024
|
+
// Absent unless the beat asked for it, so a flat stack emits the bytes it
|
|
5025
|
+
// always did.
|
|
5026
|
+
// `.stackwrap` and not the scene: the slabs lean, the headline does not.
|
|
5027
|
+
p.tilt ? depthCss(sid, { ...DEFAULT_POSE, rotateX: p.tilt }, ".stackwrap") : ""
|
|
5028
|
+
].filter(Boolean).join("\n")
|
|
4804
5029
|
};
|
|
4805
5030
|
};
|
|
4806
5031
|
|
|
@@ -4815,6 +5040,7 @@ var emitters = {
|
|
|
4815
5040
|
stack,
|
|
4816
5041
|
"split-compare": splitCompare,
|
|
4817
5042
|
"equation-walk": equationWalk,
|
|
5043
|
+
"equation-morph": equationMorph,
|
|
4818
5044
|
"line-chart": lineChart,
|
|
4819
5045
|
// The ones that describe.
|
|
4820
5046
|
title,
|
|
@@ -5007,6 +5233,7 @@ function round4(n3) {
|
|
|
5007
5233
|
// src/emit/composition.ts
|
|
5008
5234
|
var GSAP_SRC = "./vendor/gsap.min.js";
|
|
5009
5235
|
var DRAWSVG_SRC = "./vendor/DrawSVGPlugin.min.js";
|
|
5236
|
+
var MORPH_SRC = "./vendor/ds-morph.js";
|
|
5010
5237
|
var KATEX_JS = "./vendor/katex.min.js";
|
|
5011
5238
|
var KATEX_CSS = "./katex/katex.min.css";
|
|
5012
5239
|
function emitDeck(storyboard, source, format, runtimeJs, opts = {}) {
|
|
@@ -5097,6 +5324,7 @@ function layout(storyboard, source, format, opts = {}) {
|
|
|
5097
5324
|
});
|
|
5098
5325
|
let start = 0;
|
|
5099
5326
|
let builds = false;
|
|
5327
|
+
const plugins = /* @__PURE__ */ new Set();
|
|
5100
5328
|
cuts.forEach((cut2, i) => {
|
|
5101
5329
|
const { beat, sid, dive, inside, duration } = cut2;
|
|
5102
5330
|
if (cut2.segments?.length) spoken[sid] = cut2.segments;
|
|
@@ -5111,6 +5339,7 @@ function layout(storyboard, source, format, opts = {}) {
|
|
|
5111
5339
|
}
|
|
5112
5340
|
if (scene.css) archetypeCss.add(scene.css.trim());
|
|
5113
5341
|
if (scene.measure?.length) builds = true;
|
|
5342
|
+
for (const p of scene.plugins ?? []) plugins.add(p);
|
|
5114
5343
|
scenes.push(
|
|
5115
5344
|
sceneHtml(
|
|
5116
5345
|
sid,
|
|
@@ -5142,7 +5371,8 @@ function layout(storyboard, source, format, opts = {}) {
|
|
|
5142
5371
|
spoken,
|
|
5143
5372
|
total: start,
|
|
5144
5373
|
cut,
|
|
5145
|
-
builds
|
|
5374
|
+
builds,
|
|
5375
|
+
plugins
|
|
5146
5376
|
};
|
|
5147
5377
|
}
|
|
5148
5378
|
function enteredParts(beats) {
|
|
@@ -5223,6 +5453,9 @@ function renderComposition(storyboard, format, laid) {
|
|
|
5223
5453
|
<link rel="stylesheet" href="${FONT_BUNDLE_HREF}" />` : "";
|
|
5224
5454
|
const island = format.navigable ? `
|
|
5225
5455
|
${emitIsland(slides)}` : "";
|
|
5456
|
+
const morph = laid.plugins.has("dsMorph") ? `
|
|
5457
|
+
<script src="${MORPH_SRC}"></script>
|
|
5458
|
+
<script>gsap.registerPlugin(DSMorphPlugin);</script>` : "";
|
|
5226
5459
|
return `<!doctype html>
|
|
5227
5460
|
<html lang="${esc(storyboard.lang)}" data-resolution="${orientation}">
|
|
5228
5461
|
<head>
|
|
@@ -5231,7 +5464,7 @@ ${emitIsland(slides)}` : "";
|
|
|
5231
5464
|
<meta name="viewport" content="width=${format.width}, height=${format.height}" />
|
|
5232
5465
|
<script src="${GSAP_SRC}"></script>
|
|
5233
5466
|
<script src="${DRAWSVG_SRC}"></script>
|
|
5234
|
-
<script>gsap.registerPlugin(DrawSVGPlugin);</script
|
|
5467
|
+
<script>gsap.registerPlugin(DrawSVGPlugin);</script>${morph}
|
|
5235
5468
|
<link rel="stylesheet" href="${KATEX_CSS}" />
|
|
5236
5469
|
<script src="${KATEX_JS}"></script>${fontLink}${fontFace}
|
|
5237
5470
|
<style>
|
|
@@ -5942,6 +6175,17 @@ import { tmpdir } from "node:os";
|
|
|
5942
6175
|
import { join as join3 } from "node:path";
|
|
5943
6176
|
import { z as z2 } from "zod";
|
|
5944
6177
|
|
|
6178
|
+
// src/plan/arc.ts
|
|
6179
|
+
var ARC_ROLES = ["intro", "background", "limitations", "conclusion"];
|
|
6180
|
+
function requiredRoles(beatCount) {
|
|
6181
|
+
if (beatCount >= 8) return ARC_ROLES;
|
|
6182
|
+
if (beatCount >= 5) return ["limitations", "conclusion"];
|
|
6183
|
+
return [];
|
|
6184
|
+
}
|
|
6185
|
+
function paperArcRequested(prefs) {
|
|
6186
|
+
return prefs.genre === "paper";
|
|
6187
|
+
}
|
|
6188
|
+
|
|
5945
6189
|
// src/plan/duration.ts
|
|
5946
6190
|
var SPEECH_CPS = { latin: 14.4, cjk: 6.5 };
|
|
5947
6191
|
var LAST_HOLD_SECONDS = 4.2;
|
|
@@ -6138,6 +6382,7 @@ var REVEALS = {
|
|
|
6138
6382
|
title: "1",
|
|
6139
6383
|
"claim-figure": "2",
|
|
6140
6384
|
"equation-walk": "one per term",
|
|
6385
|
+
"equation-morph": "2",
|
|
6141
6386
|
"data-table": "one per highlighted row, plus 1",
|
|
6142
6387
|
"line-chart": "1",
|
|
6143
6388
|
callout: "one per panel",
|
|
@@ -6169,9 +6414,9 @@ A beat is one idea, one visual, one hold. It carries:
|
|
|
6169
6414
|
beat immediately before it. See RULE 11. Leave it off unless the
|
|
6170
6415
|
source itself puts one inside the other.
|
|
6171
6416
|
|
|
6172
|
-
THE
|
|
6417
|
+
THE THIRTEEN ARCHETYPES
|
|
6173
6418
|
|
|
6174
|
-
|
|
6419
|
+
Nine of them DRAW: they build a vector graphic out of the source's own content
|
|
6175
6420
|
and reveal it stage by stage, so the viewer watches the idea assemble. Four only
|
|
6176
6421
|
describe. The drawing ones are the default. The describing ones are what you
|
|
6177
6422
|
fall back to when a point genuinely has no shape.
|
|
@@ -6249,6 +6494,14 @@ DRAWING ARCHETYPES \u2014 reach here first
|
|
|
6249
6494
|
An equation quoted to back a claim someone else is making is
|
|
6250
6495
|
evidence under another archetype, not a beat of its own.
|
|
6251
6496
|
|
|
6497
|
+
equation-morph One equation becoming the next, the shared terms carried
|
|
6498
|
+
across. The tell: THE SOURCE DERIVES ONE LINE FROM ANOTHER \u2014 a
|
|
6499
|
+
substitution, a rearrangement, a special case \u2014 and the point
|
|
6500
|
+
is what moved. \`fromId\` and \`toId\` name two equations from
|
|
6501
|
+
the inventory. Each terms[].tex must appear verbatim in BOTH,
|
|
6502
|
+
and travels as one piece; a term in only one of them is
|
|
6503
|
+
dropped. Four terms maximum.
|
|
6504
|
+
|
|
6252
6505
|
line-chart A trend the source states numerically but does not plot. The
|
|
6253
6506
|
tell: A QUANTITY MOVING ALONG AN ORDERED AXIS \u2014 over length, over
|
|
6254
6507
|
scale, over training. Points come from the source's numbers;
|
|
@@ -6522,6 +6775,46 @@ ${REVEAL_COUNTS}` : ` - ${n3 === 1 ? "ONE SENTENCE" : `${n3} SENTENCES`} FOR TH
|
|
|
6522
6775
|
miss its duration.`;
|
|
6523
6776
|
return { sentences, length };
|
|
6524
6777
|
}
|
|
6778
|
+
function paperArc(slides) {
|
|
6779
|
+
const asked = requiredRoles(slides);
|
|
6780
|
+
const full = asked.includes("intro");
|
|
6781
|
+
return `
|
|
6782
|
+
|
|
6783
|
+
PAPER ARC \u2014 this source was declared a research paper.
|
|
6784
|
+
|
|
6785
|
+
Four beats have a structural job, and each one NAMES its job in \`role\`. Every
|
|
6786
|
+
other beat leaves \`role\` off. A role is a job, not a heading: never write
|
|
6787
|
+
"Related work" or "Conclusion" as a headline, because RULE 8 still applies to
|
|
6788
|
+
all four.
|
|
6789
|
+
${full ? `
|
|
6790
|
+
role: "intro" Near the front. What problem exists and who has it, in
|
|
6791
|
+
the viewer's own terms. This is the opening RULE 6
|
|
6792
|
+
already asks for, named so the deck can be checked.
|
|
6793
|
+
role: "background" In the first three beats. What people did before this
|
|
6794
|
+
work, and where that ran out. Take it from what the
|
|
6795
|
+
source itself says about earlier approaches \u2014 if the
|
|
6796
|
+
source says nothing about them, leave the role off
|
|
6797
|
+
rather than inventing a literature (RULE 3).` : `
|
|
6798
|
+
This deck is short, so only the ENDING is required \u2014 an opening the deck
|
|
6799
|
+
already has is not worth a slide of its own here.`}
|
|
6800
|
+
role: "limitations" THE SECOND-TO-LAST beat. What the work does not do, in
|
|
6801
|
+
the source's own admission. Not a hedge inside another
|
|
6802
|
+
beat's sentence: its own slide.
|
|
6803
|
+
role: "conclusion" THE LAST beat, with nothing after it. What the viewer
|
|
6804
|
+
should carry away.
|
|
6805
|
+
|
|
6806
|
+
- The closing pair is TWO beats and they must not share an archetype (RULE 1).
|
|
6807
|
+
A limitation the source admits to is usually a panel; the conclusion is the
|
|
6808
|
+
claim the deck lands, so draw it where the source states it \u2014 bars, a
|
|
6809
|
+
contrast, the figure that settles it \u2014 and fall back to a panel only when it
|
|
6810
|
+
genuinely has no shape.
|
|
6811
|
+
- Give all four a weight of 0.8 or above. A short cut keeps the
|
|
6812
|
+
highest-weighted beats, and a structural beat below 0.8 is one the deck
|
|
6813
|
+
loses at the first budget.
|
|
6814
|
+
- If the source does not support one of these, LEAVE IT OUT. A slide that
|
|
6815
|
+
admits a limitation the paper never admits is worse than no slide.
|
|
6816
|
+
`;
|
|
6817
|
+
}
|
|
6525
6818
|
function illustrations(images) {
|
|
6526
6819
|
return `
|
|
6527
6820
|
|
|
@@ -6550,7 +6843,7 @@ nothing, so it can never dangle.
|
|
|
6550
6843
|
}
|
|
6551
6844
|
function systemPrompt(prefs) {
|
|
6552
6845
|
const plan = durationPlan(prefs);
|
|
6553
|
-
return `${rules(cadenceFor(prefs, plan))}${prefs.images.enabled ? illustrations(prefs.images) : ""}
|
|
6846
|
+
return `${rules(cadenceFor(prefs, plan))}${paperArcRequested(prefs) ? paperArc(prefs.slides) : ""}${prefs.images.enabled ? illustrations(prefs.images) : ""}
|
|
6554
6847
|
|
|
6555
6848
|
PREFERENCES \u2014 chosen by the person who asked for this deck.
|
|
6556
6849
|
${prefs.duration === void 0 ? "" : `
|
|
@@ -6647,7 +6940,8 @@ function renderSource(source) {
|
|
|
6647
6940
|
out.push("", "== EQUATIONS ==");
|
|
6648
6941
|
for (const e of source.equations)
|
|
6649
6942
|
out.push(`[equation ${e.id}] ${e.display ? "display" : "inline"} \u2014 ${e.tex}`);
|
|
6650
|
-
if (!source.equations.length)
|
|
6943
|
+
if (!source.equations.length)
|
|
6944
|
+
out.push("(none \u2014 no equation-walk or equation-morph beat is possible)");
|
|
6651
6945
|
out.push("", "== TABLES ==");
|
|
6652
6946
|
for (const t2 of source.tables) {
|
|
6653
6947
|
out.push(`[table ${t2.id}] ${t2.caption ?? "(no caption)"}`);
|
|
@@ -6719,6 +7013,10 @@ function assertRefsResolve(storyboard, source, opts = {}) {
|
|
|
6719
7013
|
case "equation-walk":
|
|
6720
7014
|
check3(beat, "equation", beat.params.equationId, "params.equationId");
|
|
6721
7015
|
break;
|
|
7016
|
+
case "equation-morph":
|
|
7017
|
+
check3(beat, "equation", beat.params.fromId, "params.fromId");
|
|
7018
|
+
check3(beat, "equation", beat.params.toId, "params.toId");
|
|
7019
|
+
break;
|
|
6722
7020
|
case "data-table": {
|
|
6723
7021
|
check3(beat, "table", beat.params.tableId, "params.tableId");
|
|
6724
7022
|
const table = source.tables.find((t2) => t2.id === beat.params.tableId);
|
|
@@ -6813,14 +7111,45 @@ function stripNulls(node) {
|
|
|
6813
7111
|
}
|
|
6814
7112
|
return out;
|
|
6815
7113
|
}
|
|
6816
|
-
var
|
|
7114
|
+
var PLANNER_INVISIBLE = /* @__PURE__ */ new Set(["tilt"]);
|
|
7115
|
+
function plannerInvisible(prefs) {
|
|
7116
|
+
return paperArcRequested(prefs) ? PLANNER_INVISIBLE : /* @__PURE__ */ new Set([...PLANNER_INVISIBLE, "role"]);
|
|
7117
|
+
}
|
|
7118
|
+
function hideFromPlanner(node, hidden) {
|
|
7119
|
+
if (Array.isArray(node)) return node.map((n3) => hideFromPlanner(n3, hidden));
|
|
7120
|
+
if (node === null || typeof node !== "object") return node;
|
|
7121
|
+
const src = node;
|
|
7122
|
+
const out = {};
|
|
7123
|
+
for (const [key, value] of Object.entries(src)) {
|
|
7124
|
+
if (key === "properties" && value && typeof value === "object") {
|
|
7125
|
+
const kept = {};
|
|
7126
|
+
for (const [prop, sub] of Object.entries(value))
|
|
7127
|
+
if (!hidden.has(prop)) kept[prop] = hideFromPlanner(sub, hidden);
|
|
7128
|
+
out.properties = kept;
|
|
7129
|
+
continue;
|
|
7130
|
+
}
|
|
7131
|
+
if (key === "required" && Array.isArray(value)) {
|
|
7132
|
+
out.required = value.filter((r) => typeof r !== "string" || !hidden.has(r));
|
|
7133
|
+
continue;
|
|
7134
|
+
}
|
|
7135
|
+
out[key] = hideFromPlanner(value, hidden);
|
|
7136
|
+
}
|
|
7137
|
+
return out;
|
|
7138
|
+
}
|
|
7139
|
+
function schemaFor(prefs) {
|
|
7140
|
+
return hideFromPlanner(
|
|
7141
|
+
forStructuredOutput(z2.toJSONSchema(storyboardSchema, { io: "input" })),
|
|
7142
|
+
plannerInvisible(prefs)
|
|
7143
|
+
);
|
|
7144
|
+
}
|
|
7145
|
+
var SCHEMA = schemaFor({ genre: "general" });
|
|
6817
7146
|
async function codexPlanner(source, opts = {}) {
|
|
6818
7147
|
const prefs = opts.prefs ?? prefsSchema.parse({});
|
|
6819
7148
|
const dir = await mkdtemp(join3(tmpdir(), "decksmith-plan-"));
|
|
6820
7149
|
try {
|
|
6821
7150
|
const schemaPath = join3(dir, "storyboard.schema.json");
|
|
6822
7151
|
const outPath = join3(dir, "storyboard.json");
|
|
6823
|
-
await writeFile3(schemaPath, JSON.stringify(
|
|
7152
|
+
await writeFile3(schemaPath, JSON.stringify(schemaFor(prefs)));
|
|
6824
7153
|
await (opts.run ?? runCodex)({
|
|
6825
7154
|
prompt: buildPrompt(source, prefs),
|
|
6826
7155
|
schemaPath,
|
|
@@ -7943,13 +8272,14 @@ function pieceArgs(source, fromFrame, motion, freeze, fps, out) {
|
|
|
7943
8272
|
out
|
|
7944
8273
|
];
|
|
7945
8274
|
}
|
|
8275
|
+
var LOUDNESS = "loudnorm=I=-16:TP=-1.5:LRA=11,aresample=48000";
|
|
7946
8276
|
function audioGraph(inputs, seconds, first = 1) {
|
|
7947
8277
|
const lines = inputs.map(
|
|
7948
8278
|
(input, i) => `[${first + i}:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,adelay=${input.delayMs}:all=1[d${i}]`
|
|
7949
8279
|
);
|
|
7950
8280
|
const labels = inputs.map((_, i) => `[d${i}]`).join("");
|
|
7951
8281
|
lines.push(
|
|
7952
|
-
`${labels}amix=inputs=${inputs.length}:normalize=0:dropout_transition=0:duration=longest,apad=whole_dur=${seconds.toFixed(3)}[aout]`
|
|
8282
|
+
`${labels}amix=inputs=${inputs.length}:normalize=0:dropout_transition=0:duration=longest,${LOUDNESS},apad=whole_dur=${seconds.toFixed(3)}[aout]`
|
|
7953
8283
|
);
|
|
7954
8284
|
return lines.join(";\n");
|
|
7955
8285
|
}
|
|
@@ -8738,6 +9068,7 @@ function parseOptions(fields) {
|
|
|
8738
9068
|
if (str(fields.lang) !== void 0) patch.lang = requireLang(str(fields.lang));
|
|
8739
9069
|
if (str(fields.tone) !== void 0) patch.tone = str(fields.tone);
|
|
8740
9070
|
if (str(fields.density) !== void 0) patch.density = str(fields.density);
|
|
9071
|
+
if (str(fields.genre) !== void 0) patch.genre = str(fields.genre);
|
|
8741
9072
|
if (str(fields.duration) !== void 0)
|
|
8742
9073
|
patch.duration = num("duration", fields.duration);
|
|
8743
9074
|
if (str(fields.speed) !== void 0) patch.animationSpeed = num("speed", fields.speed);
|
|
@@ -9588,6 +9919,9 @@ var settingsSchema = z4.object({
|
|
|
9588
9919
|
),
|
|
9589
9920
|
tone: z4.enum(["plain", "academic", "conversational", "punchy"]).optional(),
|
|
9590
9921
|
density: z4.enum(["sparse", "normal", "dense"]).optional().describe("How much text a SLIDE carries. A different axis from narration_density."),
|
|
9922
|
+
genre: z4.enum(["general", "paper"]).optional().describe(
|
|
9923
|
+
"DECLARED, never detected. `paper` asks for a research-talk shape \u2014 an introduction and background at the front, a limitations slide and then a conclusion at the end \u2014 and reports where the deck missed it. There is no detector: the documents this tool ingests are analyses OF papers, rewritten in a way that drops the headings a detector would need, so nothing infers this and absent means `general`."
|
|
9924
|
+
),
|
|
9591
9925
|
duration: z4.number().min(10).max(1800).optional().describe(
|
|
9592
9926
|
"Target seconds. Sets the pace, derives the slide count when you give none, and overrides animation_speed. Call decksmith_estimate_length first \u2014 a short target buys its words by saying less, not by playing faster."
|
|
9593
9927
|
),
|
|
@@ -9619,6 +9953,7 @@ function fieldsFor(s) {
|
|
|
9619
9953
|
put("lang", s.lang);
|
|
9620
9954
|
put("tone", s.tone);
|
|
9621
9955
|
put("density", s.density);
|
|
9956
|
+
put("genre", s.genre);
|
|
9622
9957
|
put("duration", s.duration);
|
|
9623
9958
|
put("slides", s.slides);
|
|
9624
9959
|
put("speed", s.animation_speed);
|