@jokerized/decksmith 0.9.2 → 0.9.4
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 +449 -90
- package/dist/animate/LICENSE +21 -0
- package/dist/animate/NOTICE +32 -0
- package/dist/animate/core.js +530 -0
- package/dist/animate/cut-paper.js +206 -0
- package/dist/animate/morph.js +131 -0
- package/dist/cli.js +13464 -8876
- package/dist/ds-animate.js +1 -0
- package/dist/index.js +13245 -8657
- package/dist/mcp.js +9778 -7477
- package/dist/types/bespoke/art.d.ts +172 -6
- package/dist/types/bespoke/cache.d.ts +10 -0
- package/dist/types/bespoke/cards.d.ts +66 -0
- package/dist/types/bespoke/contract.d.ts +0 -8
- package/dist/types/bespoke/databuild.d.ts +50 -0
- package/dist/types/bespoke/depth.d.ts +127 -0
- package/dist/types/bespoke/grammar.d.ts +73 -0
- package/dist/types/bespoke/inspect.d.ts +40 -1
- package/dist/types/bespoke/pipeline.d.ts +195 -11
- package/dist/types/bespoke/probe.d.ts +2 -2
- package/dist/types/bespoke/prompt.d.ts +110 -34
- package/dist/types/bespoke/references.d.ts +13 -14
- package/dist/types/bespoke/repair.d.ts +23 -29
- package/dist/types/bespoke/scene.d.ts +55 -10
- package/dist/types/bespoke/select.d.ts +28 -21
- package/dist/types/bespoke/sheet.d.ts +66 -1
- package/dist/types/bespoke/shots.d.ts +225 -22
- package/dist/types/build/files.d.ts +7 -0
- package/dist/types/build/piece.d.ts +8 -0
- package/dist/types/emit/animate-runtime.d.ts +149 -0
- package/dist/types/emit/archetypes/claim-figure.d.ts +84 -1
- package/dist/types/emit/archetypes/equation-walk.d.ts +2 -2
- package/dist/types/emit/archetypes/hero-number.d.ts +21 -0
- package/dist/types/emit/archetypes/index.d.ts +2 -2
- package/dist/types/emit/archetypes/kinetic.d.ts +2 -0
- package/dist/types/emit/archetypes/pipeline.d.ts +14 -2
- package/dist/types/emit/archetypes/stage.d.ts +57 -0
- package/dist/types/emit/backdrop.d.ts +114 -0
- package/dist/types/emit/kit.d.ts +8 -0
- package/dist/types/emit/look.d.ts +14 -0
- package/dist/types/emit/motion.d.ts +78 -46
- package/dist/types/emit/type.d.ts +48 -5
- package/dist/types/images/providers.d.ts +6 -0
- package/dist/types/plan/codex.d.ts +4 -2
- package/dist/types/plan/direct.d.ts +1 -1
- package/dist/types/plan/duration.d.ts +1 -1
- package/dist/types/plan/prompt.d.ts +1 -1
- package/dist/types/plan/refs.d.ts +2 -2
- package/dist/types/plan/variety.d.ts +74 -0
- package/dist/types/prefs.d.ts +20 -1
- package/dist/types/render/captions.d.ts +4 -5
- package/dist/types/render/capture.d.ts +7 -0
- package/dist/types/source/fonts.d.ts +22 -0
- package/dist/types/types.d.ts +680 -4
- package/dist/types/verify/drift.d.ts +5 -0
- package/dist/types/verify/fidelity.d.ts +15 -1
- package/dist/types/verify/index.d.ts +27 -4
- package/dist/types/verify/scenes.d.ts +185 -5
- package/package.json +1 -1
- package/dist/types/bespoke/callouts.d.ts +0 -76
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The script for piece `id`, whose author file `name` (the figure's `src`)
|
|
3
|
+
* holds `author`.
|
|
4
|
+
*
|
|
5
|
+
* The MIT notice rides at the top of every assembled file because the file IS a
|
|
6
|
+
* copy of the kit, shipped inside someone else's deck.
|
|
7
|
+
*/
|
|
8
|
+
export declare function assemblePiece(id: string, name: string, author: string): Promise<string>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime an animate PIECE runs on: a registry the piece's script fills,
|
|
3
|
+
* a `mount` the scene's `measure` calls, and the `dsAnimate` GSAP plugin that
|
|
4
|
+
* draws the piece as part of being seeked.
|
|
5
|
+
*
|
|
6
|
+
* A piece is a figure (`kind: "piece"`, src/types.ts) whose asset is a script:
|
|
7
|
+
* an animate scene file, assembled by src/build/piece.ts with the vendored kit
|
|
8
|
+
* (src/build/animate/) into ONE factory registered here under the figure's id.
|
|
9
|
+
* `claim-figure` draws it on a `<canvas>`. See
|
|
10
|
+
* .planning/2026-10-09-animate-piece-design.md and the spike that measured it.
|
|
11
|
+
*
|
|
12
|
+
* SEEK, NOT PLAY (invariant 1). animate's `renderFrame(t, canvas)` is a pure
|
|
13
|
+
* function of `t` — it repaints from a clean sheet every call — so the plugin's
|
|
14
|
+
* `render` is the only thing that ever draws, and it runs as part of the seek.
|
|
15
|
+
* Nothing here runs on a callback (invariant 11).
|
|
16
|
+
*
|
|
17
|
+
* THE TWEEN VALUE IS THE PIECE'S OWN TIME, IN SECONDS. One convention, chosen
|
|
18
|
+
* because it is the one the spike rendered and watched:
|
|
19
|
+
*
|
|
20
|
+
* tl.fromTo("#s2-pc", { dsAnimate: 0 }, { dsAnimate: S, duration: S, ease: "none" }, at)
|
|
21
|
+
*
|
|
22
|
+
* where `S` is the figure's `seconds`. The tween's value at ratio `r` is
|
|
23
|
+
* `r * S`, a piece-local second, and the tween runs at 1:1 with the deck's
|
|
24
|
+
* clock. A 0-to-1 value would work too (design A wrote it), but two conventions
|
|
25
|
+
* in one codebase is how one of them gets the other's arithmetic.
|
|
26
|
+
*
|
|
27
|
+
* THE LAST-FRAME CLAMP. morph.js picks its frame as
|
|
28
|
+
* `round(t * FPS) % NFRAMES`, so the tween's end, `t = S`, is frame NFRAMES —
|
|
29
|
+
* which wraps to frame 0. The piece would snap back to its first pose for the
|
|
30
|
+
* whole hold after it. `pieceTime` stops at `(n - 1) / fps`, the last frame
|
|
31
|
+
* that exists.
|
|
32
|
+
*
|
|
33
|
+
* A DRAWING ERROR IS A PAGE ERROR. The hyperframes runtime runs every seek
|
|
34
|
+
* inside a `catch` that reports only to a hook the page must install, so an
|
|
35
|
+
* error thrown from `render` vanished: lint, check, verify and render all
|
|
36
|
+
* exited 0 over a canvas frozen on its last good pose (the spike's Q2). So
|
|
37
|
+
* `render` hands the error to `reportError` — the browser's own "uncaught
|
|
38
|
+
* exception" path, synchronous, which `hyperframes check` records as a
|
|
39
|
+
* `page_error` and so fails `decksmith verify` — and then rethrows it for any
|
|
40
|
+
* caller that does not swallow. `render` still exits 0; verify is the gate.
|
|
41
|
+
*
|
|
42
|
+
* ONE DRAW PER FRAME. hyperframes' transport seek moves the timeline three
|
|
43
|
+
* times per captured frame (`N`, `N + .001`, `N`), and each one used to repaint
|
|
44
|
+
* the whole piece. morph.js draws a pure function of `round(t * FPS)` (with
|
|
45
|
+
* `sub` unset), so a second draw of the same frame on the same canvas paints
|
|
46
|
+
* the same pixels: `render` skips it. The memo is per canvas, cleared before a
|
|
47
|
+
* draw and set only after it returns: a draw that throws leaves the canvas
|
|
48
|
+
* half-painted, so it must not be remembered as holding any frame.
|
|
49
|
+
*
|
|
50
|
+
* Bundled to an IIFE by `scripts/build.mjs` as `dist/ds-animate.js` and loaded
|
|
51
|
+
* by a deck only when some scene names `dsAnimate` (PLUGINS in
|
|
52
|
+
* composition.ts). The pure halves are exported for the tests, which is why the
|
|
53
|
+
* window registration at the bottom is guarded.
|
|
54
|
+
*/
|
|
55
|
+
/** What the scene's `measure` passes `mount`: the numbers the factory's head needs. */
|
|
56
|
+
export interface PieceConfig {
|
|
57
|
+
/** The piece's length — the figure's `seconds`. */
|
|
58
|
+
seconds: number;
|
|
59
|
+
/** Frames per second the piece is baked at. */
|
|
60
|
+
fps: number;
|
|
61
|
+
/** The deck's font stack, for the kit's `HAND`. No text is drawn; see `mount`. */
|
|
62
|
+
hand: string;
|
|
63
|
+
}
|
|
64
|
+
/** What a piece's factory hands back: animate's seekable draw, and its frame count. */
|
|
65
|
+
export interface Piece {
|
|
66
|
+
rf: (t: number, canvas: HTMLCanvasElement) => unknown;
|
|
67
|
+
n: number;
|
|
68
|
+
fps: number;
|
|
69
|
+
}
|
|
70
|
+
/** A piece's script registers one of these under its figure id. */
|
|
71
|
+
export type PieceFactory = (cfg: PieceConfig & {
|
|
72
|
+
width: number;
|
|
73
|
+
height: number;
|
|
74
|
+
}) => Piece;
|
|
75
|
+
/**
|
|
76
|
+
* The attribute `claim-figure` writes on a piece's `<canvas>`. It says the
|
|
77
|
+
* canvas is 2D by construction: `mount` takes its 2D context before anything
|
|
78
|
+
* draws, and a canvas holding a 2D context can never hold a WebGL one. The
|
|
79
|
+
* capture path (`openDeck`, src/render/capture.ts) reads it to exempt the
|
|
80
|
+
* canvas from its no-WebGL refusal, which otherwise refused every piece on a
|
|
81
|
+
* checker without GL and so skipped the whole fidelity gate (the spike's Q4).
|
|
82
|
+
*/
|
|
83
|
+
export declare const PIECE_ATTR = "data-ds-piece";
|
|
84
|
+
/** Filled by each piece's script as the document parses. */
|
|
85
|
+
export declare const pieces: Record<string, PieceFactory>;
|
|
86
|
+
/** A mounted piece: what draws it, and the frame its canvas last finished drawing. */
|
|
87
|
+
interface Host {
|
|
88
|
+
id: string;
|
|
89
|
+
piece: Piece;
|
|
90
|
+
drawn: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The piece-local second to draw at tween value `t`: `t`, but never past the
|
|
94
|
+
* last frame — see THE LAST-FRAME CLAMP above.
|
|
95
|
+
*/
|
|
96
|
+
export declare function pieceTime(t: number, piece: {
|
|
97
|
+
n: number;
|
|
98
|
+
fps: number;
|
|
99
|
+
}): number;
|
|
100
|
+
/**
|
|
101
|
+
* Build piece `id` against `canvas`. Called from the scene's `measure`, inside
|
|
102
|
+
* the ready gate, so it runs once, before the timeline that tweens it exists.
|
|
103
|
+
* A throw from the factory — the author's file runs here, not at script load —
|
|
104
|
+
* leaves the scene's timeline unregistered, which `check` already reports.
|
|
105
|
+
*
|
|
106
|
+
* THE CONTEXT IS TAKEN HERE, FIRST, with `willReadFrequently`, so the raster
|
|
107
|
+
* mode is fixed from frame 0 rather than flipped by whatever reads it back
|
|
108
|
+
* later (.planning/2026-09-06-canvas-seek-purity.md).
|
|
109
|
+
*/
|
|
110
|
+
export declare function mount(canvas: HTMLCanvasElement | null, id: string, cfg: PieceConfig): void;
|
|
111
|
+
/** A piece frame that threw under `sweep`. */
|
|
112
|
+
export interface BrokenFrame {
|
|
113
|
+
id: string;
|
|
114
|
+
/** The frames that threw, in order. */
|
|
115
|
+
frames: number[];
|
|
116
|
+
/** The first one's message. */
|
|
117
|
+
message: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* EVERY FRAME OF EVERY MOUNTED PIECE, drawn once, for the gate.
|
|
121
|
+
*
|
|
122
|
+
* A DRAWING ERROR IS A PAGE ERROR only at the times something seeks to, and
|
|
123
|
+
* `hyperframes check` samples a long deck coarsely: a throw confined to a short
|
|
124
|
+
* window — a bridge's sub-phase, one frame — could fall between its samples,
|
|
125
|
+
* pass verify, and ship half-painted frames from a render that exits 0. So
|
|
126
|
+
* `fidelity` calls this once and fails on what it returns.
|
|
127
|
+
*
|
|
128
|
+
* Drawn on a scratch canvas of the same size, never the mounted one: `renderFrame`
|
|
129
|
+
* reads nothing of its canvas but the context, and the deck's canvas and its
|
|
130
|
+
* frame memo stay exactly as the timeline left them. Each draw runs under the
|
|
131
|
+
* text trap, as the plugin's does. Nothing is reported to the page; the caller
|
|
132
|
+
* reads the result.
|
|
133
|
+
*/
|
|
134
|
+
export declare function sweep(): BrokenFrame[];
|
|
135
|
+
interface PluginState {
|
|
136
|
+
host: Host;
|
|
137
|
+
canvas: HTMLCanvasElement;
|
|
138
|
+
end: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* `dsAnimate` as a GSAP property. The tween's ease must be "none": the value is
|
|
142
|
+
* a clock, and an ease would play the piece at a varying speed.
|
|
143
|
+
*/
|
|
144
|
+
export declare const DSAnimatePlugin: {
|
|
145
|
+
name: string;
|
|
146
|
+
init(this: PluginState, target: HTMLCanvasElement, value: unknown): void;
|
|
147
|
+
render(ratio: number, d: PluginState): void;
|
|
148
|
+
};
|
|
149
|
+
export {};
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A claim from the source, shown next to the figure that backs it.
|
|
3
|
+
*
|
|
4
|
+
* The layout is chosen from the figure's own aspect ratio rather than being a
|
|
5
|
+
* parameter: EXPERIMENT-002 full-bled a 1.98-aspect figure and pushed its
|
|
6
|
+
* caption 200px off-canvas. Only a genuine strip earns the full width.
|
|
7
|
+
*/
|
|
8
|
+
import type { Figure } from "../../types.js";
|
|
1
9
|
import { type Fit } from "../fit.js";
|
|
2
|
-
import type { Emitter } from "../kit.js";
|
|
10
|
+
import type { Emitter, Tween } from "../kit.js";
|
|
11
|
+
/**
|
|
12
|
+
* The shortest beat that plays a piece of `seconds`: its entrance, the piece,
|
|
13
|
+
* and the tail before its hold. Exported for the planner, which has to be told
|
|
14
|
+
* it before it picks a beat's length (src/plan/prompt.ts).
|
|
15
|
+
*/
|
|
16
|
+
export declare const pieceBeatSeconds: (seconds: number) => number;
|
|
3
17
|
type Mode = "tall" | "wide" | "beside";
|
|
4
18
|
/** One way to draw the beat, predicted. */
|
|
5
19
|
export interface Arrangement {
|
|
@@ -13,5 +27,74 @@ export interface Arrangement {
|
|
|
13
27
|
}
|
|
14
28
|
/** The arrangement to draw: the classic mode unless another fills clearly more. */
|
|
15
29
|
export declare function choose(options: readonly Arrangement[]): Arrangement;
|
|
30
|
+
/** What the plate holds, and the tag its height cap and its drift rule name. */
|
|
31
|
+
export interface Plate {
|
|
32
|
+
html: string;
|
|
33
|
+
/** `img`, `video` or `canvas`. One CSS rule is written, for whichever is actually there. */
|
|
34
|
+
el: "img" | "video" | "canvas";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A CLIP WE HOLD THE FILE FOR IS A `<video>`. EVERYTHING ELSE IS AN `<img>`.
|
|
38
|
+
*
|
|
39
|
+
* A PLAYER-PAGE CLIP IS NOT AN ERROR PATH, and that is the whole of why this
|
|
40
|
+
* branches on `href` rather than on `kind`. `href` is present exactly when the
|
|
41
|
+
* video's bytes were unavailable — src/types.ts calls it "Absent for a clip we
|
|
42
|
+
* hold the file for" — and the still is then everything any format can show,
|
|
43
|
+
* which is what `renderSource` already promises the planner in those words
|
|
44
|
+
* ("its still is all any format will ever show"). Refusing there would delete a
|
|
45
|
+
* beat the planner was told it could spend, over a picture that draws perfectly
|
|
46
|
+
* well.
|
|
47
|
+
*
|
|
48
|
+
* The `<img>` branch is byte-for-byte the markup every still has always had, so
|
|
49
|
+
* a deck with no clip in it is the deck it was.
|
|
50
|
+
*
|
|
51
|
+
* AND IT STAYS AN `<img>` NOW THAT `deck.html` CAN BUILD A REAL PLAYER FROM THE
|
|
52
|
+
* SAME FIGURE. `emitDeck` asks `embedUrl` for the frame-able form of this
|
|
53
|
+
* `href` and writes it into a deck-only island, so a presented deck offers
|
|
54
|
+
* click-to-play where the mp4 shows the still. None of that reaches here, and it
|
|
55
|
+
* must not: this markup goes into `index.html`, which is the document the
|
|
56
|
+
* renderer CAPTURES. A third-party frame there breaks invariant 4 twice over —
|
|
57
|
+
* virtual time is propagated only into same-origin frames, so the embed would
|
|
58
|
+
* play at wall-clock speed while the deck is seeked; and the compile-time
|
|
59
|
+
* localiser has no pattern for an iframe's src, so every render would refetch it
|
|
60
|
+
* from the network. `scanDeterminism` refuses a literal `<iframe` in a
|
|
61
|
+
* composition, and test/emit.test.ts ("never embeds a document it does not own")
|
|
62
|
+
* asserts the vocabulary never writes one — or this figure's watch URL — to
|
|
63
|
+
* begin with.
|
|
64
|
+
*
|
|
65
|
+
* SHARED WITH `stage`, which is why the refusals name `who` (the archetype and
|
|
66
|
+
* beat) rather than assuming claim-figure, and why a piece's canvas may be
|
|
67
|
+
* given a `buffer` size: a stage canvas is the frame, not the figure's box.
|
|
68
|
+
*/
|
|
69
|
+
export declare function plate(fig: Figure, sid: string, who: string, start: number | undefined, buffer?: {
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
}): Plate;
|
|
73
|
+
/**
|
|
74
|
+
* What a piece adds to the scene: the statement that mounts it, ONE `fromTo`
|
|
75
|
+
* that plays it, and the hold after its last frame.
|
|
76
|
+
*
|
|
77
|
+
* THE TWEEN IS THE ONLY THING THAT DRAWS (invariants 1 and 11). Its value is
|
|
78
|
+
* the piece's own second — `0` to `seconds` over `seconds`, ease "none" —
|
|
79
|
+
* and the `dsAnimate` plugin draws that second as part of the seek
|
|
80
|
+
* (src/emit/animate-runtime.ts). No callback, no CSS animation: before the
|
|
81
|
+
* tween the canvas holds frame 0, after it the last frame, so holds and slide
|
|
82
|
+
* edges are static.
|
|
83
|
+
*
|
|
84
|
+
* `hand` is the deck's font stack, for the kit's `HAND`: a piece draws no text,
|
|
85
|
+
* and the kit's `measureText` should still never name a family the deck does
|
|
86
|
+
* not declare (invariant 9).
|
|
87
|
+
*
|
|
88
|
+
* REFUSED BY NAME rather than clamped: a piece with no length has no clock, a
|
|
89
|
+
* `src` not ending in `.js` is a script verify never scans, and
|
|
90
|
+
* a beat too short for the piece would end it mid-motion with the hold clamped
|
|
91
|
+
* on top of a moving frame. `who` names the archetype and beat, because
|
|
92
|
+
* `stage` plays a piece through this too.
|
|
93
|
+
*/
|
|
94
|
+
export declare function pieceTimeline(fig: Figure, sid: string, who: string, seconds: number, hand: string): {
|
|
95
|
+
tween: Tween;
|
|
96
|
+
mount: string;
|
|
97
|
+
hold: number;
|
|
98
|
+
};
|
|
16
99
|
export declare const claimFigure: Emitter<"claim-figure">;
|
|
17
100
|
export {};
|
|
@@ -102,7 +102,7 @@ export declare function chipSetup(sid: string, term: Term): string;
|
|
|
102
102
|
* asserted something false. `statements` and the `size` cap below are what make
|
|
103
103
|
* the claim true.
|
|
104
104
|
*/
|
|
105
|
-
export declare function equationSize(tex: string): number;
|
|
105
|
+
export declare function equationSize(tex: string, v2?: boolean): number;
|
|
106
106
|
/** Estimated rendered width of a display, in ems. */
|
|
107
107
|
export declare function texUnits(tex: string): number;
|
|
108
108
|
/**
|
|
@@ -119,5 +119,5 @@ export declare function texUnits(tex: string): number;
|
|
|
119
119
|
export declare function chipFit(sid: string, terms: Term[]): string[];
|
|
120
120
|
/** One row per term: a chip KaTeX fills in `setup`, and the label. Shared with the morph. */
|
|
121
121
|
export declare function legendRows(sid: string, terms: Term[], theme: Theme): string;
|
|
122
|
-
export declare function legendCss(theme: Theme): string;
|
|
122
|
+
export declare function legendCss(theme: Theme, v2?: boolean): string;
|
|
123
123
|
export declare const equationWalk: Emitter<"equation-walk">;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Emitter } from "../kit.js";
|
|
2
|
+
/** Bars only when the shorter is at most this share of the longer — see the header. */
|
|
3
|
+
export declare const NEAR = 0.85;
|
|
4
|
+
/**
|
|
5
|
+
* How many full turns reel `i` (0 the leftmost) makes before it lands: one for the most
|
|
6
|
+
* significant, up to three for the least — a counter's right-hand wheels spin
|
|
7
|
+
* fastest.
|
|
8
|
+
*/
|
|
9
|
+
export declare function turnsOf(i: number): number;
|
|
10
|
+
/**
|
|
11
|
+
* `s` as a plain number, or undefined. Thousands separators are allowed; a
|
|
12
|
+
* fraction, a percentage or a multiplier is not a length on one scale with
|
|
13
|
+
* anything, so it is not drawn as one.
|
|
14
|
+
*/
|
|
15
|
+
export declare function plainNumber(s: string): number | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* `value - compare`, signed, at the finer of the two's decimal places, with the
|
|
18
|
+
* unit: "+0.83 dB". Only called when both are plain numbers.
|
|
19
|
+
*/
|
|
20
|
+
export declare function signedDelta(value: string, compare: string, unit?: string): string;
|
|
21
|
+
export declare const heroNumber: Emitter<"hero-number">;
|
|
@@ -4,8 +4,8 @@ export declare const emitters: {
|
|
|
4
4
|
[A in Archetype]: Emitter<A>;
|
|
5
5
|
};
|
|
6
6
|
/**
|
|
7
|
-
* Dispatch a beat to its emitter. The cast is the one place the pairing is taken
|
|
8
|
-
* on trust: `emitters[beat.archetype]` is a union of
|
|
7
|
+
* Dispatch a beat to its emitter, over its backdrop when it has one. The cast is the one place the pairing is taken
|
|
8
|
+
* on trust: `emitters[beat.archetype]` is a union of sixteen emitters and TypeScript
|
|
9
9
|
* will not narrow the key and the beat together. The table above already proves
|
|
10
10
|
* every archetype has exactly one emitter of the right shape.
|
|
11
11
|
*/
|
|
@@ -77,12 +77,24 @@ export interface PipeLayout {
|
|
|
77
77
|
* `budget` is the height the SVG may take (the region less the wrapper's margin
|
|
78
78
|
* and the note); `region` is the whole body region, which bounds a box's height
|
|
79
79
|
* at `GROWN_BOX_H` of it — the plan's `MAX_BOX_H = 0.8·region.h`. The label's
|
|
80
|
-
* preferred size
|
|
80
|
+
* preferred size is `TYPE_SCALE.body` and `fitBoxes` still shrinks it to the width.
|
|
81
81
|
*/
|
|
82
82
|
export interface PipeGrow {
|
|
83
83
|
budget: number;
|
|
84
84
|
region: number;
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The split of `label` into at most `k` lines whose WIDEST line is narrowest,
|
|
88
|
+
* with no line an orphan (`ORPHAN_SHARE`) — fewer lines when every split into
|
|
89
|
+
* `k` leaves one, so the grown row takes the bigger type only when it reads.
|
|
90
|
+
*
|
|
91
|
+
* `balance` breaks once a line has taken its share, which never breaks a
|
|
92
|
+
* two-word label whose first word is the shorter: "Split attributes" stays one
|
|
93
|
+
* line at k = 2, so asking for two lines bought nothing and the grown row held
|
|
94
|
+
* every label at the one-line size (46px, in 545px boxes). Labels are a handful
|
|
95
|
+
* of words, so every placement of the `k - 1` breaks is simply tried.
|
|
96
|
+
*/
|
|
97
|
+
export declare function splitEven(label: string, k: number, face: Face): string[];
|
|
86
98
|
/**
|
|
87
99
|
* All the geometry, with no SVG in sight.
|
|
88
100
|
*
|
|
@@ -90,6 +102,6 @@ export interface PipeGrow {
|
|
|
90
102
|
* archetype must never get wrong, and asserting it against numbers beats
|
|
91
103
|
* asserting it against a rendered string.
|
|
92
104
|
*/
|
|
93
|
-
export declare function pipeLayout(stageW: number, stages: readonly Stage[], loop?: Loop, face?: Face, grow?: PipeGrow): PipeLayout;
|
|
105
|
+
export declare function pipeLayout(stageW: number, stages: readonly Stage[], loop?: Loop, face?: Face, grow?: PipeGrow, size?: number): PipeLayout;
|
|
94
106
|
export declare const pipeline: Emitter<"pipeline">;
|
|
95
107
|
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One figure that owns the whole frame.
|
|
3
|
+
*
|
|
4
|
+
* Every other archetype draws inside the scene's padded content box under a
|
|
5
|
+
* headline, so a deck of them is one layout repeated: headline top, body below,
|
|
6
|
+
* the figure in a framed plate. A stage beat is the other kind of slide — a
|
|
7
|
+
* product UI, a scene, a photo or an animate piece edge to edge, with no plate,
|
|
8
|
+
* border or side column, and the words (if any) set over it.
|
|
9
|
+
*
|
|
10
|
+
* WHAT KEEPS THE WORDS READABLE IS THE SCRIM, NOT THE PICTURE. The media is
|
|
11
|
+
* arbitrary — a white paper figure, a dark night scene — so the text is always
|
|
12
|
+
* white over a black scrim whose solid part covers the whole text block at
|
|
13
|
+
* `SCRIM` alpha. At 0.55 black over a pure-white pixel the background is
|
|
14
|
+
* 115/255, which white text clears at about 4.8:1 — over the 4.5:1 bar for
|
|
15
|
+
* body text, though every stage line is large text (3:1) — so the contrast
|
|
16
|
+
* gate passes on any picture rather than on the one it was tried with. It was
|
|
17
|
+
* 0.66 (7:1), which read as a grey slab over a light picture. The solid extent is
|
|
18
|
+
* DERIVED from the text block's measured height and width, and is
|
|
19
|
+
* bounded on BOTH axes (`scrimFor`): a scrim that ran the whole width dimmed
|
|
20
|
+
* the picture's subject wherever it shared the words' rows.
|
|
21
|
+
*
|
|
22
|
+
* THE TEXT IS NEVER SHRUNK. Headline and line have fixed sizes above the 40px
|
|
23
|
+
* floor (invariant 5). Text that does not fit its placement's column in
|
|
24
|
+
* `MAX_HEAD_LINES` / `MAX_LINE_LINES` takes the next wider column (`WIDER`),
|
|
25
|
+
* so the words cover more of the picture rather than the slide being dropped;
|
|
26
|
+
* only text that will not fit across the whole content width is refused by
|
|
27
|
+
* name, the same answer callout and split-compare give.
|
|
28
|
+
*
|
|
29
|
+
* THE CAPTION RESERVE IS LEFT CLEAR. The media and its scrim stop `reserveRef`
|
|
30
|
+
* above the bottom edge, so a deck that asked for burned captions keeps the
|
|
31
|
+
* strip it asked for (`ink_in_caption_reserve`). Nothing that MOVES is that
|
|
32
|
+
* box: the entrance scales an inner wrapper the box clips, so no frame of it
|
|
33
|
+
* reaches the strip either.
|
|
34
|
+
*/
|
|
35
|
+
import type { STAGE_PLACEMENTS } from "../../types.js";
|
|
36
|
+
import type { Emitter } from "../kit.js";
|
|
37
|
+
type Placed = Exclude<(typeof STAGE_PLACEMENTS)[number], "none">;
|
|
38
|
+
/**
|
|
39
|
+
* The scrim for a text block `textH` tall whose longest line is `textW` wide,
|
|
40
|
+
* in reference px of a scrim box `boxW` x `boxH`. The words' own width, not
|
|
41
|
+
* their column's: a one-line headline in a 62% column left the scrim solid over
|
|
42
|
+
* picture the words never reach. Solid over the block and `SCRIM_MARGIN` past it,
|
|
43
|
+
* then a `SCRIM_FADE` to clear — so the picture is untouched away from the
|
|
44
|
+
* words, on both axes.
|
|
45
|
+
*
|
|
46
|
+
* TWO GRADIENTS, MULTIPLIED: `background` runs along one axis and `mask` along
|
|
47
|
+
* the other, so the scrim's alpha is the product — solid only where both are,
|
|
48
|
+
* which is the block plus its margin. Two `background` layers cannot do this:
|
|
49
|
+
* layers composite as a union, and a union of two full-length bands is the
|
|
50
|
+
* header bar across the slide this archetype exists to avoid.
|
|
51
|
+
*/
|
|
52
|
+
export declare function scrimFor(placement: Placed, textH: number, textW: number, boxW: number, boxH: number): {
|
|
53
|
+
background: string;
|
|
54
|
+
mask: string;
|
|
55
|
+
};
|
|
56
|
+
export declare const stage: Emitter<"stage">;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A diagram drawn over a full-bleed picture.
|
|
3
|
+
*
|
|
4
|
+
* The founder's bar is explainer-video motion, and what he hates is a deck of
|
|
5
|
+
* cards on one pale ground. Stage gives a picture the whole frame but has no
|
|
6
|
+
* room for a mechanism; the diagram archetypes draw the mechanism on the pack's
|
|
7
|
+
* ground. A `backdrop` (src/types.ts) puts the second over the first: the
|
|
8
|
+
* picture covers the frame and drifts, a scrim darkens it, and the panels are
|
|
9
|
+
* drawn as dark glass with light ink.
|
|
10
|
+
*
|
|
11
|
+
* ONE WRAPPER, NOT FOUR EMITTERS TAUGHT A SECOND PALETTE. Every archetype paints
|
|
12
|
+
* through its `Theme` — panel, ink, rule, tones — so the archetype is emitted
|
|
13
|
+
* unchanged with the GLASS theme (`glass`) and this file adds the picture under
|
|
14
|
+
* it. Geometry, holds and stop counts are therefore the archetype's own: only
|
|
15
|
+
* colours move, which is also why the measuring passes (`planCut`, `narrate`,
|
|
16
|
+
* `timing`) can run through it without disagreeing with the build.
|
|
17
|
+
*
|
|
18
|
+
* THE ARCHETYPES' CSS IS UNSCOPED (`.panel{background:…}`) and the shell emits
|
|
19
|
+
* each distinct block once for the whole deck, so a glass `.panel` rule would
|
|
20
|
+
* repaint every callout in the deck — or lose to a pale one emitted later. So a
|
|
21
|
+
* backdrop scene's stylesheet is scoped to the scene (`scopeCss`), raising every
|
|
22
|
+
* one of its rules by the same one id, so their order among themselves is kept.
|
|
23
|
+
*
|
|
24
|
+
* WHAT KEEPS THE WORDS READABLE IS THE SCRIM, NOT THE PICTURE — stage's rule,
|
|
25
|
+
* applied to the whole frame because a diagram's words are everywhere. At
|
|
26
|
+
* `SCRIM` black over a pure-white pixel the ground is 97/255; every glass ink —
|
|
27
|
+
* `MUTED`, and each tone after `readable` lifts it — clears 4.5:1 on that, so
|
|
28
|
+
* the contrast gate passes on any picture rather than on the one tried. Text on
|
|
29
|
+
* a glass panel sits on the panel's own fill over that, darker still.
|
|
30
|
+
*
|
|
31
|
+
* `DIM` ALONE IS HELD TO 3:1, and on purpose. It is what an archetype steps
|
|
32
|
+
* back to — bar-compare paints every bar it is not pointing at in it — so it
|
|
33
|
+
* must be DARKER than every lifted tone, or the bars in the background are the
|
|
34
|
+
* brightest marks on the slide (round 1 of the 2026-10-09 ko e2e: dim at
|
|
35
|
+
* luminance 0.76 over tones at 0.71-0.74, in every pack). Tones sit at 4.5:1,
|
|
36
|
+
* so dim goes below that; 3:1 is the contrast audit's floor for large text,
|
|
37
|
+
* which all audience text is (invariant 5: never below 40px).
|
|
38
|
+
*/
|
|
39
|
+
import type { Backdrop, Beat, Figure } from "../types.js";
|
|
40
|
+
import type { EmitContext, Scene, Theme } from "./kit.js";
|
|
41
|
+
/** Black at this alpha over the whole picture — see the header. */
|
|
42
|
+
export declare const SCRIM = 0.62;
|
|
43
|
+
/** The backdrop a beat asks for, if its archetype takes one. */
|
|
44
|
+
export declare function backdropOf(beat: Beat): Backdrop | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The figure a beat's backdrop resolves to, or undefined when it has none.
|
|
47
|
+
*
|
|
48
|
+
* A PENDING backdrop — a brief `illustrate` has not drawn — is drawn without
|
|
49
|
+
* one rather than refused: the planner's own passes (`assertInsideResolves`)
|
|
50
|
+
* emit beats before `illustrate` runs, and `assertRefsResolve` refuses a
|
|
51
|
+
* pending brief before any build gets here.
|
|
52
|
+
*/
|
|
53
|
+
export declare function backdropFigure(beat: Beat, ctx: EmitContext): Figure | undefined;
|
|
54
|
+
/** Contrast of `hex` on the worst-case ground. */
|
|
55
|
+
export declare function onWorstGround(hex: string): number;
|
|
56
|
+
/**
|
|
57
|
+
* The theme a backdrop scene is drawn in: the pack's type and forms, glass
|
|
58
|
+
* colours, and its tones lifted only as far as they must go to read on the
|
|
59
|
+
* scrim (`readable`).
|
|
60
|
+
*/
|
|
61
|
+
export declare function glass(theme: Theme): Theme;
|
|
62
|
+
/**
|
|
63
|
+
* Every top-level rule of `css` raised by the scene's id. A selector already
|
|
64
|
+
* rooted at the scene (`#s3 .panel`) repeats the id (`#s3#s3 .panel`, the same
|
|
65
|
+
* element) so it stays exactly one id above the rules it used to beat; any
|
|
66
|
+
* other selector is put under the scene. At-rules — the `.ds-live` ambient
|
|
67
|
+
* rule, already scoped by its own construction — are left alone.
|
|
68
|
+
*/
|
|
69
|
+
export declare function scopeCss(css: string, sid: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* `scene`, emitted with the `glass` theme, set over `fig`.
|
|
72
|
+
*
|
|
73
|
+
* The picture sits UNDER the archetype's own html at `z-index:-1` inside a
|
|
74
|
+
* scene that isolates its stacking, so no in-flow element has to be told it is
|
|
75
|
+
* on a picture. It stops `reserveRef` above the bottom edge, as stage's does,
|
|
76
|
+
* so a deck with burned captions keeps its strip clear.
|
|
77
|
+
*
|
|
78
|
+
* `fit` is dropped: the fill gate measures ink off the frame's modal colour,
|
|
79
|
+
* and a full-bleed picture has none, so its prediction could only disagree.
|
|
80
|
+
*/
|
|
81
|
+
export declare function overBackdrop(scene: Scene, fig: Figure, ctx: EmitContext, seconds: number): Scene;
|
|
82
|
+
/**
|
|
83
|
+
* The FIELD a full-bleed archetype stands on when it has no picture: the
|
|
84
|
+
* pack's accent taken toward black until it is no lighter than half the
|
|
85
|
+
* scrim's worst-case ground (`WORST_GROUND`). Every `glass` ink is held to
|
|
86
|
+
* 4.5:1 on that ground, so on a darker one each clears it with room to spare —
|
|
87
|
+
* the same proof, not a second palette. Keeps the hue, so a deck's fields are
|
|
88
|
+
* its own colour rather than one stock navy. Not `#rrggbb`: the glass ground.
|
|
89
|
+
*/
|
|
90
|
+
export declare function fieldColour(accent: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Which of the pack's colours a field takes, by the scene's place in the deck:
|
|
93
|
+
* the accent and each tone that is not the accent, in turn. Round 2 of the
|
|
94
|
+
* 2026-10-09 ko e2e set five field beats on one identical blue, and two hero
|
|
95
|
+
* numbers 19 s apart read as the same slide with new digits. Neighbours differ
|
|
96
|
+
* whenever the pack has two colours, and beats two apart whenever it has three
|
|
97
|
+
* — the variety rule never lets two of one archetype touch, so two apart is the
|
|
98
|
+
* nearest the same layout comes. By the scene id, not a counter, so every pass
|
|
99
|
+
* that emits one beat (planCut, narrate, the Director) agrees with the build.
|
|
100
|
+
*/
|
|
101
|
+
export declare function fieldStep(theme: Theme, sid: string): {
|
|
102
|
+
colour: string;
|
|
103
|
+
focus: string;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* `scene`, emitted with the `glass` theme, set over a field of the pack's
|
|
107
|
+
* accent (`fieldColour`) — what `hero-number` and `kinetic` stand on without a
|
|
108
|
+
* `backdrop`, so that neither is ever a card on the pale ground.
|
|
109
|
+
*
|
|
110
|
+
* The field is never lighter than its flat colour: what moves over it is a
|
|
111
|
+
* vignette of black, drifting slowly across the beat on a `fromTo`, so the
|
|
112
|
+
* frame is alive without any pixel of it rising toward the inks.
|
|
113
|
+
*/
|
|
114
|
+
export declare function overField(scene: Scene, ctx: EmitContext, seconds: number): Scene;
|
package/dist/types/emit/kit.d.ts
CHANGED
|
@@ -455,6 +455,14 @@ export interface Scene {
|
|
|
455
455
|
holds: number[];
|
|
456
456
|
/** CSS this archetype needs. Deduplicated by the shell, emitted once. */
|
|
457
457
|
css?: string;
|
|
458
|
+
/**
|
|
459
|
+
* The scene's entrances ARE what it says, so the v2 motion grammar must not
|
|
460
|
+
* re-voice them (`restyleEntrance` in ./motion.ts). Set by `kinetic`, whose
|
|
461
|
+
* phrases each arrive with a move of their own: re-voiced into the deck's one
|
|
462
|
+
* verb for the scene, four phrases arrived the same way and the beat was a
|
|
463
|
+
* paragraph fading in. In memory only, never serialised.
|
|
464
|
+
*/
|
|
465
|
+
ownEntrances?: boolean;
|
|
458
466
|
/**
|
|
459
467
|
* The archetype's own prediction of how full its final hold is — v2 only.
|
|
460
468
|
* In memory, never serialised into the composition (like `parts`); the shell
|
|
@@ -68,6 +68,20 @@ export declare function variantOf(ctx: EmitContext, archetype: Archetype): strin
|
|
|
68
68
|
* not, whatever their text says.
|
|
69
69
|
*/
|
|
70
70
|
export declare function signature(archetype: Archetype, look?: Look): string;
|
|
71
|
+
/**
|
|
72
|
+
* Whether an archetype has chrome a look places. A `stage` sets its words over
|
|
73
|
+
* the picture where its own `placement` param says, so the `top` in its classic
|
|
74
|
+
* look is not on the slide: counted as top chrome, every stage inflated
|
|
75
|
+
* `modalChrome` and charged the beat after two of them for a "third top in a
|
|
76
|
+
* row" nobody could see.
|
|
77
|
+
*/
|
|
78
|
+
export declare function chromeless(archetype: Archetype): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* `signature`, for a beat. A stage's is where its words sit — the one thing
|
|
81
|
+
* that tells two stages apart at a glance — rather than the `top` its look
|
|
82
|
+
* carries, which every stage shares.
|
|
83
|
+
*/
|
|
84
|
+
export declare function beatSignature(beat: Beat, look?: Look): string;
|
|
71
85
|
/**
|
|
72
86
|
* Whether the canvas is wide enough to set a headline in a column beside the
|
|
73
87
|
* body. 16:9 only in practice: at 1:1 the rail would be 292px and a 64px
|