@jokerized/decksmith 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli.js +455 -110
- package/dist/ds-morph.js +1 -0
- package/dist/index.js +452 -110
- package/dist/mcp.js +263 -33
- 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 +7 -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/types.d.ts +142 -0
- package/dist/types/verify/apparent.d.ts +123 -0
- package/dist/types/verify/typefloor.d.ts +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tilting a plane, and paying for it in type size.
|
|
3
|
+
*
|
|
4
|
+
* Gap 2 wants depth as exposition — a diagram tilting to reveal a layer. The
|
|
5
|
+
* expensive half is not the transform. It is that perspective shrinks the type on
|
|
6
|
+
* the far half of the plane, and invariant 5 is about what the AUDIENCE sees, so
|
|
7
|
+
* a tilt that leaves a 40px label drawing 31px has broken the floor whatever the
|
|
8
|
+
* source says. `src/verify/apparent.ts` is the gate that catches it; this module
|
|
9
|
+
* is how an archetype avoids tripping it, by declaring bigger type up front.
|
|
10
|
+
*
|
|
11
|
+
* WHAT IS AND IS NOT SUPPORTED HERE. One transform on one plane — the whole
|
|
12
|
+
* scene's content tilts as a unit. NOT per-element depth: measured in Chrome,
|
|
13
|
+
* `translateZ` on an SVG `<g>` under a `preserve-3d` ancestor is ignored
|
|
14
|
+
* completely (z=0, z=+200 and z=-200 paint the identical rect, while an HTML
|
|
15
|
+
* sibling in the same test moves and scales correctly). An exploding stack needs
|
|
16
|
+
* sibling HTML planes and is a later slice; a tilting plate is this one.
|
|
17
|
+
*
|
|
18
|
+
* THE MODEL. For a plane rotated by `t` about its horizontal centre line under
|
|
19
|
+
* CSS `perspective: d`, a point `dy` below the plane's centre is drawn at
|
|
20
|
+
*
|
|
21
|
+
* scale(dy) = cos(t) * (d / (d - dy * sin(t)))^2
|
|
22
|
+
*
|
|
23
|
+
* — `cos(t)` for the foreshortening and the square for the perspective divide
|
|
24
|
+
* applying to both the glyph's height and its distance from the eye. Fitted
|
|
25
|
+
* against Chrome at `rotateX(30deg)`, `perspective: 1400px`:
|
|
26
|
+
*
|
|
27
|
+
* dy = -340 (near the top) model 0.6886 Chrome 0.6820 +0.97%
|
|
28
|
+
* dy = +360 (near the bottom) model 1.1404 Chrome 1.1260 +1.28%
|
|
29
|
+
*
|
|
30
|
+
* The model runs about 1% OPTIMISTIC, so `worstScale` rounds its answer down by
|
|
31
|
+
* 2% before anyone divides by it. Erring large costs a couple of type px; erring
|
|
32
|
+
* small ships a slide under the floor, which is the whole failure this exists to
|
|
33
|
+
* prevent.
|
|
34
|
+
*/
|
|
35
|
+
/** How a scene's plane is tilted. Degrees, and CSS `perspective` in px. */
|
|
36
|
+
export interface Pose {
|
|
37
|
+
/** Rotation about the horizontal centre line. Positive tips the top away. */
|
|
38
|
+
rotateX: number;
|
|
39
|
+
/** CSS `perspective`, in reference px. Smaller is a stronger effect. */
|
|
40
|
+
perspective: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The default a depth archetype gets when it asks for one.
|
|
44
|
+
*
|
|
45
|
+
* 12 degrees, not the 30 the spike reached for. The table in `worstScale`'s note
|
|
46
|
+
* is why: at 30 degrees a 40px audience floor needs 67px declared, which is more
|
|
47
|
+
* than a headline can spend and still fit its own line. At 12 it needs 48.7px,
|
|
48
|
+
* which archetypes can and do pay. Depth here is a lean, not a dive.
|
|
49
|
+
*/
|
|
50
|
+
export declare const DEFAULT_POSE: Pose;
|
|
51
|
+
/** Apparent scale at `dy` reference px below the plane's centre. */
|
|
52
|
+
export declare function scaleAt(pose: Pose, dy: number): number;
|
|
53
|
+
/**
|
|
54
|
+
* The smallest scale anywhere on a `height`-tall plane, already discounted.
|
|
55
|
+
*
|
|
56
|
+
* The smallest scale is at the TOP edge, furthest from the eye. The BOTTOM edge
|
|
57
|
+
* is the one that can swing through the eye entirely, and when it does the plane
|
|
58
|
+
* is not being tilted any more — it is being turned inside out — so the pose is
|
|
59
|
+
* refused rather than priced. Checking only the top would have returned a
|
|
60
|
+
* perfectly reasonable-looking floor for a pose that cannot be drawn.
|
|
61
|
+
*
|
|
62
|
+
* What it buys, at `perspective: 1400` on a 1080 canvas, AFTER the 2% discount:
|
|
63
|
+
*
|
|
64
|
+
* rotateX(6) 0.901 -> a 40px floor needs 44.4px declared
|
|
65
|
+
* rotateX(12) 0.821 -> 48.7px
|
|
66
|
+
* rotateX(18) 0.744 -> 53.8px
|
|
67
|
+
* rotateX(24) 0.669 -> 59.8px
|
|
68
|
+
* rotateX(30) 0.597 -> 67.0px
|
|
69
|
+
*
|
|
70
|
+
* That table is the tilt budget. Depth in this project is bounded by the type
|
|
71
|
+
* floor rather than by taste, and the bound is tighter than it looks.
|
|
72
|
+
*/
|
|
73
|
+
export declare function worstScale(pose: Pose, height: number): number;
|
|
74
|
+
/**
|
|
75
|
+
* The type floor an archetype must solve against to LAND on `floor` once tilted.
|
|
76
|
+
*
|
|
77
|
+
* Archetypes already solve against a floor — `stack` passes `MIN_FONT` into its
|
|
78
|
+
* own fit — so depth costs them one substitution rather than a second solver.
|
|
79
|
+
*/
|
|
80
|
+
export declare function tiltedFloor(pose: Pose, height: number, floor: number): number;
|
|
81
|
+
/**
|
|
82
|
+
* The CSS a tilted scene needs, scoped to its own id (invariant 3).
|
|
83
|
+
*
|
|
84
|
+
* `perspective` sits on the scene and the rotation on ONE part of it, named by
|
|
85
|
+
* `part`, because tilting a whole scene tilts its chrome too. Built that way
|
|
86
|
+
* first and it was obviously wrong the moment a frame was opened: the headline
|
|
87
|
+
* came out sheared, leaning like a mistake rather than a design, and the stack's
|
|
88
|
+
* probe marker — a straight rule with a dot under it — bent into a hook. The
|
|
89
|
+
* diagram is what gains by leaning; the words are what the audience reads.
|
|
90
|
+
*
|
|
91
|
+
* Nothing here animates: a pose is a static property of the beat, and a tilt that
|
|
92
|
+
* moved would put every label at a different apparent size on every frame while
|
|
93
|
+
* `apparent` only measures at the declared stops.
|
|
94
|
+
*/
|
|
95
|
+
export declare function depthCss(sid: string, pose: Pose, part: string): string;
|
package/dist/types/emit/kit.d.ts
CHANGED
|
@@ -285,6 +285,13 @@ export interface Scene {
|
|
|
285
285
|
* values.
|
|
286
286
|
*/
|
|
287
287
|
measure?: string[];
|
|
288
|
+
/**
|
|
289
|
+
* Vendored runtimes this scene's tweens need registered before its script
|
|
290
|
+
* runs — `"dsMorph"` for the equation morph. The shell loads a runtime only
|
|
291
|
+
* when some scene names it, so a deck that names none is byte-for-byte what
|
|
292
|
+
* it was.
|
|
293
|
+
*/
|
|
294
|
+
plugins?: readonly string[];
|
|
288
295
|
/**
|
|
289
296
|
* Hold points in seconds from the scene's start — where a presenter should
|
|
290
297
|
* pause. The shell converts these to absolute island fragment times.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keyed equation morph — TransformMatchingTex, for the DOM.
|
|
3
|
+
*
|
|
4
|
+
* Two KaTeX renderings in, one seek-evaluable plan out. `build()` runs once in
|
|
5
|
+
* the browser, inside the ready gate (Seam B), and bakes every number; the
|
|
6
|
+
* plan is then driven by a GSAP PLUGIN — `render(ratio)` is part of being
|
|
7
|
+
* seeked, so it fires under `suppressEvents` where an `onUpdate` would not
|
|
8
|
+
* (invariant 11). Nothing here runs on a callback.
|
|
9
|
+
*
|
|
10
|
+
* Four phases, kept separate on purpose:
|
|
11
|
+
* 1. LIFT — pull every ink-bearing leaf out of KaTeX's nested boxes into a
|
|
12
|
+
* flat absolutely-positioned overlay. Removes clipping, vlist
|
|
13
|
+
* stacking and transform-on-inline entirely.
|
|
14
|
+
* 2. GROUP — collect leaves into the units that move as one body. An author
|
|
15
|
+
* key makes a group; an unkeyed leaf is its own group.
|
|
16
|
+
* 3. MATCH — decide which group of A becomes which group of B.
|
|
17
|
+
* 4. PLAN — a similarity transform per group, as per-leaf segments.
|
|
18
|
+
*
|
|
19
|
+
* `evaluate()` turns a plan and a progress into styles. It is a pure function
|
|
20
|
+
* of its arguments — two seeks to one time write the same strings — which is
|
|
21
|
+
* what makes the render byte-identical across browser processes, measured on
|
|
22
|
+
* the spike this is ported from (`experiments/013-vocabulary/morph/`).
|
|
23
|
+
*
|
|
24
|
+
* Bundled to an IIFE by `scripts/build.mjs`, vendored beside GSAP by the CLI,
|
|
25
|
+
* and loaded only by a deck that has a morph in it. The pure halves — `group`,
|
|
26
|
+
* `match`, `plan`, `evaluate` — are exported for the tests, which is why the
|
|
27
|
+
* window registration at the bottom is guarded.
|
|
28
|
+
*/
|
|
29
|
+
/** The class prefix an author key is carried on: `\htmlClass{ds-k-<key>}{...}`. */
|
|
30
|
+
export declare const KEY_PREFIX = "ds-k-";
|
|
31
|
+
export interface Leaf {
|
|
32
|
+
el: HTMLElement;
|
|
33
|
+
key: string;
|
|
34
|
+
sig: string;
|
|
35
|
+
text: string;
|
|
36
|
+
/** Font size in layout px. */
|
|
37
|
+
fs: number;
|
|
38
|
+
/** Centre and box, in the host's own layout px. */
|
|
39
|
+
cx: number;
|
|
40
|
+
cy: number;
|
|
41
|
+
w: number;
|
|
42
|
+
h: number;
|
|
43
|
+
}
|
|
44
|
+
export interface Group {
|
|
45
|
+
ident: string;
|
|
46
|
+
key: string;
|
|
47
|
+
leaves: Leaf[];
|
|
48
|
+
cx: number;
|
|
49
|
+
cy: number;
|
|
50
|
+
w: number;
|
|
51
|
+
h: number;
|
|
52
|
+
fs: number;
|
|
53
|
+
text: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The unit that moves as one body.
|
|
57
|
+
*
|
|
58
|
+
* This is the whole quality argument of the file. Matching GLYPH BY GLYPH is
|
|
59
|
+
* what a naive reading of TransformMatchingTex suggests and it is visibly
|
|
60
|
+
* wrong: in `a^2+b^2=c^2 -> c^2-b^2=a^2` the three `2`s are interchangeable
|
|
61
|
+
* under any text-plus-class identity, so they pair left-to-right and stay put
|
|
62
|
+
* while their bases cross over. The audience sees the letters swap and the
|
|
63
|
+
* exponents refuse to follow, which asserts something false about the algebra.
|
|
64
|
+
*
|
|
65
|
+
* Manim does not avoid this by being cleverer — `MathTex("a^2", "+", "b^2",
|
|
66
|
+
* ...)` is the AUTHOR splitting the expression into the parts meant to
|
|
67
|
+
* survive. `\htmlClass{ds-k-<key>}{...}` is the same act, and it is the only
|
|
68
|
+
* mechanism here that produces a correct morph on an ambiguous pair.
|
|
69
|
+
*
|
|
70
|
+
* A group's identity is its key alone, not its contents, so `ds-k-exp` can
|
|
71
|
+
* carry `x^2` onto `x^3`: the body moves and its contents dissolve.
|
|
72
|
+
*/
|
|
73
|
+
export declare function group(items: Leaf[]): Group[];
|
|
74
|
+
export interface Match {
|
|
75
|
+
pairs: [number, number][];
|
|
76
|
+
dropA: number[];
|
|
77
|
+
addB: number[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Bucket by identity, then pair NEAREST FIRST within a bucket.
|
|
81
|
+
*
|
|
82
|
+
* Document order — the spike's rule — sends the wrong twin across the slide:
|
|
83
|
+
* `F = E(I), X = W(F)` has two `=` and its successor one, and pairing the
|
|
84
|
+
* first `=` with it flew a glyph 800px over the equation while the `=` sitting
|
|
85
|
+
* 40px from its destination faded out. Nearest-first is what a viewer expects
|
|
86
|
+
* of a symbol that did not move. Ties fall to document order, so it is still a
|
|
87
|
+
* pure function of the two layouts.
|
|
88
|
+
*/
|
|
89
|
+
export declare function match(A: Group[], B: Group[]): Match;
|
|
90
|
+
export type Prop = "x" | "y" | "s" | "o";
|
|
91
|
+
export type Ease = "none" | "power1.in" | "power1.out" | "power2.in" | "power2.out" | "power2.inOut";
|
|
92
|
+
/**
|
|
93
|
+
* One property of one leaf, over one slice of the morph.
|
|
94
|
+
*
|
|
95
|
+
* Times are FRACTIONS of the morph, not seconds: the plan is evaluated against
|
|
96
|
+
* the plugin tween's ratio, so `pace()` scaling the tween's `duration` scales
|
|
97
|
+
* all of this with it, and the plan never has to know how long it takes.
|
|
98
|
+
*/
|
|
99
|
+
export interface Step {
|
|
100
|
+
el: HTMLElement;
|
|
101
|
+
prop: Prop;
|
|
102
|
+
from: number;
|
|
103
|
+
to: number;
|
|
104
|
+
at: number;
|
|
105
|
+
dur: number;
|
|
106
|
+
ease: Ease;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* One similarity transform per matched group, expressed per leaf.
|
|
110
|
+
*
|
|
111
|
+
* A group's leaves move RIGIDLY: every leaf gets the same scale and the
|
|
112
|
+
* translation that the group's own centre-to-centre move implies for its
|
|
113
|
+
* position within the body. `a^2` therefore arrives with its exponent still
|
|
114
|
+
* attached, which is the whole reason groups exist.
|
|
115
|
+
*
|
|
116
|
+
* The B side is placed by the INVERSE transform — B's leaves start where they
|
|
117
|
+
* would sit inside A's box — so when the contents differ the two renderings
|
|
118
|
+
* dissolve into each other while sharing one trajectory.
|
|
119
|
+
*
|
|
120
|
+
* `arc` bows the path. Two glyphs swapping ends of an equation travel the same
|
|
121
|
+
* straight line in opposite directions and pile up in the middle, which reads
|
|
122
|
+
* as a collision rather than an exchange. The bow is two segments on `y` — out
|
|
123
|
+
* over the first half, back over the second — against one on `x` that runs
|
|
124
|
+
* the whole way.
|
|
125
|
+
*/
|
|
126
|
+
export declare function plan(A: Group[], B: Group[], m: Match, opt: {
|
|
127
|
+
arc?: boolean;
|
|
128
|
+
}): Step[];
|
|
129
|
+
export interface MorphPlan {
|
|
130
|
+
leaves: {
|
|
131
|
+
el: HTMLElement;
|
|
132
|
+
side: "a" | "b";
|
|
133
|
+
}[];
|
|
134
|
+
steps: Step[];
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Write the morph at progress `v` in [0, 1] into every leaf's style.
|
|
138
|
+
*
|
|
139
|
+
* Every leaf is written every time, from its resting state forward — A drawn,
|
|
140
|
+
* B hidden, nothing moved — so a cold seek to any progress produces the same
|
|
141
|
+
* strings as a walk to it. That is the property the capture depends on.
|
|
142
|
+
*/
|
|
143
|
+
export declare function evaluate(p: MorphPlan, v: number): void;
|
|
144
|
+
export interface Stats {
|
|
145
|
+
groupsA: number;
|
|
146
|
+
groupsB: number;
|
|
147
|
+
leavesA: number;
|
|
148
|
+
leavesB: number;
|
|
149
|
+
matched: number;
|
|
150
|
+
dropped: number;
|
|
151
|
+
added: number;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Build the morph inside `host`, which must already contain two rendered KaTeX
|
|
155
|
+
* roots marked `data-morph="a"` and `data-morph="b"`. Runs in `measure`, after
|
|
156
|
+
* fonts; the plan is kept for the plugin tween on the same host to find.
|
|
157
|
+
*/
|
|
158
|
+
export declare function build(host: HTMLElement, opt?: {
|
|
159
|
+
arc?: boolean;
|
|
160
|
+
}): Stats;
|
|
161
|
+
interface PluginState {
|
|
162
|
+
plan: MorphPlan;
|
|
163
|
+
end: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* `dsMorph` as a GSAP property: `tl.fromTo(host, { dsMorph: 0 }, { dsMorph: 1 })`.
|
|
167
|
+
*
|
|
168
|
+
* A plugin's `render` is called by the timeline as part of being seeked, so
|
|
169
|
+
* this runs under `suppressEvents` — the capture path — where an `onUpdate`
|
|
170
|
+
* does not. The tween's own ease must be "none": the plan carries its eases
|
|
171
|
+
* per segment, and a second ease on top would warp every one of them.
|
|
172
|
+
*/
|
|
173
|
+
export declare const DSMorphPlugin: {
|
|
174
|
+
name: string;
|
|
175
|
+
init(this: PluginState, target: Element, value: unknown): void;
|
|
176
|
+
render(ratio: number, d: PluginState): void;
|
|
177
|
+
};
|
|
178
|
+
export {};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -140,6 +140,30 @@ export declare const equationWalkParamsSchema: z.ZodObject<{
|
|
|
140
140
|
}>;
|
|
141
141
|
}, z.core.$strip>>;
|
|
142
142
|
}, z.core.$strip>;
|
|
143
|
+
/**
|
|
144
|
+
* Two equations from the source, the first becoming the second.
|
|
145
|
+
*
|
|
146
|
+
* `terms` are the parts that TRAVEL: each must occur in both equations, and is
|
|
147
|
+
* carried from its place in the first to its place in the second as one rigid
|
|
148
|
+
* body. Everything else dissolves. Which terms are keyed IS the animation —
|
|
149
|
+
* the same two strings are a glyph riot unkeyed and an exchange keyed.
|
|
150
|
+
*/
|
|
151
|
+
export declare const equationMorphParamsSchema: z.ZodObject<{
|
|
152
|
+
eyebrow: z.ZodOptional<z.ZodString>;
|
|
153
|
+
headline: z.ZodString;
|
|
154
|
+
fromId: z.ZodString;
|
|
155
|
+
toId: z.ZodString;
|
|
156
|
+
terms: z.ZodArray<z.ZodObject<{
|
|
157
|
+
tex: z.ZodString;
|
|
158
|
+
label: z.ZodString;
|
|
159
|
+
tone: z.ZodEnum<{
|
|
160
|
+
a: "a";
|
|
161
|
+
b: "b";
|
|
162
|
+
c: "c";
|
|
163
|
+
d: "d";
|
|
164
|
+
}>;
|
|
165
|
+
}, z.core.$strip>>;
|
|
166
|
+
}, z.core.$strip>;
|
|
143
167
|
export declare const dataTableParamsSchema: z.ZodObject<{
|
|
144
168
|
eyebrow: z.ZodOptional<z.ZodString>;
|
|
145
169
|
headline: z.ZodString;
|
|
@@ -274,6 +298,7 @@ export declare const stackParamsSchema: z.ZodObject<{
|
|
|
274
298
|
note: z.ZodOptional<z.ZodString>;
|
|
275
299
|
}, z.core.$strip>>;
|
|
276
300
|
note: z.ZodOptional<z.ZodString>;
|
|
301
|
+
tilt: z.ZodOptional<z.ZodNumber>;
|
|
277
302
|
}, z.core.$strip>;
|
|
278
303
|
export declare const splitCompareParamsSchema: z.ZodObject<{
|
|
279
304
|
eyebrow: z.ZodOptional<z.ZodString>;
|
|
@@ -423,6 +448,44 @@ export declare const beatSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
423
448
|
}, z.core.$strip>>>;
|
|
424
449
|
weight: z.ZodDefault<z.ZodNumber>;
|
|
425
450
|
seconds: z.ZodDefault<z.ZodNumber>;
|
|
451
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
452
|
+
narration: z.ZodOptional<z.ZodString>;
|
|
453
|
+
archetype: z.ZodLiteral<"equation-morph">;
|
|
454
|
+
params: z.ZodObject<{
|
|
455
|
+
eyebrow: z.ZodOptional<z.ZodString>;
|
|
456
|
+
headline: z.ZodString;
|
|
457
|
+
fromId: z.ZodString;
|
|
458
|
+
toId: z.ZodString;
|
|
459
|
+
terms: z.ZodArray<z.ZodObject<{
|
|
460
|
+
tex: z.ZodString;
|
|
461
|
+
label: z.ZodString;
|
|
462
|
+
tone: z.ZodEnum<{
|
|
463
|
+
a: "a";
|
|
464
|
+
b: "b";
|
|
465
|
+
c: "c";
|
|
466
|
+
d: "d";
|
|
467
|
+
}>;
|
|
468
|
+
}, z.core.$strip>>;
|
|
469
|
+
}, z.core.$strip>;
|
|
470
|
+
id: z.ZodString;
|
|
471
|
+
intent: z.ZodString;
|
|
472
|
+
inside: z.ZodOptional<z.ZodObject<{
|
|
473
|
+
beat: z.ZodString;
|
|
474
|
+
element: z.ZodString;
|
|
475
|
+
label: z.ZodOptional<z.ZodString>;
|
|
476
|
+
}, z.core.$strip>>;
|
|
477
|
+
claim: z.ZodOptional<z.ZodString>;
|
|
478
|
+
evidence: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
479
|
+
kind: z.ZodEnum<{
|
|
480
|
+
figure: "figure";
|
|
481
|
+
equation: "equation";
|
|
482
|
+
table: "table";
|
|
483
|
+
section: "section";
|
|
484
|
+
}>;
|
|
485
|
+
id: z.ZodString;
|
|
486
|
+
}, z.core.$strip>>>;
|
|
487
|
+
weight: z.ZodDefault<z.ZodNumber>;
|
|
488
|
+
seconds: z.ZodDefault<z.ZodNumber>;
|
|
426
489
|
}, z.core.$strip>, z.ZodObject<{
|
|
427
490
|
narration: z.ZodOptional<z.ZodString>;
|
|
428
491
|
archetype: z.ZodLiteral<"data-table">;
|
|
@@ -703,6 +766,7 @@ export declare const beatSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
703
766
|
note: z.ZodOptional<z.ZodString>;
|
|
704
767
|
}, z.core.$strip>>;
|
|
705
768
|
note: z.ZodOptional<z.ZodString>;
|
|
769
|
+
tilt: z.ZodOptional<z.ZodNumber>;
|
|
706
770
|
}, z.core.$strip>;
|
|
707
771
|
id: z.ZodString;
|
|
708
772
|
intent: z.ZodString;
|
|
@@ -905,6 +969,44 @@ export declare const storyboardSchema: z.ZodObject<{
|
|
|
905
969
|
}, z.core.$strip>>>;
|
|
906
970
|
weight: z.ZodDefault<z.ZodNumber>;
|
|
907
971
|
seconds: z.ZodDefault<z.ZodNumber>;
|
|
972
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
973
|
+
narration: z.ZodOptional<z.ZodString>;
|
|
974
|
+
archetype: z.ZodLiteral<"equation-morph">;
|
|
975
|
+
params: z.ZodObject<{
|
|
976
|
+
eyebrow: z.ZodOptional<z.ZodString>;
|
|
977
|
+
headline: z.ZodString;
|
|
978
|
+
fromId: z.ZodString;
|
|
979
|
+
toId: z.ZodString;
|
|
980
|
+
terms: z.ZodArray<z.ZodObject<{
|
|
981
|
+
tex: z.ZodString;
|
|
982
|
+
label: z.ZodString;
|
|
983
|
+
tone: z.ZodEnum<{
|
|
984
|
+
a: "a";
|
|
985
|
+
b: "b";
|
|
986
|
+
c: "c";
|
|
987
|
+
d: "d";
|
|
988
|
+
}>;
|
|
989
|
+
}, z.core.$strip>>;
|
|
990
|
+
}, z.core.$strip>;
|
|
991
|
+
id: z.ZodString;
|
|
992
|
+
intent: z.ZodString;
|
|
993
|
+
inside: z.ZodOptional<z.ZodObject<{
|
|
994
|
+
beat: z.ZodString;
|
|
995
|
+
element: z.ZodString;
|
|
996
|
+
label: z.ZodOptional<z.ZodString>;
|
|
997
|
+
}, z.core.$strip>>;
|
|
998
|
+
claim: z.ZodOptional<z.ZodString>;
|
|
999
|
+
evidence: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
1000
|
+
kind: z.ZodEnum<{
|
|
1001
|
+
figure: "figure";
|
|
1002
|
+
equation: "equation";
|
|
1003
|
+
table: "table";
|
|
1004
|
+
section: "section";
|
|
1005
|
+
}>;
|
|
1006
|
+
id: z.ZodString;
|
|
1007
|
+
}, z.core.$strip>>>;
|
|
1008
|
+
weight: z.ZodDefault<z.ZodNumber>;
|
|
1009
|
+
seconds: z.ZodDefault<z.ZodNumber>;
|
|
908
1010
|
}, z.core.$strip>, z.ZodObject<{
|
|
909
1011
|
narration: z.ZodOptional<z.ZodString>;
|
|
910
1012
|
archetype: z.ZodLiteral<"data-table">;
|
|
@@ -1185,6 +1287,7 @@ export declare const storyboardSchema: z.ZodObject<{
|
|
|
1185
1287
|
note: z.ZodOptional<z.ZodString>;
|
|
1186
1288
|
}, z.core.$strip>>;
|
|
1187
1289
|
note: z.ZodOptional<z.ZodString>;
|
|
1290
|
+
tilt: z.ZodOptional<z.ZodNumber>;
|
|
1188
1291
|
}, z.core.$strip>;
|
|
1189
1292
|
id: z.ZodString;
|
|
1190
1293
|
intent: z.ZodString;
|
|
@@ -1546,6 +1649,44 @@ export declare const packSchema: z.ZodObject<{
|
|
|
1546
1649
|
}, z.core.$strip>>>;
|
|
1547
1650
|
weight: z.ZodDefault<z.ZodNumber>;
|
|
1548
1651
|
seconds: z.ZodDefault<z.ZodNumber>;
|
|
1652
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1653
|
+
narration: z.ZodOptional<z.ZodString>;
|
|
1654
|
+
archetype: z.ZodLiteral<"equation-morph">;
|
|
1655
|
+
params: z.ZodObject<{
|
|
1656
|
+
eyebrow: z.ZodOptional<z.ZodString>;
|
|
1657
|
+
headline: z.ZodString;
|
|
1658
|
+
fromId: z.ZodString;
|
|
1659
|
+
toId: z.ZodString;
|
|
1660
|
+
terms: z.ZodArray<z.ZodObject<{
|
|
1661
|
+
tex: z.ZodString;
|
|
1662
|
+
label: z.ZodString;
|
|
1663
|
+
tone: z.ZodEnum<{
|
|
1664
|
+
a: "a";
|
|
1665
|
+
b: "b";
|
|
1666
|
+
c: "c";
|
|
1667
|
+
d: "d";
|
|
1668
|
+
}>;
|
|
1669
|
+
}, z.core.$strip>>;
|
|
1670
|
+
}, z.core.$strip>;
|
|
1671
|
+
id: z.ZodString;
|
|
1672
|
+
intent: z.ZodString;
|
|
1673
|
+
inside: z.ZodOptional<z.ZodObject<{
|
|
1674
|
+
beat: z.ZodString;
|
|
1675
|
+
element: z.ZodString;
|
|
1676
|
+
label: z.ZodOptional<z.ZodString>;
|
|
1677
|
+
}, z.core.$strip>>;
|
|
1678
|
+
claim: z.ZodOptional<z.ZodString>;
|
|
1679
|
+
evidence: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
1680
|
+
kind: z.ZodEnum<{
|
|
1681
|
+
figure: "figure";
|
|
1682
|
+
equation: "equation";
|
|
1683
|
+
table: "table";
|
|
1684
|
+
section: "section";
|
|
1685
|
+
}>;
|
|
1686
|
+
id: z.ZodString;
|
|
1687
|
+
}, z.core.$strip>>>;
|
|
1688
|
+
weight: z.ZodDefault<z.ZodNumber>;
|
|
1689
|
+
seconds: z.ZodDefault<z.ZodNumber>;
|
|
1549
1690
|
}, z.core.$strip>, z.ZodObject<{
|
|
1550
1691
|
narration: z.ZodOptional<z.ZodString>;
|
|
1551
1692
|
archetype: z.ZodLiteral<"data-table">;
|
|
@@ -1826,6 +1967,7 @@ export declare const packSchema: z.ZodObject<{
|
|
|
1826
1967
|
note: z.ZodOptional<z.ZodString>;
|
|
1827
1968
|
}, z.core.$strip>>;
|
|
1828
1969
|
note: z.ZodOptional<z.ZodString>;
|
|
1970
|
+
tilt: z.ZodOptional<z.ZodNumber>;
|
|
1829
1971
|
}, z.core.$strip>;
|
|
1830
1972
|
id: z.ZodString;
|
|
1831
1973
|
intent: z.ZodString;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type floor, measured on the rendered frame instead of read off the source.
|
|
3
|
+
*
|
|
4
|
+
* `typefloor.ts` scans declared sizes and says so plainly in its own header:
|
|
5
|
+
* "text shrunk by a `scale` below 1 at a hold reads as its unscaled size". That
|
|
6
|
+
* hole is not hypothetical and it is not only about `scale`. Measured in Chrome
|
|
7
|
+
* at `rotateX(30deg)` under `perspective: 1400px`, a run declaring 46px renders
|
|
8
|
+
* 31.4 apparent px near the top of the plane — invariant 5 broken by 8.6px, with
|
|
9
|
+
* `lint`, `check`, `drift` and the declared-size scan all green, because not one
|
|
10
|
+
* of them looks at how big the glyph actually came out.
|
|
11
|
+
*
|
|
12
|
+
* That matters now because Gap 2 wants depth, and every way of tilting a plane
|
|
13
|
+
* shrinks the type on the far half of it. A depth archetype built against a floor
|
|
14
|
+
* that cannot see projection would ship unreadable slides that pass. So the gate
|
|
15
|
+
* comes first and the archetype second.
|
|
16
|
+
*
|
|
17
|
+
* HOW APPARENT SIZE IS MEASURED. For an SVG text node, `getBBox()` is the
|
|
18
|
+
* untransformed box in user units and `getClientRects()` is the painted box in
|
|
19
|
+
* device px, so their ratio is the TOTAL scale between the two — the element's
|
|
20
|
+
* own transform, every ancestor transform, the 3D projection's per-point
|
|
21
|
+
* perspective divide, and the stage zoom, all of it, without having to know which
|
|
22
|
+
* of them applied. Measured against the three cases that matter:
|
|
23
|
+
*
|
|
24
|
+
* flat, declared 46 ratio 1.000 -> 46.0 apparent px
|
|
25
|
+
* tilted 30deg, near plane top 0.682 -> 31.4
|
|
26
|
+
* tilted 30deg, near plane bottom 1.126 -> 51.8 (magnified, not a fault)
|
|
27
|
+
* `scale(0.6)` at a hold 0.600 -> 27.6
|
|
28
|
+
*
|
|
29
|
+
* `getScreenCTM()` was tried first and rejected: it returns 0.673 for BOTH the
|
|
30
|
+
* top and bottom runs above, because it carries the affine part and drops the
|
|
31
|
+
* perspective divide that makes those two differ by 23 apparent px.
|
|
32
|
+
*
|
|
33
|
+
* WHY IT DIVIDES BY THE STAGE. A deck legitimately scales its whole stage —
|
|
34
|
+
* `zoomOf`/`REF_PULL` — and in portrait 40 reference px is SUPPOSED to land at 30
|
|
35
|
+
* canvas px, which `typefloor.ts` calls "the whole argument of REF_PULL and not a
|
|
36
|
+
* violation". Dividing the run's own ratio by the scene's gives apparent size
|
|
37
|
+
* back in REFERENCE px, so this gate agrees with the declared one on every deck
|
|
38
|
+
* that does nothing clever, and only diverges where something shrank one element
|
|
39
|
+
* relative to its own scene.
|
|
40
|
+
*/
|
|
41
|
+
import type { Finding } from "../types.js";
|
|
42
|
+
/** One text run, as the frame actually drew it. */
|
|
43
|
+
export interface ApparentRun {
|
|
44
|
+
/** First 40 characters, for the message. */
|
|
45
|
+
text: string;
|
|
46
|
+
/** `font-size` as declared, in px. */
|
|
47
|
+
declared: number;
|
|
48
|
+
/** Painted height over untransformed height, including the stage's own zoom. */
|
|
49
|
+
ratio: number;
|
|
50
|
+
/** Its own computed opacity, multiplied down the ancestors that carry one. */
|
|
51
|
+
opacity: number;
|
|
52
|
+
}
|
|
53
|
+
/** What `measureApparent` hands back for one instant. */
|
|
54
|
+
export interface ApparentStop {
|
|
55
|
+
sid: string;
|
|
56
|
+
t: number;
|
|
57
|
+
runs: ApparentRun[];
|
|
58
|
+
/** The scene's own painted-over-declared scale, the baseline runs divide by. */
|
|
59
|
+
stage: number;
|
|
60
|
+
/**
|
|
61
|
+
* True at a declared stop, where the frame has arrived and everything drawn is
|
|
62
|
+
* being read. False at a sampled midpoint, where a run may be part-way through
|
|
63
|
+
* its own entrance — see `SETTLED_OPACITY`.
|
|
64
|
+
*/
|
|
65
|
+
settled: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Serialised into the page: every visible SVG text run's apparent scale.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately the same walk as `collectSvgTextRuns` in overprint.ts — same
|
|
71
|
+
* visibility rules, same "only inside an `<svg>`" restriction — because two gates
|
|
72
|
+
* that disagree about which text exists are two gates that cannot be reconciled
|
|
73
|
+
* when they disagree about a deck.
|
|
74
|
+
*/
|
|
75
|
+
export declare function collectApparent(sid: string): {
|
|
76
|
+
runs: ApparentRun[];
|
|
77
|
+
stage: number;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Apparent size in reference px: what the run declared, times how much the frame
|
|
81
|
+
* shrank or grew it relative to its own scene.
|
|
82
|
+
*/
|
|
83
|
+
export declare function apparentPx(run: ApparentRun, stage: number): number;
|
|
84
|
+
/**
|
|
85
|
+
* How opaque a run must be, at a sampled midpoint, to count as text being read.
|
|
86
|
+
*
|
|
87
|
+
* At a declared stop the frame has arrived and everything drawn counts. Between
|
|
88
|
+
* stops it has not: `annotated-figure` enters its labels from `scale: 0.97`, so
|
|
89
|
+
* a run caught half way through its own entrance is BOTH under the floor and
|
|
90
|
+
* fading in, and failing a build for it would be crying wolf about a frame no
|
|
91
|
+
* one reads. A run at 0.95 opacity is not entering any more; it has arrived.
|
|
92
|
+
*/
|
|
93
|
+
export declare const SETTLED_OPACITY = 0.95;
|
|
94
|
+
/**
|
|
95
|
+
* The times to sample BETWEEN the declared stops.
|
|
96
|
+
*
|
|
97
|
+
* `fidelity` measures at stops because that is where a frame has settled, and
|
|
98
|
+
* the apparent floor inherited that — which left exactly the hole it was built
|
|
99
|
+
* to close, one interval over: text scaled DOWN between two stops is invisible
|
|
100
|
+
* to a gate that only looks at the stops. A camera pulling back, or an exit that
|
|
101
|
+
* shrinks a label, would ship.
|
|
102
|
+
*
|
|
103
|
+
* Midway between consecutive stops of the SAME scene. Not across a scene
|
|
104
|
+
* boundary, where the midpoint lands in a cross-fade between two compositions
|
|
105
|
+
* and belongs to neither. Costs a seek and a DOM read each — no screenshot,
|
|
106
|
+
* which is what makes doubling the sample count affordable.
|
|
107
|
+
*/
|
|
108
|
+
export declare function midpoints(stops: readonly {
|
|
109
|
+
sid: string;
|
|
110
|
+
t: number;
|
|
111
|
+
}[]): {
|
|
112
|
+
sid: string;
|
|
113
|
+
t: number;
|
|
114
|
+
}[];
|
|
115
|
+
/**
|
|
116
|
+
* Runs the frame drew below the floor, whatever shrank them.
|
|
117
|
+
*
|
|
118
|
+
* An ERROR, not a warning, and for the same reason `typefloor` is: text the
|
|
119
|
+
* audience cannot read is not a matter of degree. The tolerance is a tenth of a
|
|
120
|
+
* pixel, which is rasteriser noise rather than a grace margin — a deck that wants
|
|
121
|
+
* 40px declares 40px and measures 40.0, as the flat control does exactly.
|
|
122
|
+
*/
|
|
123
|
+
export declare function gradeApparent(stops: readonly ApparentStop[], floor?: number): Finding[];
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
*
|
|
29
29
|
* WHAT IT DOES NOT SEE, stated because a gate that overclaims is the thing this
|
|
30
30
|
* file was written to stop. It reads declared sizes, so text shrunk by a
|
|
31
|
-
* `scale` below 1 at a hold reads as its unscaled size
|
|
31
|
+
* `scale` below 1 at a hold reads as its unscaled size — `apparent.ts` is the
|
|
32
|
+
* gate that closes exactly that, by measuring the painted glyph instead, and it
|
|
33
|
+
* catches a 3D projection the same way; and it says nothing about
|
|
32
34
|
* whether the text FITS — `container_overflow` and `text_occluded` in
|
|
33
35
|
* `hyperframes check` are the gates for that, and `check.ts` already grades them
|
|
34
36
|
* up to errors.
|