@impractical-ai/motion 0.1.11 → 0.1.13
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 +12 -0
- package/assets/engine.d.ts +1015 -682
- package/assets/impractical/SKILL.md +12 -0
- package/dist/{api-FMMG3W6J.js → api-4YQKINQI.js} +2 -2
- package/dist/{chunk-VTHAZQEV.js → chunk-GAHKKLF3.js} +1 -1
- package/dist/{chunk-HGA2O3JM.js → chunk-PTYSKJ2Y.js} +4 -3
- package/dist/{chunk-SN6SLPIC.js → chunk-ZCTJTQB7.js} +2 -2
- package/dist/{commands-EDRH2XMY.js → commands-NWJK7ANI.js} +3 -3
- package/dist/index.js +7 -6
- package/dist/stdio-PLWZDKDA.js +72714 -0
- package/package.json +3 -2
- package/dist/stdio-VBPVT2KS.js +0 -36204
package/assets/engine.d.ts
CHANGED
|
@@ -1,9 +1,191 @@
|
|
|
1
1
|
// Impractical Motion — public engine surface v1.1.
|
|
2
2
|
//
|
|
3
3
|
// A scene is two files:
|
|
4
|
-
// App.tsx the stage markup (plain React + CSS
|
|
4
|
+
// App.tsx the stage markup (plain React + CSS) — the SET, not a
|
|
5
|
+
// replica of the product: what the shots need on screen
|
|
5
6
|
// direction.ts the choreography — `export default (ctx: DirectionContext) => {...}`
|
|
6
7
|
//
|
|
8
|
+
// COMPOSE THE SHOTS BEFORE YOU BUILD THE SET. Reconstruct the product first
|
|
9
|
+
// and you get a slideshow of panels. List the shots first — the set is what
|
|
10
|
+
// they need, and their components ARE the build list for App.tsx. Per shot:
|
|
11
|
+
// the CLAIM it proves, WHAT IS ON SCREEN, the FRAMING —
|
|
12
|
+
// in-situ a crop of the product, when where-it-lives is the point
|
|
13
|
+
// composed elements arranged for this shot alone, at a scale where the
|
|
14
|
+
// change reads as total; need not exist in the product. A
|
|
15
|
+
// picker beside a miniature of what it recolours beats either
|
|
16
|
+
// one filmed in place.
|
|
17
|
+
// full-frame a claim card; type owns the frame
|
|
18
|
+
// A dense product at 1920x1080 puts body text near 11px, so a wide shot of
|
|
19
|
+
// one establishes nothing. Frame the smallest thing that carries the meaning;
|
|
20
|
+
// widen only when it means something. If the film claims coherence, ONE shot
|
|
21
|
+
// shows the product whole.
|
|
22
|
+
//
|
|
23
|
+
// THE KIT LIBRARY. The types below are the ENGINE — the floor, not the
|
|
24
|
+
// craft. Most of the house craft is in the kit library, which is NOT in this
|
|
25
|
+
// file: call install_kits to write it into your draft, then import from
|
|
26
|
+
// './kit/<name>'. Every kit below is already written and tuned against
|
|
27
|
+
// reference footage; hand-rolling one is how a scene ends up looking it.
|
|
28
|
+
//
|
|
29
|
+
// HOW A FILM IS WRITTEN. The neutral film sequencer owns ordering; World
|
|
30
|
+
// Camera owns only named-object framing, depth, and camera gestures.
|
|
31
|
+
// const film = createFilmSequencer({ tl })
|
|
32
|
+
// const world = createWorldCamera({ tl, stage, camera, register: 'promo' })
|
|
33
|
+
// const copy = createTextPlans(text)
|
|
34
|
+
// const result = film.do(manner.realize('[data-obj="RESULT_ROWS"]'))
|
|
35
|
+
// film.do(world.camera.whipTo('RESULT_ROWS').landWith(result))
|
|
36
|
+
// film.do(copy.exit('[data-claim]', 'rise'))
|
|
37
|
+
// film.cut(editorial.cut(layers, next), world.camera.cutTo('NEXT'))
|
|
38
|
+
// Camera verbs: cutTo / moveTo / zoomTo / whipTo / departTo / angleTo /
|
|
39
|
+
// focusOn. The id is data-obj. Every verb centers that object's authored
|
|
40
|
+
// bounding box and maximizes it inside deterministic padding. Optional
|
|
41
|
+
// framing words are tight / normal / loose — breathing room, not zoom.
|
|
42
|
+
// film.do advances to settle, film.together shares a start, and
|
|
43
|
+
// film.at(handle.impact) attaches a consequence to its cause. film.cut
|
|
44
|
+
// waits for every outgoing plan END; coveredCut(handle, marker, reason, ...)
|
|
45
|
+
// is the explicit exception for an intentionally carried seam. Content
|
|
46
|
+
// manner and editorial cuts stay separate from World Camera. The raw camera
|
|
47
|
+
// in DirectionContext below
|
|
48
|
+
// (framing/full/crop/set/move) is the ESCAPE HATCH — measured or skeleton
|
|
49
|
+
// work that needs an exact pose at an exact time — not the default path.
|
|
50
|
+
//
|
|
51
|
+
// kit/kits — Side-effect import: the draft-side working-views engine (Studio
|
|
52
|
+
// reveal verbs on the currently deployed engine). Every kit imports this
|
|
53
|
+
// module,
|
|
54
|
+
// kit/reveal — DRAFT-SIDE working-views engine — GENERATED from
|
|
55
|
+
// src/stage/reveal.ts. Do not hand-edit.
|
|
56
|
+
// kit/actions — Actions — cursor, click, drag, scroll, and UI state
|
|
57
|
+
// primitives [exports: createActionsKit, createActionPlans]
|
|
58
|
+
// kit/ensemble — Ensemble choreography — words that move MANY elements as one
|
|
59
|
+
// system. [exports: createEnsembleKit]
|
|
60
|
+
// kit/looks — Looks — scene lighting, shading, ambient backdrops, and the
|
|
61
|
+
// hero shot [exports: createLooksKit]
|
|
62
|
+
// kit/sentence — The live-sentence kit: a sentence is a stateful object, not
|
|
63
|
+
// a static block. [exports: createSentenceKit]
|
|
64
|
+
// kit/morphkit — Universal shape morph — the shared-element architecture (how
|
|
65
|
+
// Framer Motion's layoutId / iOS matchedGeometryEffect / Flutter Hero do
|
|
66
|
+
// it): [exports: createMorphKit]
|
|
67
|
+
// kit/morphtree — GENERATED from src/stage/morph-tree.ts by
|
|
68
|
+
// build:morph-runtime. Do not edit. [exports: createMorphTreeKit]
|
|
69
|
+
// kit/highlight — Highlight — annotation primitives for pre-existing
|
|
70
|
+
// components [exports: createHighlightKit]
|
|
71
|
+
// kit/impact — Impact moves — the "slam": type or a card that hits the frame
|
|
72
|
+
// hard, or tips away out of it. [exports: createImpactKit]
|
|
73
|
+
// kit/streamkit — Streaming states — the choreographed WAITING that AI
|
|
74
|
+
// products live in. [exports: createStreamKit]
|
|
75
|
+
// kit/editorial — Editorial — cuts, jumps, and the camera-solved match cut
|
|
76
|
+
// [exports: createEditorialKit, createEditorialPlans]
|
|
77
|
+
// kit/depthmath — Depth math — pure, DOM-free, tested in isolation
|
|
78
|
+
// kit/depthkit — Depth — the stage as a physical world: layers, tilt, glass,
|
|
79
|
+
// light [exports: createDepthKit]
|
|
80
|
+
// kit/anchor — Anchor — never guess where another element is [exports:
|
|
81
|
+
// createAnchorKit]
|
|
82
|
+
// kit/atelier — Atelier — the house motion studies, callable from a film
|
|
83
|
+
// [exports: createAtelierKit]
|
|
84
|
+
// kit/cursor-geometry — Cursor geometry — project authored anchors into the
|
|
85
|
+
// cursor's actual plane.
|
|
86
|
+
// kit/film — Film — semantic sequencing without timestamp arithmetic
|
|
87
|
+
// [exports: createFilmSequencer]
|
|
88
|
+
// kit/manner — Manner kit — component-property motion (v2, the coverage
|
|
89
|
+
// bundle) [exports: createMannerKit, createMannerPlans]
|
|
90
|
+
// kit/morph-surface — Continuous vector surfaces with recursive content
|
|
91
|
+
// matching, scheduled by Film. [exports: createMorphSurfacePlans]
|
|
92
|
+
// kit/profile — Motion profile — loader, types, and the timing vocabulary
|
|
93
|
+
// kit/text — Text plans — full-span text timing for safe film boundaries
|
|
94
|
+
// [exports: createTextPlans]
|
|
95
|
+
// kit/world — World Camera — named subjects, spatial continuity, depth, and
|
|
96
|
+
// targeting [exports: createWorldCamera, createWorldKit]
|
|
97
|
+
//
|
|
98
|
+
// ─── MARKUP: what an element declares about itself ───────────────────────
|
|
99
|
+
//
|
|
100
|
+
// App.tsx is not inert scenery. The kits read ATTRIBUTES off the elements
|
|
101
|
+
// you write, so these are decisions made WHILE writing the markup: an
|
|
102
|
+
// element declares its identity, how it arrives, and how closely it can be
|
|
103
|
+
// looked at. The direction then only says WHEN. An attribute you never
|
|
104
|
+
// write is motion you never get, and it fails QUIET — manner.realize()
|
|
105
|
+
// over elements with no data-manner animates nothing.
|
|
106
|
+
//
|
|
107
|
+
// IDENTITY — what the camera, the profile, and the kits can name
|
|
108
|
+
// data-obj="rows" — this element IS an object and keeps that identity for
|
|
109
|
+
// the whole film. Camera poses name objects, never coordinates. Ids must
|
|
110
|
+
// be unique.
|
|
111
|
+
// data-part — a part of its object — what assemble() rises in sequence.
|
|
112
|
+
// Unmarked, its direct children are the parts.
|
|
113
|
+
// data-el — a member of a manner cascade — what 'cascade' births one at a
|
|
114
|
+
// time. Unmarked, the root's direct children are the members.
|
|
115
|
+
// data-product-ui — this element is the product shell. Legacy world.beat
|
|
116
|
+
// fits and camera angles use it as a coherent plane; camera-only target
|
|
117
|
+
// framing always measures the specifically named object.
|
|
118
|
+
// data-detail="fine" — legacy world.beat declaration that content survives
|
|
119
|
+
// a deep closeup. Camera-only target framing has no magnification cap or
|
|
120
|
+
// waiver. The only legal value is 'fine'.
|
|
121
|
+
//
|
|
122
|
+
// HOW IT ARRIVES — a property of the ELEMENT, not of the cue
|
|
123
|
+
// data-manner="<word>" — the element declares its own entrance; the
|
|
124
|
+
// direction only says WHEN, with manner.realize(sel, at). A
|
|
125
|
+
// space-separated list is legal and the direction names which fires.
|
|
126
|
+
// Without this attribute manner.realize() silently does nothing. The
|
|
127
|
+
// words:
|
|
128
|
+
// cascade — children write on, metronome births + settle
|
|
129
|
+
// accent-pop — accent pop onto an already-born element (never past rest
|
|
130
|
+
// size)
|
|
131
|
+
// pop — the item pop — the overshoot entrance
|
|
132
|
+
// slide-home — slides home whole on the measured entry deceleration…
|
|
133
|
+
// complete — appears COMPLETE in one frame — no fade
|
|
134
|
+
// breath — the surface takes one small breath
|
|
135
|
+
// grow — grows downward clipped to reveal its rows
|
|
136
|
+
// zoom-out — the SURFACE zooms itself out over a static world — a
|
|
137
|
+
// subject-carried device…
|
|
138
|
+
// snap — a hard ui-state change in ONE frame — never a fade. TWO FORMS,
|
|
139
|
+
// discriminated…
|
|
140
|
+
// spark — a small burst off the element's corner
|
|
141
|
+
// doodle — a rigid motif flies a measured arc (data-arc names it)
|
|
142
|
+
// reveal — an outcome row snaps on whole and settles up
|
|
143
|
+
// slide — peers slide one pitch to make room
|
|
144
|
+
// swap — covered-cut content arrives low and settles while the chrome holds
|
|
145
|
+
// still
|
|
146
|
+
// reorg — reorganized content slides up settling
|
|
147
|
+
// data-speed="quick" — 'snap' | 'quick' | 'slow'. On 'cascade' it picks the
|
|
148
|
+
// birth cadence class; elsewhere it scales the profile duration.
|
|
149
|
+
// Stepped-table manners take no speed (an error, not a no-op) and a bare
|
|
150
|
+
// number is always an error.
|
|
151
|
+
// data-anchor="50% 50%" — transform-origin for the scale-borne manners.
|
|
152
|
+
// data-late — on a cascade member: excluded from the cascade — the kit
|
|
153
|
+
// hides it and its own later cue reveals it.
|
|
154
|
+
// data-display-birth="flex" — members are display:none until born, then
|
|
155
|
+
// take this display — for members whose arrival displaces layout.
|
|
156
|
+
// data-visible-from="next-frame" — births render no earlier than one frame
|
|
157
|
+
// after the cascade starts: the mid-birth cut mask. The only value —
|
|
158
|
+
// times belong to the direction.
|
|
159
|
+
// data-snap-<property>="value" — for 'snap' on an element that STAYS on
|
|
160
|
+
// screen: the properties it lands on, set in one frame, e.g.
|
|
161
|
+
// data-snap-background="#5a5a5a". An element that IS the state visual
|
|
162
|
+
// snaps on whole and needs none.
|
|
163
|
+
// data-arc="<name>" — which measured arc a 'doodle' flies.
|
|
164
|
+
// data-from="left" — 'left' | 'right' | 'above' | 'below' — the side a
|
|
165
|
+
// 'slide' comes from (default 'left').
|
|
166
|
+
// data-focus — on a descendant: what a 'zoom-out' opens magnified on before
|
|
167
|
+
// pulling back.
|
|
168
|
+
//
|
|
169
|
+
// STAGE ROLES
|
|
170
|
+
// data-backdrop — you own what the backdrop IS — any markup, animated on
|
|
171
|
+
// the timeline; World Camera owns that it COVERS and automatically keeps
|
|
172
|
+
// one authored backdrop outside camera-space.
|
|
173
|
+
// data-frame-overlay — screen-fixed material above the camera world, such
|
|
174
|
+
// as continuous grain, vignette, scanlines, or letterbox bars. World
|
|
175
|
+
// Camera hoists it out of camera-space and keeps it viewport-sized.
|
|
176
|
+
// data-morph="name" — pairs a sub-component with the same name on the other
|
|
177
|
+
// side of a morph. Untagged children auto-pair by text.
|
|
178
|
+
// data-block — the blend-isolation host: filters and blend modes composite
|
|
179
|
+
// against this ancestor instead of leaking under the stage.
|
|
180
|
+
//
|
|
181
|
+
// ATELIER AND SCENE LIFECYCLE
|
|
182
|
+
// data-scene="id" — Register with world.scene(id); travel between
|
|
183
|
+
// registered scenes retires the scene being left. Re-enter it explicitly
|
|
184
|
+
// before revisiting.
|
|
185
|
+
// data-cadence — Optional manner/cadence grouping marker.
|
|
186
|
+
// data-atelier="id" — Wrap the skeleton returned by atelier_study; preserve
|
|
187
|
+
// its nested selector contract.
|
|
188
|
+
//
|
|
7
189
|
// The timeline is created PAUSED and rendered by seeking frame by frame, so
|
|
8
190
|
// every animation must be a function of timeline position alone. Never use
|
|
9
191
|
// Date.now(), Math.random(), setTimeout, CSS animations, or autoplaying video —
|
|
@@ -25,24 +207,24 @@ export type TransitionDirection = 'left' | 'right' | 'up' | 'down'
|
|
|
25
207
|
|
|
26
208
|
export type TransitionOpts = {
|
|
27
209
|
color?: string
|
|
28
|
-
|
|
210
|
+
/** Travel direction of the sweep (wipe/pixels). Default 'left'. */
|
|
29
211
|
direction?: TransitionDirection
|
|
30
212
|
duration?: number
|
|
31
213
|
ease?: string
|
|
32
|
-
|
|
214
|
+
/** Pixel cell size in px (pixels style). Default: frame height / 9. */
|
|
33
215
|
cell?: number
|
|
34
|
-
|
|
216
|
+
/** Iris center as fractions of the frame, e.g. `{ x: 0.7, y: 0.4 }`. */
|
|
35
217
|
at?: { x?: number; y?: number }
|
|
36
218
|
}
|
|
37
219
|
|
|
38
220
|
export type TransitionResult = {
|
|
39
221
|
duration: number
|
|
40
|
-
|
|
222
|
+
/** The overlay's root element — e.g. to hide it manually mid-timeline. */
|
|
41
223
|
root: HTMLElement
|
|
42
224
|
}
|
|
43
225
|
|
|
44
226
|
/** What the camera dives through. Every mode computes its own geometry —
|
|
45
|
-
|
|
227
|
+
* never hand-tune margins or wrap glyphs in spans to fake these. */
|
|
46
228
|
export type ThroughVia =
|
|
47
229
|
| 'surface' // the element's face: its rect covers the frame (default)
|
|
48
230
|
| 'glyph' // one character's COUNTER (the enclosed hole) covers the frame
|
|
@@ -50,99 +232,99 @@ export type ThroughVia =
|
|
|
50
232
|
| 'anchor'
|
|
51
233
|
|
|
52
234
|
export type ThroughOpts = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
235
|
+
/** Dive mode. Default 'surface'. 'glyph' needs `char` (or `charIndex`),
|
|
236
|
+
* 'word' needs `word`; both address plain text — no wrapper spans.
|
|
237
|
+
* 'anchor' dives toward the element and, at the cut, match-frames the
|
|
238
|
+
* element matching the SAME selector in the post-swap world (its twin),
|
|
239
|
+
* so the subject persists while everything around it changes. */
|
|
58
240
|
via?: ThroughVia
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
241
|
+
/** glyph mode: the character to dive through — first occurrence in the
|
|
242
|
+
* target's text. The dive passes through its enclosed counter (o, e, a,
|
|
243
|
+
* 0…); characters without a hole throw with a diagnostic. */
|
|
62
244
|
char?: string
|
|
63
|
-
|
|
245
|
+
/** glyph mode: 0-based index into the target's text, instead of `char`. */
|
|
64
246
|
charIndex?: number
|
|
65
|
-
|
|
247
|
+
/** word mode: the word to dive through — first occurrence. */
|
|
66
248
|
word?: string
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
249
|
+
/** Takeover color. Defaults: surface/anchor — the target's effective
|
|
250
|
+
* background (first non-transparent backgroundColor walking up from the
|
|
251
|
+
* target); glyph/word — the surface BEHIND the text (the walk starts at
|
|
252
|
+
* the text element's parent), i.e. what actually shows through the
|
|
253
|
+
* counters. */
|
|
72
254
|
color?: string
|
|
73
|
-
|
|
255
|
+
/** Dive duration in seconds. Default 0.5. */
|
|
74
256
|
duration?: number
|
|
75
|
-
|
|
257
|
+
/** Dive ease. Default 'power3.in' (accelerates into the surface). */
|
|
76
258
|
ease?: string
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
259
|
+
/** Cover overshoot: 1.0 = the dive geometry (element rect, glyph counter,
|
|
260
|
+
* or word rect per `via`) exactly covers the frame at the cut. Default
|
|
261
|
+
* 1.15, so a few px of drift can never expose an edge. Override only for
|
|
262
|
+
* taste — every mode measures its own geometry, so no mode NEEDS a
|
|
263
|
+
* hand-tuned margin. */
|
|
82
264
|
margin?: number
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
265
|
+
/** The world swap, applied as seek-safe `tl.set`s at the exact cut moment
|
|
266
|
+
* while the frame is fully covered: `hide` gets autoAlpha 0, `show` gets
|
|
267
|
+
* autoAlpha 1. For anything richer, place your own `tl.set`s at the
|
|
268
|
+
* returned `cutAt` time. */
|
|
87
269
|
swap?: { hide?: gsap.TweenTarget; show?: gsap.TweenTarget }
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
270
|
+
/** Element in the post-swap world to ARRIVE from: at the cut the camera
|
|
271
|
+
* snaps to this element's own cover framing (measured after the swap), so
|
|
272
|
+
* releasing the overlay reveals it filling the frame, and the pull-out
|
|
273
|
+
* starts inside the new world — the match-frame landing. Omit to pull out
|
|
274
|
+
* from the dive's own framing. */
|
|
93
275
|
land?: string | Element
|
|
94
|
-
|
|
95
|
-
|
|
276
|
+
/** Framing the pull-out settles on. Default the full frame
|
|
277
|
+
* `{ scale: 1, x: 0, y: 0 }`. */
|
|
96
278
|
settle?: { scale: number; x: number; y: number }
|
|
97
|
-
|
|
279
|
+
/** Pull-out duration. Default 1.0. */
|
|
98
280
|
outDuration?: number
|
|
99
|
-
|
|
281
|
+
/** Pull-out ease. Default 'expo.out'. */
|
|
100
282
|
outEase?: string
|
|
101
283
|
}
|
|
102
284
|
|
|
103
285
|
export type ThroughResult = {
|
|
104
|
-
|
|
286
|
+
/** Full span: dive + pull-out. */
|
|
105
287
|
duration: number
|
|
106
|
-
|
|
107
|
-
|
|
288
|
+
/** Absolute timeline time of the cut — the frame is guaranteed fully
|
|
289
|
+
* covered here; schedule extra world-swap `tl.set`s at this time. */
|
|
108
290
|
cutAt: number
|
|
109
|
-
|
|
291
|
+
/** The takeover overlay's root element. */
|
|
110
292
|
root: HTMLElement
|
|
111
293
|
}
|
|
112
294
|
|
|
113
295
|
export type TransitionKit = {
|
|
114
|
-
|
|
115
|
-
|
|
296
|
+
/** Cover the frame — put this at the end of a clip. Appends to the
|
|
297
|
+
* timeline's end when no position is given. */
|
|
116
298
|
out: (
|
|
117
299
|
style: TransitionStyle,
|
|
118
300
|
opts?: TransitionOpts,
|
|
119
301
|
position?: gsap.Position
|
|
120
302
|
) => TransitionResult
|
|
121
|
-
|
|
122
|
-
|
|
303
|
+
/** Start covered and reveal the scene — put this at a clip's start
|
|
304
|
+
* (position 0). Continues the same travel direction as its `out`. */
|
|
123
305
|
in: (
|
|
124
306
|
style: TransitionStyle,
|
|
125
307
|
opts?: TransitionOpts,
|
|
126
308
|
position?: gsap.Position
|
|
127
309
|
) => TransitionResult
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
310
|
+
/** The go-through: dive the camera INTO something until its color takes
|
|
311
|
+
* over the whole frame, cut worlds under the cover, then pull out into the
|
|
312
|
+
* new world. NEVER hand-roll this (a magic-scale zoom + a hand-timed
|
|
313
|
+
* `tl.set` swap + a re-aim) — through() computes the exact cover scale for
|
|
314
|
+
* its dive geometry, measures it in its state AT THE DIVE'S START TIME
|
|
315
|
+
* (entrances, moves, and tilts since build are all accounted for), and
|
|
316
|
+
* snaps a full-frame overlay in the right color at the cut so the
|
|
317
|
+
* takeover is guaranteed to the pixel. Four dive modes via `opts.via`:
|
|
318
|
+
* 'surface' (default — the element's face), 'glyph' (through one
|
|
319
|
+
* character's enclosed counter: `{ via: 'glyph', char: 'o' }` on plain
|
|
320
|
+
* text, no wrapper span — the hole is measured from the actual glyph
|
|
321
|
+
* outline, so the margin is computed, never tuned), 'word' (`{ via:
|
|
322
|
+
* 'word', word: 'world' }` — the word's rect), and 'anchor' (match-cut:
|
|
323
|
+
* dive toward the element, swap the world at the cut, and land on the
|
|
324
|
+
* element matching the same selector in the new world, so the subject
|
|
325
|
+
* persists while everything around it changes). Deterministic: every
|
|
326
|
+
* value is resolved once at build; the swap is `tl.set`s, never
|
|
327
|
+
* callbacks. Appends at the timeline's end when no position is given. */
|
|
146
328
|
through: (
|
|
147
329
|
target: string | Element,
|
|
148
330
|
opts?: ThroughOpts,
|
|
@@ -223,39 +405,39 @@ export type CountOpts = {
|
|
|
223
405
|
}
|
|
224
406
|
|
|
225
407
|
export type TextKit = {
|
|
226
|
-
|
|
408
|
+
/** Animate text in. Returns the total span so you can sequence after it. */
|
|
227
409
|
reveal: (
|
|
228
410
|
target: string | Element,
|
|
229
411
|
effect: RevealEffect,
|
|
230
412
|
opts?: TextEffectOpts,
|
|
231
413
|
position?: gsap.Position
|
|
232
414
|
) => TextResult
|
|
233
|
-
|
|
415
|
+
/** Animate text out (mirror of reveal). */
|
|
234
416
|
exit: (
|
|
235
417
|
target: string | Element,
|
|
236
418
|
effect: ExitEffect,
|
|
237
419
|
opts?: TextEffectOpts,
|
|
238
420
|
position?: gsap.Position
|
|
239
421
|
) => TextResult
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
422
|
+
/** Deterministic number count-up on an element's text (for stat pills and
|
|
423
|
+
* badges — pairs with a camera push-in). Snaps to integers on the
|
|
424
|
+
* timeline; width is locked so the layout doesn't breathe. */
|
|
243
425
|
count: (
|
|
244
426
|
target: string | Element,
|
|
245
427
|
opts: CountOpts,
|
|
246
428
|
position?: gsap.Position
|
|
247
429
|
) => TextResult
|
|
248
|
-
|
|
249
|
-
|
|
430
|
+
/** A settle-back gesture on a whole element (no split): pulse, wiggle,
|
|
431
|
+
* shake. Great on CTAs and accent words after they appear. */
|
|
250
432
|
emphasize: (
|
|
251
433
|
target: string | Element,
|
|
252
434
|
effect: EmphasisEffect,
|
|
253
435
|
opts?: EmphasisOpts,
|
|
254
436
|
position?: gsap.Position
|
|
255
437
|
) => TextResult
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
438
|
+
/** Draw a hand-sketched underline stroke beneath an element. The stroke
|
|
439
|
+
* spans the element's box — target a tight element (an inline-block span
|
|
440
|
+
* around the exact words), not a full-width heading. */
|
|
259
441
|
underline: (
|
|
260
442
|
target: string | Element,
|
|
261
443
|
opts?: UnderlineOpts,
|
|
@@ -322,29 +504,29 @@ export type LogoResult = {
|
|
|
322
504
|
}
|
|
323
505
|
|
|
324
506
|
export type LogoKit = {
|
|
325
|
-
|
|
507
|
+
/** Animate a logo in. Returns the total span so you can sequence after it. */
|
|
326
508
|
reveal: (
|
|
327
509
|
target: string | Element,
|
|
328
510
|
effect: LogoRevealEffect,
|
|
329
511
|
opts?: LogoEffectOpts,
|
|
330
512
|
position?: gsap.Position
|
|
331
513
|
) => LogoResult
|
|
332
|
-
|
|
514
|
+
/** Animate a logo out (mirror of reveal). */
|
|
333
515
|
exit: (
|
|
334
516
|
target: string | Element,
|
|
335
517
|
effect: LogoExitEffect,
|
|
336
518
|
opts?: LogoEffectOpts,
|
|
337
519
|
position?: gsap.Position
|
|
338
520
|
) => LogoResult
|
|
339
|
-
|
|
521
|
+
/** A settle-back gesture on the whole mark: pulse, breathe, wiggle. */
|
|
340
522
|
emphasize: (
|
|
341
523
|
target: string | Element,
|
|
342
524
|
effect: LogoEmphasisEffect,
|
|
343
525
|
opts?: LogoEmphasisOpts,
|
|
344
526
|
position?: gsap.Position
|
|
345
527
|
) => LogoResult
|
|
346
|
-
|
|
347
|
-
|
|
528
|
+
/** A glint sweep across the mark. Follows the logo's silhouette (the
|
|
529
|
+
* sheen is masked by the SVG shapes or the image alpha). */
|
|
348
530
|
shine: (
|
|
349
531
|
target: string | Element,
|
|
350
532
|
opts?: ShineOpts,
|
|
@@ -358,69 +540,69 @@ export type FilterTier = 'css' | 'svg'
|
|
|
358
540
|
|
|
359
541
|
export type FilterEffect =
|
|
360
542
|
// ── css tier ────────────────────────────────────────────────────────────
|
|
361
|
-
|
|
543
|
+
/** Gaussian blur. Replaces the hand-rolled whip-pan "stage-blur spike". */
|
|
362
544
|
| 'blur'
|
|
363
|
-
|
|
545
|
+
/** A halo in the element's own colour, from stacked drop-shadows. */
|
|
364
546
|
| 'glow'
|
|
365
|
-
|
|
547
|
+
/** Colour lifts (>1) or drains (<1). */
|
|
366
548
|
| 'saturate'
|
|
367
|
-
|
|
549
|
+
/** Brightness pull-down — for backgrounding a layer under an overlay. */
|
|
368
550
|
| 'dim'
|
|
369
|
-
|
|
551
|
+
/** Hue rotation drift. */
|
|
370
552
|
| 'hue'
|
|
371
553
|
// ── svg tier ────────────────────────────────────────────────────────────
|
|
372
|
-
|
|
554
|
+
/** DIRECTIONAL blur along `angle`. CSS filter cannot do this at all. */
|
|
373
555
|
| 'smear'
|
|
374
|
-
|
|
375
|
-
|
|
556
|
+
/** Chromatic aberration: red and blue channels offset in opposite
|
|
557
|
+
* directions. The glitch/impact look, without an asset. */
|
|
376
558
|
| 'chroma'
|
|
377
|
-
|
|
559
|
+
/** Turbulence-driven displacement: heat haze, liquid warp, dissolve. */
|
|
378
560
|
| 'displace'
|
|
379
|
-
|
|
561
|
+
/** Fractal-noise film grain composited over the source. */
|
|
380
562
|
| 'grain'
|
|
381
|
-
|
|
563
|
+
/** Bright-pass blur screened back over the source — bloom. */
|
|
382
564
|
| 'bloom'
|
|
383
|
-
|
|
565
|
+
/** Posterize to N discrete levels per channel. */
|
|
384
566
|
| 'threshold'
|
|
385
567
|
|
|
386
568
|
export type FilterOpts = {
|
|
387
|
-
|
|
569
|
+
/** Peak value of the effect's primary parameter. */
|
|
388
570
|
amount?: number
|
|
389
|
-
|
|
571
|
+
/** Secondary constant: turbulence frequency, bloom radius, grain scale. */
|
|
390
572
|
detail?: number
|
|
391
573
|
duration?: number
|
|
392
574
|
ease?: string
|
|
393
|
-
|
|
575
|
+
/** Direction in degrees for `smear` and `chroma`. 0 = horizontal. */
|
|
394
576
|
angle?: number
|
|
395
|
-
|
|
577
|
+
/** Tint for `glow` and `bloom`. Defaults to the element's computed colour. */
|
|
396
578
|
color?: string
|
|
397
579
|
}
|
|
398
580
|
|
|
399
581
|
export type FilterResult = {
|
|
400
|
-
|
|
582
|
+
/** Full span in seconds, including the return leg of a `spike`. */
|
|
401
583
|
duration: number
|
|
402
584
|
targets: Element[]
|
|
403
585
|
}
|
|
404
586
|
|
|
405
587
|
export type FilterKit = {
|
|
406
|
-
|
|
588
|
+
/** Ramp an effect ON and leave it there — a state change. */
|
|
407
589
|
apply: (
|
|
408
590
|
target: string | Element | Element[],
|
|
409
591
|
effect: FilterEffect,
|
|
410
592
|
opts?: FilterOpts,
|
|
411
593
|
position?: gsap.Position
|
|
412
594
|
) => FilterResult
|
|
413
|
-
|
|
595
|
+
/** Ramp it back to nothing. */
|
|
414
596
|
clear: (
|
|
415
597
|
target: string | Element | Element[],
|
|
416
598
|
effect: FilterEffect,
|
|
417
599
|
opts?: FilterOpts,
|
|
418
600
|
position?: gsap.Position
|
|
419
601
|
) => FilterResult
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
602
|
+
/** In and out in ONE gesture: the whip-pan blur, the impact bloom, the
|
|
603
|
+
* glitch chroma pop. One tween, so the Tweak panel shows ONE row with one
|
|
604
|
+
* slider, one duration and one ease — instead of the two rows the
|
|
605
|
+
* hand-rolled pattern produces, which the user has to keep in sync. */
|
|
424
606
|
spike: (
|
|
425
607
|
target: string | Element | Element[],
|
|
426
608
|
effect: FilterEffect,
|
|
@@ -432,21 +614,21 @@ export type FilterKit = {
|
|
|
432
614
|
// ─── Masks — soft edges, mattes, fills, and blend modes ──────────────────
|
|
433
615
|
|
|
434
616
|
export type MaskShape =
|
|
435
|
-
|
|
617
|
+
/** Hard directional edge (clip-path inset) — same geometry as logo.wipe. */
|
|
436
618
|
| 'wipe'
|
|
437
|
-
|
|
619
|
+
/** SOFT directional edge. The capability clip-path structurally lacks. */
|
|
438
620
|
| 'feather'
|
|
439
|
-
|
|
621
|
+
/** Hard circle growing from a point. */
|
|
440
622
|
| 'iris'
|
|
441
|
-
|
|
623
|
+
/** Soft circle with a feathered falloff. */
|
|
442
624
|
| 'radial'
|
|
443
|
-
|
|
625
|
+
/** Letterbox opening from a horizontal centre line. */
|
|
444
626
|
| 'slit'
|
|
445
|
-
|
|
627
|
+
/** Two panels parting from the centre outward. */
|
|
446
628
|
| 'barn'
|
|
447
|
-
|
|
629
|
+
/** N bands opening together. */
|
|
448
630
|
| 'blinds'
|
|
449
|
-
|
|
631
|
+
/** Caller-supplied clip-path or mask-image value — the escape hatch. */
|
|
450
632
|
| 'shape'
|
|
451
633
|
|
|
452
634
|
export type BlendMode =
|
|
@@ -467,40 +649,40 @@ export type MatteKind = 'luma' | 'alpha'
|
|
|
467
649
|
|
|
468
650
|
export type MaskOpts = {
|
|
469
651
|
direction?: MaskDirection
|
|
470
|
-
|
|
652
|
+
/** Feather width as a fraction of the element's box. 0 = hard edge. */
|
|
471
653
|
softness?: number
|
|
472
|
-
|
|
654
|
+
/** Band count for `blinds`. */
|
|
473
655
|
bands?: number
|
|
474
|
-
|
|
656
|
+
/** Centre for `iris` / `radial`, as fractions of the box. */
|
|
475
657
|
at?: { x?: number; y?: number }
|
|
476
|
-
|
|
658
|
+
/** For `shape`: any clip-path value. */
|
|
477
659
|
shape?: string
|
|
478
660
|
duration?: number
|
|
479
661
|
ease?: string
|
|
480
662
|
}
|
|
481
663
|
|
|
482
664
|
export type MatteOpts = {
|
|
483
|
-
|
|
665
|
+
/** 'luma' uses the source's brightness, 'alpha' its transparency. */
|
|
484
666
|
kind?: MatteKind
|
|
485
|
-
|
|
667
|
+
/** Sweep the matte across the target rather than holding it still. */
|
|
486
668
|
sweep?: boolean
|
|
487
669
|
duration?: number
|
|
488
670
|
ease?: string
|
|
489
671
|
}
|
|
490
672
|
|
|
491
673
|
export type FillOpts = {
|
|
492
|
-
|
|
674
|
+
/** A CSS gradient, e.g. 'linear-gradient(90deg,#ED1D24,#00B8FF)'. */
|
|
493
675
|
gradient?: string
|
|
494
|
-
|
|
676
|
+
/** Or an image URL. */
|
|
495
677
|
image?: string
|
|
496
|
-
|
|
678
|
+
/** How far the fill drifts across the glyphs, in percent. */
|
|
497
679
|
drift?: number
|
|
498
680
|
duration?: number
|
|
499
681
|
ease?: string
|
|
500
682
|
}
|
|
501
683
|
|
|
502
684
|
export type BlendOpts = {
|
|
503
|
-
|
|
685
|
+
/** Opacity the layer fades up from. Default 0. */
|
|
504
686
|
from?: number
|
|
505
687
|
duration?: number
|
|
506
688
|
ease?: string
|
|
@@ -512,35 +694,35 @@ export type MaskResult = {
|
|
|
512
694
|
}
|
|
513
695
|
|
|
514
696
|
export type MaskKit = {
|
|
515
|
-
|
|
516
|
-
|
|
697
|
+
/** Reveal ANY element through a moving mask — the general verb that
|
|
698
|
+
* `text.reveal('wipe')` and `logo.reveal('iris')` are special cases of. */
|
|
517
699
|
reveal: (
|
|
518
700
|
target: string | Element | Element[],
|
|
519
701
|
shape: MaskShape,
|
|
520
702
|
opts?: MaskOpts,
|
|
521
703
|
position?: gsap.Position
|
|
522
704
|
) => MaskResult
|
|
523
|
-
|
|
705
|
+
/** The mirror: mask the element back out. */
|
|
524
706
|
hide: (
|
|
525
707
|
target: string | Element | Element[],
|
|
526
708
|
shape: MaskShape,
|
|
527
709
|
opts?: MaskOpts,
|
|
528
710
|
position?: gsap.Position
|
|
529
711
|
) => MaskResult
|
|
530
|
-
|
|
712
|
+
/** Track matte: `source`'s silhouette becomes `target`'s visibility. */
|
|
531
713
|
matte: (
|
|
532
714
|
target: string | Element,
|
|
533
715
|
source: string | Element,
|
|
534
716
|
opts?: MatteOpts,
|
|
535
717
|
position?: gsap.Position
|
|
536
718
|
) => MaskResult
|
|
537
|
-
|
|
719
|
+
/** Fill an element's GLYPHS with a live layer and drift it. */
|
|
538
720
|
fill: (
|
|
539
721
|
target: string | Element,
|
|
540
722
|
opts?: FillOpts,
|
|
541
723
|
position?: gsap.Position
|
|
542
724
|
) => MaskResult
|
|
543
|
-
|
|
725
|
+
/** Composite a layer against its beat with a blend mode. */
|
|
544
726
|
blend: (
|
|
545
727
|
target: string | Element | Element[],
|
|
546
728
|
mode: BlendMode,
|
|
@@ -554,20 +736,20 @@ export type MaskKit = {
|
|
|
554
736
|
export type AxisName = 'wght' | 'wdth' | 'slnt' | 'opsz' | (string & {})
|
|
555
737
|
|
|
556
738
|
export type AxisEffect =
|
|
557
|
-
|
|
739
|
+
/** Ramp the axis from its current value to `to` and hold. */
|
|
558
740
|
| 'ramp'
|
|
559
|
-
|
|
741
|
+
/** Ramp up and settle back — one breath. */
|
|
560
742
|
| 'pulse'
|
|
561
|
-
|
|
743
|
+
/** A per-character wave that travels through the line once. */
|
|
562
744
|
| 'wave'
|
|
563
745
|
|
|
564
746
|
export type AxisOpts = {
|
|
565
|
-
|
|
747
|
+
/** Target axis value. Clamped to the face's declared range. */
|
|
566
748
|
to?: number
|
|
567
749
|
from?: number
|
|
568
750
|
duration?: number
|
|
569
751
|
ease?: string
|
|
570
|
-
|
|
752
|
+
/** wave: seconds between adjacent characters. */
|
|
571
753
|
stagger?: number
|
|
572
754
|
}
|
|
573
755
|
|
|
@@ -586,15 +768,15 @@ export type AxisKit = {
|
|
|
586
768
|
// ─── 3D — a Three.js layer under the deterministic timeline ──────────────
|
|
587
769
|
|
|
588
770
|
export type ThreeLayerOpts = {
|
|
589
|
-
|
|
771
|
+
/** Camera vertical FOV in degrees. */
|
|
590
772
|
fov?: number
|
|
591
|
-
|
|
773
|
+
/** Initial camera position. Default { x: 0, y: 0, z: 6 }. */
|
|
592
774
|
camera?: { x?: number; y?: number; z?: number }
|
|
593
|
-
|
|
775
|
+
/** Layer opacity 0..1 (the canvas is a normal stage element). */
|
|
594
776
|
opacity?: number
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
777
|
+
/** Stacking: 'front' (default) composites the transparent layer above
|
|
778
|
+
* the DOM; 'back' slots it behind — note an opaque App background will
|
|
779
|
+
* hide a 'back' layer entirely. */
|
|
598
780
|
depth?: 'back' | 'front'
|
|
599
781
|
}
|
|
600
782
|
|
|
@@ -613,29 +795,29 @@ export type ThreeCameraPose = {
|
|
|
613
795
|
}
|
|
614
796
|
|
|
615
797
|
export type ThreeFrameOpts = {
|
|
616
|
-
|
|
798
|
+
/** Projected fraction of frame height occupied by the subject. Default 0.7. */
|
|
617
799
|
height?: number
|
|
618
|
-
|
|
800
|
+
/** `contain` also enforces maxWidth; `height` may crop horizontally. */
|
|
619
801
|
mode?: 'contain' | 'height'
|
|
620
802
|
maxWidth?: number
|
|
621
803
|
angle?: ThreeCameraAngle
|
|
622
|
-
|
|
804
|
+
/** Subject-to-camera direction. Overrides angle. */
|
|
623
805
|
direction?: { x: number; y: number; z: number }
|
|
624
806
|
roll?: number
|
|
625
807
|
}
|
|
626
808
|
|
|
627
809
|
export type ThreeMoveOpts = {
|
|
628
810
|
duration?: number
|
|
629
|
-
|
|
630
|
-
|
|
811
|
+
/** Camera travel ease. Defaults to the house dolly (`expo.inOut`), same as
|
|
812
|
+
* the 2D camera; pass one only when the user asks for a different feel. */
|
|
631
813
|
ease?: string
|
|
632
814
|
}
|
|
633
815
|
|
|
634
816
|
export type ThreeOrbitOpts = ThreeFrameOpts &
|
|
635
817
|
ThreeMoveOpts & {
|
|
636
|
-
|
|
818
|
+
/** Horizontal orbit delta in degrees. */
|
|
637
819
|
azimuth?: number
|
|
638
|
-
|
|
820
|
+
/** Vertical orbit delta in degrees. */
|
|
639
821
|
elevation?: number
|
|
640
822
|
}
|
|
641
823
|
|
|
@@ -646,7 +828,7 @@ export type ThreeLightingPreset =
|
|
|
646
828
|
| 'studio-neutral'
|
|
647
829
|
|
|
648
830
|
export type ThreeLightingOpts = {
|
|
649
|
-
|
|
831
|
+
/** Camera-relative stays flattering through a move; world-locked reveals form. */
|
|
650
832
|
mode?: 'camera-relative' | 'world-locked'
|
|
651
833
|
intensity?: number
|
|
652
834
|
exposure?: number
|
|
@@ -662,30 +844,30 @@ export type ThreeLightRig = {
|
|
|
662
844
|
}
|
|
663
845
|
|
|
664
846
|
export type ThreeCameraRig = {
|
|
665
|
-
|
|
847
|
+
/** Solve a pose whose projected subject bounds meet the framing constraint. */
|
|
666
848
|
frame: (subject: THREE.Object3D, opts?: ThreeFrameOpts) => ThreeCameraPose
|
|
667
|
-
|
|
849
|
+
/** Immediate deterministic camera cut to a resolved pose. */
|
|
668
850
|
set: (pose: ThreeCameraPose) => void
|
|
669
|
-
|
|
851
|
+
/** Tween XYZ, target, roll, and clipping planes while keeping the target locked. */
|
|
670
852
|
moveTo: (
|
|
671
853
|
pose: ThreeCameraPose,
|
|
672
854
|
opts?: ThreeMoveOpts,
|
|
673
855
|
position?: gsap.Position
|
|
674
856
|
) => { duration: number }
|
|
675
|
-
|
|
857
|
+
/** Solve and dolly to a subject. */
|
|
676
858
|
focus: (
|
|
677
859
|
subject: THREE.Object3D,
|
|
678
860
|
frame?: ThreeFrameOpts,
|
|
679
861
|
move?: ThreeMoveOpts,
|
|
680
862
|
position?: gsap.Position
|
|
681
863
|
) => { duration: number; pose: ThreeCameraPose }
|
|
682
|
-
|
|
864
|
+
/** Orbit in camera-local spherical coordinates and re-solve framing. */
|
|
683
865
|
orbit: (
|
|
684
866
|
subject: THREE.Object3D,
|
|
685
867
|
opts?: ThreeOrbitOpts,
|
|
686
868
|
position?: gsap.Position
|
|
687
869
|
) => { duration: number; pose: ThreeCameraPose }
|
|
688
|
-
|
|
870
|
+
/** Current planned pose, including moves not yet reached by the playhead. */
|
|
689
871
|
pose: () => ThreeCameraPose
|
|
690
872
|
}
|
|
691
873
|
|
|
@@ -698,19 +880,19 @@ export type ThreeLightingRig = {
|
|
|
698
880
|
}
|
|
699
881
|
|
|
700
882
|
export type ThreeLayer = {
|
|
701
|
-
|
|
883
|
+
/** The scene graph — add meshes, lights, groups. */
|
|
702
884
|
scene: THREE.Scene
|
|
703
885
|
camera: THREE.PerspectiveCamera
|
|
704
|
-
|
|
886
|
+
/** Subject-first XYZ camera control: exact projected framing, dolly, and orbit. */
|
|
705
887
|
rig: ThreeCameraRig
|
|
706
|
-
|
|
888
|
+
/** Deterministic key/fill/rim presets bound to a 3D subject. */
|
|
707
889
|
lighting: ThreeLightingRig
|
|
708
|
-
|
|
890
|
+
/** The canvas element, for tweening opacity/filters like any element. */
|
|
709
891
|
canvas: HTMLCanvasElement
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
892
|
+
/** Drive the layer over [position, position+duration]. `update` runs every
|
|
893
|
+
* frame with eased progress 0..1 BEFORE the render — set uniforms, camera
|
|
894
|
+
* and material state from `t` alone (no clocks, no accumulation). Call
|
|
895
|
+
* once per active span; overlapping drives render redundantly. */
|
|
714
896
|
drive: (
|
|
715
897
|
duration: number,
|
|
716
898
|
update?: (t: number) => void,
|
|
@@ -720,17 +902,17 @@ export type ThreeLayer = {
|
|
|
720
902
|
}
|
|
721
903
|
|
|
722
904
|
export type ThreeKit = {
|
|
723
|
-
|
|
724
|
-
|
|
905
|
+
/** Create a 3D layer sized to the stage. Everything async about it (first
|
|
906
|
+
* compile, first paint) completes before window.__ready. */
|
|
725
907
|
layer: (opts?: ThreeLayerOpts) => ThreeLayer
|
|
726
908
|
}
|
|
727
909
|
|
|
728
910
|
// ─── Actions — stateful cursor actors, press, scroll, drag ───────────────
|
|
729
911
|
|
|
730
912
|
/** Built-in cursor icons. `collab` is the Figma-style colored arrow + name
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
913
|
+
* chip for multi-user scenes. Custom art passes `{ svg, hotspot }` — the
|
|
914
|
+
* hotspot (0..1 of the icon box, where the tip sits) is part of the contract,
|
|
915
|
+
* so brand cursors cannot reopen the misaligned-click bug. */
|
|
734
916
|
export type CursorIconName =
|
|
735
917
|
| 'arrow'
|
|
736
918
|
| 'pointer'
|
|
@@ -743,57 +925,57 @@ export type CursorIconName =
|
|
|
743
925
|
export type CursorIcon = CursorIconName | { svg: string; hotspot: { x: number; y: number } }
|
|
744
926
|
|
|
745
927
|
export type CursorActorOpts = {
|
|
746
|
-
|
|
928
|
+
/** Icon set. Default 'arrow'; named collaborators usually want 'collab'. */
|
|
747
929
|
icon?: CursorIcon
|
|
748
|
-
|
|
930
|
+
/** Tint for the collab arrow and label chip. */
|
|
749
931
|
color?: string
|
|
750
|
-
|
|
932
|
+
/** Figma-style name tag riding the cursor. */
|
|
751
933
|
label?: string
|
|
752
|
-
|
|
753
|
-
|
|
934
|
+
/** Icon box size in scene px. Default 56 (at 1080p) — marketing videos
|
|
935
|
+
* oversize the cursor so the action reads at a glance. */
|
|
754
936
|
size?: number
|
|
755
|
-
|
|
756
|
-
|
|
937
|
+
/** 'still' (default) or 'human' — sub-pixel drift while parked between
|
|
938
|
+
* actions, so a resting cursor never reads as a freeze-frame. */
|
|
757
939
|
idle?: 'still' | 'human'
|
|
758
|
-
|
|
759
|
-
|
|
940
|
+
/** Auto-morph arrow → pointer over the last stretch of a click travel.
|
|
941
|
+
* Default true. */
|
|
760
942
|
morph?: boolean
|
|
761
943
|
}
|
|
762
944
|
|
|
763
945
|
export type CursorMoveOpts = {
|
|
764
|
-
|
|
946
|
+
/** Travel time in seconds. Default 0.8. */
|
|
765
947
|
duration?: number
|
|
766
|
-
|
|
948
|
+
/** Default 'ease-in-out-cubic' — every action eases in-out. */
|
|
767
949
|
ease?: string
|
|
768
|
-
|
|
769
|
-
|
|
950
|
+
/** Target-box fractions, reprojected each frame after camera/target transforms.
|
|
951
|
+
* Default `{ x: 0.5, y: 0.52 }`. */
|
|
770
952
|
anchor?: { x?: number; y?: number }
|
|
771
|
-
|
|
953
|
+
/** Extra scene-px nudge after the anchor. */
|
|
772
954
|
offset?: { x?: number; y?: number }
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
955
|
+
/** First-action entrance: 'edge' (default — slides in from the nearest
|
|
956
|
+
* frame edge) or 'fade' (materializes just short of the target, for tight
|
|
957
|
+
* close-ups where the edge is far away). Ignored while already visible. */
|
|
776
958
|
enter?: 'edge' | 'fade'
|
|
777
959
|
}
|
|
778
960
|
|
|
779
961
|
/** What the cursor does after the action — the continuity contract.
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
962
|
+
* 'stay' (default) parks it where it is so the NEXT action continues from
|
|
963
|
+
* there; 'exit' leaves via the nearest edge; 'park' steps aside just below
|
|
964
|
+
* the target (clears it for typing or a reveal); a selector travels to that
|
|
965
|
+
* element and parks there. */
|
|
784
966
|
export type CursorThen = 'stay' | 'exit' | 'park' | string
|
|
785
967
|
|
|
786
968
|
export type CursorClickOpts = CursorMoveOpts & {
|
|
787
|
-
|
|
788
|
-
|
|
969
|
+
/** Target squash scale. 'auto' (default) derives from the element's size —
|
|
970
|
+
* big surfaces squash less. A number (e.g. 0.9) is used verbatim. */
|
|
789
971
|
press?: number | 'auto'
|
|
790
|
-
|
|
972
|
+
/** Squash time each way. Default 0.09. */
|
|
791
973
|
pressDuration?: number
|
|
792
974
|
then?: CursorThen
|
|
793
975
|
}
|
|
794
976
|
|
|
795
977
|
export type CursorDragOpts = CursorMoveOpts & {
|
|
796
|
-
|
|
978
|
+
/** Subtle scale lift on the payload while it is held. Default true. */
|
|
797
979
|
lift?: boolean
|
|
798
980
|
then?: CursorThen
|
|
799
981
|
}
|
|
@@ -801,50 +983,49 @@ export type CursorDragOpts = CursorMoveOpts & {
|
|
|
801
983
|
export type CursorExitOpts = {
|
|
802
984
|
duration?: number
|
|
803
985
|
ease?: string
|
|
804
|
-
|
|
986
|
+
/** 'edge' (default) or 'fade'. */
|
|
805
987
|
via?: 'edge' | 'fade'
|
|
806
988
|
}
|
|
807
989
|
|
|
808
990
|
export type CursorActor = {
|
|
809
|
-
|
|
991
|
+
/** Move to an element (or continue there from the last position). */
|
|
810
992
|
to: (
|
|
811
993
|
target: string | Element,
|
|
812
994
|
opts?: CursorMoveOpts,
|
|
813
995
|
position?: gsap.Position
|
|
814
996
|
) => { duration: number }
|
|
815
|
-
|
|
816
|
-
|
|
997
|
+
/** Travel + press: squashes the cursor AND the target, ordered so the
|
|
998
|
+
* click visibly precedes any result the direction schedules after it. */
|
|
817
999
|
click: (
|
|
818
1000
|
target: string | Element,
|
|
819
1001
|
opts?: CursorClickOpts,
|
|
820
1002
|
position?: gsap.Position
|
|
821
1003
|
) => { duration: number }
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
* (payload centers on it) or a scene-px delta `{ x, y }`. */
|
|
1004
|
+
/** Track the transformed payload each frame. `to`: same-plane element/selector
|
|
1005
|
+
* (centers the payload) or scene-px delta `{ x, y }`. */
|
|
825
1006
|
drag: (
|
|
826
1007
|
payload: string | Element,
|
|
827
1008
|
to: string | Element | { x: number; y: number },
|
|
828
1009
|
opts?: CursorDragOpts,
|
|
829
1010
|
position?: gsap.Position
|
|
830
1011
|
) => { duration: number }
|
|
831
|
-
|
|
1012
|
+
/** Leave the frame. The next action re-enters fresh. */
|
|
832
1013
|
exit: (opts?: CursorExitOpts, position?: gsap.Position) => { duration: number }
|
|
833
|
-
|
|
1014
|
+
/** Update the collab name chip on the timeline. */
|
|
834
1015
|
label: (text: string, position?: gsap.Position) => void
|
|
835
1016
|
}
|
|
836
1017
|
|
|
837
1018
|
export type CursorKit = CursorActor & {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1019
|
+
/** Declare a named cursor — an independent actor with its own icon, color,
|
|
1020
|
+
* label chip, and continuity state. Two actors animate concurrently just by
|
|
1021
|
+
* overlapping their positions. */
|
|
841
1022
|
actor: (name: string, opts?: CursorActorOpts) => CursorActor
|
|
842
1023
|
}
|
|
843
1024
|
|
|
844
1025
|
export type PressOpts = {
|
|
845
|
-
|
|
1026
|
+
/** Squash scale; 'auto' (default) derives from element size. */
|
|
846
1027
|
amount?: number | 'auto'
|
|
847
|
-
|
|
1028
|
+
/** Each way. Default 0.09. */
|
|
848
1029
|
duration?: number
|
|
849
1030
|
ease?: string
|
|
850
1031
|
}
|
|
@@ -852,46 +1033,46 @@ export type PressOpts = {
|
|
|
852
1033
|
export type ScrollOpts = {
|
|
853
1034
|
duration?: number
|
|
854
1035
|
ease?: string
|
|
855
|
-
|
|
856
|
-
|
|
1036
|
+
/** Where the target settles in the container: 0 top, 0.5 center (default),
|
|
1037
|
+
* 1 bottom. */
|
|
857
1038
|
align?: number
|
|
858
1039
|
}
|
|
859
1040
|
|
|
860
1041
|
export type UiChange = {
|
|
861
|
-
|
|
1042
|
+
/** Replace the element's text content. */
|
|
862
1043
|
text?: string
|
|
863
|
-
|
|
1044
|
+
/** Set an input's value. */
|
|
864
1045
|
value?: string
|
|
865
|
-
|
|
1046
|
+
/** Swap an image/video source. */
|
|
866
1047
|
src?: string
|
|
867
|
-
|
|
1048
|
+
/** Set attributes verbatim. */
|
|
868
1049
|
attrs?: Record<string, string>
|
|
869
|
-
|
|
1050
|
+
/** Add/remove classes (resolved against the authored class list). */
|
|
870
1051
|
classes?: { add?: string[]; remove?: string[] }
|
|
871
|
-
|
|
872
|
-
|
|
1052
|
+
/** Brief brightness pulse after the change, so the state change reads as an
|
|
1053
|
+
* event rather than a glitch. */
|
|
873
1054
|
flash?: boolean
|
|
874
1055
|
}
|
|
875
1056
|
|
|
876
1057
|
export type ActionsKit = {
|
|
877
1058
|
cursor: CursorKit
|
|
878
|
-
|
|
1059
|
+
/** Squash without a cursor — keyboard sends, programmatic actions. */
|
|
879
1060
|
press: (
|
|
880
1061
|
target: string | Element,
|
|
881
1062
|
opts?: PressOpts,
|
|
882
1063
|
position?: gsap.Position
|
|
883
1064
|
) => { duration: number }
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1065
|
+
/** Animated scroll of a container until `to` (element, selector, or an
|
|
1066
|
+
* absolute scrollTop) is in view. `duration: 0` sets it instantly for
|
|
1067
|
+
* initial state. */
|
|
887
1068
|
scroll: (
|
|
888
1069
|
container: string | Element,
|
|
889
1070
|
to: string | Element | number,
|
|
890
1071
|
opts?: ScrollOpts,
|
|
891
1072
|
position?: gsap.Position
|
|
892
1073
|
) => { duration: number }
|
|
893
|
-
|
|
894
|
-
|
|
1074
|
+
/** Declarative hard UI state change on the timeline (seek-reversible).
|
|
1075
|
+
* Instant by design — a cut is hard; `flash` adds the one animated accent. */
|
|
895
1076
|
ui: {
|
|
896
1077
|
set: (
|
|
897
1078
|
target: string | Element,
|
|
@@ -906,37 +1087,37 @@ export type ActionsKit = {
|
|
|
906
1087
|
export type HighlightRelease = 'fade' | 'undraw' | 'hold'
|
|
907
1088
|
|
|
908
1089
|
export type HighlightOpts = {
|
|
909
|
-
|
|
1090
|
+
/** Stroke/fill color. Each primitive has its own sensible default. */
|
|
910
1091
|
color?: string
|
|
911
|
-
|
|
1092
|
+
/** Draw-on time in seconds. */
|
|
912
1093
|
duration?: number
|
|
913
|
-
|
|
1094
|
+
/** Default 'ease-in-out-cubic'. */
|
|
914
1095
|
ease?: string
|
|
915
|
-
|
|
916
|
-
|
|
1096
|
+
/** Seconds the highlight holds after drawing, or 'hold' to stay to the end.
|
|
1097
|
+
* Default 'hold' — releasing is a deliberate choice. */
|
|
917
1098
|
persist?: number | 'hold'
|
|
918
|
-
|
|
1099
|
+
/** How it leaves when persist is a number: 'fade' (default) or 'undraw'. */
|
|
919
1100
|
release?: Exclude<HighlightRelease, 'hold'>
|
|
920
|
-
|
|
1101
|
+
/** Extra space around the target in scene px. Default derives from size. */
|
|
921
1102
|
padding?: number
|
|
922
|
-
|
|
1103
|
+
/** Stroke thickness in px. */
|
|
923
1104
|
thickness?: number
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1105
|
+
/** What to fit: 'text' hugs the glyphs (a block element's box spans its
|
|
1106
|
+
* full column — a marker on "99.99%" must not swallow the empty half of
|
|
1107
|
+
* the card); 'box' takes the element box. Marker, circle, and underline
|
|
1108
|
+
* default to 'text'; ring, glow, and spotlight default to 'box'. */
|
|
928
1109
|
fit?: 'text' | 'box'
|
|
929
1110
|
}
|
|
930
1111
|
|
|
931
1112
|
export type SpotlightOpts = {
|
|
932
|
-
|
|
1113
|
+
/** How dark the rest of the frame goes (0..1). Default 0.55. */
|
|
933
1114
|
dim?: number
|
|
934
|
-
|
|
935
|
-
|
|
1115
|
+
/** Defocus everything OUTSIDE the cutout by this many px (the target stays
|
|
1116
|
+
* sharp). 0 (default) dims only. Engine ceiling: 20. */
|
|
936
1117
|
blur?: number
|
|
937
|
-
|
|
1118
|
+
/** Gaussian softness of the cutout edge in px. Default 26. */
|
|
938
1119
|
feather?: number
|
|
939
|
-
|
|
1120
|
+
/** Breathing room between the element's edge and the cutout, px. Default 16. */
|
|
940
1121
|
padding?: number
|
|
941
1122
|
duration?: number
|
|
942
1123
|
ease?: string
|
|
@@ -944,51 +1125,51 @@ export type SpotlightOpts = {
|
|
|
944
1125
|
}
|
|
945
1126
|
|
|
946
1127
|
export type HighlightKit = {
|
|
947
|
-
|
|
948
|
-
|
|
1128
|
+
/** Rounded ring draws itself around the element, following its corner
|
|
1129
|
+
* radius. The workhorse product-tour annotation. */
|
|
949
1130
|
ring: (target: string | Element, opts?: HighlightOpts, position?: gsap.Position) => { duration: number }
|
|
950
|
-
|
|
1131
|
+
/** Hand-drawn ellipse loop with overshoot — red-pen annotation energy. */
|
|
951
1132
|
circle: (target: string | Element, opts?: HighlightOpts, position?: gsap.Position) => { duration: number }
|
|
952
|
-
|
|
953
|
-
|
|
1133
|
+
/** Highlighter-pen swipe behind the glyphs: a skewed band wipes across in
|
|
1134
|
+
* multiply blend, sized from the line height. */
|
|
954
1135
|
marker: (target: string | Element, opts?: HighlightOpts, position?: gsap.Position) => { duration: number }
|
|
955
|
-
|
|
1136
|
+
/** Underline draw. Styles: 'straight' | 'marker' | 'sketch'. */
|
|
956
1137
|
underline: (
|
|
957
1138
|
target: string | Element,
|
|
958
1139
|
style?: 'straight' | 'marker' | 'sketch',
|
|
959
1140
|
opts?: HighlightOpts,
|
|
960
1141
|
position?: gsap.Position
|
|
961
1142
|
) => { duration: number }
|
|
962
|
-
|
|
1143
|
+
/** One soft glow pulse as a beat lands — light, not opacity. */
|
|
963
1144
|
glow: (target: string | Element, opts?: HighlightOpts, position?: gsap.Position) => { duration: number }
|
|
964
|
-
|
|
965
|
-
|
|
1145
|
+
/** Inverse move: everything EXCEPT the target dims behind a feathered
|
|
1146
|
+
* cutout. The strongest "look here" in the vocabulary. */
|
|
966
1147
|
spotlight: (target: string | Element, opts?: SpotlightOpts, position?: gsap.Position) => { duration: number }
|
|
967
1148
|
}
|
|
968
1149
|
|
|
969
1150
|
// ─── Looks — scene light, shading, backdrops, hero shot, track ───────────
|
|
970
1151
|
|
|
971
1152
|
export type LightState = {
|
|
972
|
-
|
|
973
|
-
|
|
1153
|
+
/** Direction the light COMES FROM, degrees clockwise from 12 o'clock.
|
|
1154
|
+
* 135 = upper-left key (the portrait default). */
|
|
974
1155
|
angle: number
|
|
975
|
-
|
|
1156
|
+
/** 0 cool … 1 warm. Tints sweeps and rims. */
|
|
976
1157
|
warmth: number
|
|
977
1158
|
}
|
|
978
1159
|
|
|
979
1160
|
export type LightKit = {
|
|
980
|
-
|
|
981
|
-
|
|
1161
|
+
/** Declare the scene light. Call once, early; sweeps, rims, and cast
|
|
1162
|
+
* shadows derive their direction from it. */
|
|
982
1163
|
set: (state: Partial<LightState>) => void
|
|
983
|
-
|
|
984
|
-
|
|
1164
|
+
/** A glint sweeps across the element, angled with the scene light — the
|
|
1165
|
+
* `logo.shine` move generalized to any surface. */
|
|
985
1166
|
sweep: (
|
|
986
1167
|
target: string | Element,
|
|
987
1168
|
opts?: { duration?: number; intensity?: number; width?: number; ease?: string },
|
|
988
1169
|
position?: gsap.Position
|
|
989
1170
|
) => { duration: number }
|
|
990
|
-
|
|
991
|
-
|
|
1171
|
+
/** Edge light on the side facing the scene light — the premium product-
|
|
1172
|
+
* shot rim. Fades in and holds. */
|
|
992
1173
|
rim: (
|
|
993
1174
|
target: string | Element,
|
|
994
1175
|
opts?: { color?: string; intensity?: number; duration?: number },
|
|
@@ -997,22 +1178,22 @@ export type LightKit = {
|
|
|
997
1178
|
}
|
|
998
1179
|
|
|
999
1180
|
export type ShadeKit = {
|
|
1000
|
-
|
|
1001
|
-
|
|
1181
|
+
/** Soft shadow under the element, offset along the scene light. Replaces
|
|
1182
|
+
* hand-tuned boxShadow strings and always agrees with every other shadow. */
|
|
1002
1183
|
cast: (
|
|
1003
1184
|
target: string | Element,
|
|
1004
1185
|
opts?: { distance?: number; softness?: number; opacity?: number; duration?: number },
|
|
1005
1186
|
position?: gsap.Position
|
|
1006
1187
|
) => { duration: number }
|
|
1007
|
-
|
|
1008
|
-
|
|
1188
|
+
/** Frame vignette, faded in once and held (like the grain plate — never
|
|
1189
|
+
* cut, so it doesn't blink at hard cuts). */
|
|
1009
1190
|
vignette: (
|
|
1010
1191
|
opts?: { strength?: number; duration?: number },
|
|
1011
1192
|
position?: gsap.Position
|
|
1012
1193
|
) => { duration: number }
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1194
|
+
/** Named color grade on the scene content. 'warm-product' | 'cool-saas' |
|
|
1195
|
+
* 'noir'. Applied to the scene root, so overlays (cursors, highlights)
|
|
1196
|
+
* stay ungraded and legible. */
|
|
1016
1197
|
grade: (
|
|
1017
1198
|
preset: 'warm-product' | 'cool-saas' | 'noir',
|
|
1018
1199
|
opts?: { duration?: number },
|
|
@@ -1032,28 +1213,28 @@ export type BackdropStyle =
|
|
|
1032
1213
|
| 'grid-glow'
|
|
1033
1214
|
|
|
1034
1215
|
export type BackdropOpts = {
|
|
1035
|
-
|
|
1036
|
-
|
|
1216
|
+
/** 0..1 — scales amplitude, speed, and opacity together. The default 0.3
|
|
1217
|
+
* keeps the preset inside the sub-salient budget. */
|
|
1037
1218
|
energy?: number
|
|
1038
|
-
|
|
1039
|
-
|
|
1219
|
+
/** Colors for the soft forms. Keep them near the scene's background
|
|
1220
|
+
* luminance — the preset caps opacity, but hue contrast is yours. */
|
|
1040
1221
|
palette?: string[]
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1222
|
+
/** Base fill the backdrop paints under its forms. The backdrop mounts
|
|
1223
|
+
* BEHIND the scene root, so a root that goes transparent needs the
|
|
1224
|
+
* backdrop to own the ground color. */
|
|
1044
1225
|
base?: string
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1226
|
+
/** FIXED BACKPLATE (default true): mount outside the camera-carrying
|
|
1227
|
+
* stage, so zooms and pans move the UI plane while the ground stays put —
|
|
1228
|
+
* the studio-cyclorama behavior of reference launch films. Set false to
|
|
1229
|
+
* make the backdrop part of the world the camera moves through. */
|
|
1049
1230
|
fixed?: boolean
|
|
1050
1231
|
}
|
|
1051
1232
|
|
|
1052
1233
|
export type BackdropHandle = {
|
|
1053
1234
|
el: HTMLElement
|
|
1054
|
-
|
|
1235
|
+
/** Duck the backdrop under a dense foreground beat. */
|
|
1055
1236
|
calm: (position?: gsap.Position) => void
|
|
1056
|
-
|
|
1237
|
+
/** Bring it back up during a hold. */
|
|
1057
1238
|
lift: (position?: gsap.Position) => void
|
|
1058
1239
|
}
|
|
1059
1240
|
|
|
@@ -1062,26 +1243,26 @@ export type HeroAnchor =
|
|
|
1062
1243
|
| { x: number; y: number }
|
|
1063
1244
|
|
|
1064
1245
|
export type HeroOpts = {
|
|
1065
|
-
|
|
1066
|
-
|
|
1246
|
+
/** Which region of the target the camera pushes toward. Default
|
|
1247
|
+
* 'bottom-right' — the submit-button corner. */
|
|
1067
1248
|
anchor?: HeroAnchor
|
|
1068
|
-
|
|
1069
|
-
|
|
1249
|
+
/** How far in: scale ≈ (frame width / target width) × zoom. Default 1.35 —
|
|
1250
|
+
* the target overflows the frame, close-up territory. */
|
|
1070
1251
|
zoom?: number
|
|
1071
|
-
|
|
1072
|
-
|
|
1252
|
+
/** Perspective tilt in degrees. Defaults derive from the anchor (the panel
|
|
1253
|
+
* leans its far corner away): e.g. bottom-right → pitch 7, yaw -9. */
|
|
1073
1254
|
pitch?: number
|
|
1074
1255
|
yaw?: number
|
|
1075
|
-
|
|
1256
|
+
/** Push-in time. Default 0.9. */
|
|
1076
1257
|
duration?: number
|
|
1077
|
-
|
|
1258
|
+
/** Seconds held at the angle before returning. Default 1.2. */
|
|
1078
1259
|
hold?: number
|
|
1079
|
-
|
|
1260
|
+
/** Return time. Default 0.8; `settle: false` stays in the pose. */
|
|
1080
1261
|
returnDuration?: number
|
|
1081
1262
|
settle?: boolean
|
|
1082
|
-
|
|
1263
|
+
/** Sheen sweep across the surface during the push. Default true. */
|
|
1083
1264
|
sheen?: boolean
|
|
1084
|
-
|
|
1265
|
+
/** Shadow deepens as the panel lifts. Default true. */
|
|
1085
1266
|
shadow?: boolean
|
|
1086
1267
|
ease?: string
|
|
1087
1268
|
}
|
|
@@ -1089,39 +1270,39 @@ export type HeroOpts = {
|
|
|
1089
1270
|
export type LooksKit = {
|
|
1090
1271
|
light: LightKit
|
|
1091
1272
|
shade: ShadeKit
|
|
1092
|
-
|
|
1273
|
+
/** Mount an ambient animated backdrop behind the scene root. */
|
|
1093
1274
|
backdrop: (style: BackdropStyle, opts?: BackdropOpts) => BackdropHandle
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1275
|
+
/** The angle-zoom product shot: camera pushes toward one corner of the
|
|
1276
|
+
* target while it tilts in perspective, sheen and shadow responding.
|
|
1277
|
+
* A full round trip by default. */
|
|
1097
1278
|
hero: (target: string | Element, opts?: HeroOpts, position?: gsap.Position) => { duration: number }
|
|
1098
|
-
|
|
1099
|
-
|
|
1279
|
+
/** Standalone perspective pose: tilt an element (not the stage) and hold.
|
|
1280
|
+
* Tilt back with pitch/yaw 0. */
|
|
1100
1281
|
tilt: (
|
|
1101
1282
|
target: string | Element,
|
|
1102
1283
|
opts?: { pitch?: number; yaw?: number; duration?: number; ease?: string; perspective?: number },
|
|
1103
1284
|
position?: gsap.Position
|
|
1104
1285
|
) => { duration: number }
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1286
|
+
/** Camera FOLLOWS an element while it moves: per-frame the tracked
|
|
1287
|
+
* element's live position re-centers the camera at a locked occupancy.
|
|
1288
|
+
* Seek-deterministic — each frame is a pure function of timeline position
|
|
1289
|
+
* (the catch-up from `from` is an eased blend, not stateful smoothing).
|
|
1290
|
+
* AUTHOR IT AFTER the motion it follows, so it renders after the movers. */
|
|
1110
1291
|
track: (
|
|
1111
1292
|
target: string | Element,
|
|
1112
1293
|
opts?: {
|
|
1113
|
-
|
|
1294
|
+
/** Tracking window in seconds. Default 2. */
|
|
1114
1295
|
duration?: number
|
|
1115
|
-
|
|
1296
|
+
/** Target occupancy as fraction of frame height. Default 0.5. */
|
|
1116
1297
|
height?: number
|
|
1117
|
-
|
|
1118
|
-
|
|
1298
|
+
/** Fraction of the window spent easing from `from` onto the target.
|
|
1299
|
+
* Default 0.25. */
|
|
1119
1300
|
attach?: number
|
|
1120
|
-
|
|
1121
|
-
|
|
1301
|
+
/** Camera pose the track blends FROM (what the camera shows when the
|
|
1302
|
+
* window opens). Default full frame. */
|
|
1122
1303
|
from?: { scale: number; x: number; y: number }
|
|
1123
|
-
|
|
1124
|
-
|
|
1304
|
+
/** After the window: 'hold' keeps the last framing (a later camera call
|
|
1305
|
+
* takes over); 'full' returns to the full frame. Default 'full'. */
|
|
1125
1306
|
release?: 'hold' | 'full'
|
|
1126
1307
|
},
|
|
1127
1308
|
position?: gsap.Position
|
|
@@ -1142,85 +1323,129 @@ export type SentenceHandle = {
|
|
|
1142
1323
|
// ─── Morph — any element becomes any other element ───────────────────────
|
|
1143
1324
|
|
|
1144
1325
|
export type MorphOpts = {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1326
|
+
/** 'apple' = a firm in-out flight that keeps the deformation READABLE —
|
|
1327
|
+
* the shape change is the subject, so it may not be over in three frames.
|
|
1328
|
+
* 'spring' = the same flight with the reference bounce: a LIGHT damped
|
|
1329
|
+
* sine riding the same path, calibrated from the dnyxstudios AE expression
|
|
1330
|
+
* (amp .04·v, freq 1.8Hz, decay e^-3t, eased envelope). One motion, no
|
|
1331
|
+
* delayed settle, ~one visible overshoot.
|
|
1332
|
+
*
|
|
1333
|
+
* Both are `ease-in-out-*` by design. A morph is travel WITHIN the frame,
|
|
1334
|
+
* and the house table reserves `ease-out-*` for entrances; a front-loaded
|
|
1335
|
+
* curve finishes the transformation before the viewer can look at it. */
|
|
1155
1336
|
feel?: 'apple' | 'spring'
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1337
|
+
/** Tune the 'spring' bounce. `amp` = first overshoot as a fraction of the
|
|
1338
|
+
* travel (default 0.025 — light), `freq` Hz (default 1.8), `decay` per
|
|
1339
|
+
* second (default 3), `flight` = fraction of `duration` spent on the main
|
|
1340
|
+
* move before the bounce tail (default 0.55). */
|
|
1160
1341
|
bounce?: { amp?: number; freq?: number; decay?: number; flight?: number }
|
|
1161
1342
|
duration?: number
|
|
1162
|
-
|
|
1163
|
-
|
|
1343
|
+
/** Flight curve — ANY GSAP ease works: house names, 'elastic.out(1, 0.4)',
|
|
1344
|
+
* 'back.out(1.8)'… Overshoots extrapolate past the target (kept renderable). */
|
|
1164
1345
|
ease?: string
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1346
|
+
/** 0..1 in EASED space — where along the SHAPE CHANGE the surface swaps
|
|
1347
|
+
* from A to B. Not a fade midpoint: at this instant both sides hold the
|
|
1348
|
+
* same box and the same chrome, so the swap is a match-frame cut. 0.5
|
|
1349
|
+
* means "when the shape is halfway between the two," which is what the
|
|
1350
|
+
* viewer actually perceives. */
|
|
1170
1351
|
handoff?: number
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1352
|
+
/** Fraction of the flight over which unmatched content departs and
|
|
1353
|
+
* arrives (default 0.42). Content displaces along the travel vector and
|
|
1354
|
+
* must be clear of the seam — that emptiness is what makes the cut
|
|
1355
|
+
* invisible, so widening this past ~0.5 will start to show the swap. */
|
|
1175
1356
|
content?: number
|
|
1176
|
-
|
|
1357
|
+
/** Arc lift in px (0 disables). Default: auto from travel distance. */
|
|
1177
1358
|
arc?: number
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1359
|
+
/** Content choreography (default 'displace'): unmatched children leave and
|
|
1360
|
+
* arrive by moving ALONG the travel vector — replacement reads as motion,
|
|
1361
|
+
* and both sides are clear of the seam before the surfaces swap.
|
|
1362
|
+
* `false` = containers only; children hold still and stay counter-scaled,
|
|
1363
|
+
* which is right when the two sides share a background image or the
|
|
1364
|
+
* content is meant to look continuous. */
|
|
1184
1365
|
parts?: 'displace' | false
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1366
|
+
/** Sub-component matching (default 'auto'): elements tagged
|
|
1367
|
+
* `data-morph="name"` on BOTH sides (any depth) morph INTO each other —
|
|
1368
|
+
* each pair rides its own interpolated box inside the flight, so the
|
|
1369
|
+
* source's sub-component visibly becomes the target's. Untagged direct
|
|
1370
|
+
* children are greedily auto-paired by text overlap. `false` disables. */
|
|
1190
1371
|
match?: 'auto' | false
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1372
|
+
/** Landing overshoot on top of a non-spring flight. `true`/default object
|
|
1373
|
+
* for the soft house settle, `{ scale, duration, ease }` to tune (elastic
|
|
1374
|
+
* landings live here), `false` for none. 'spring' feel ignores this — its
|
|
1375
|
+
* ring is already in the flight. */
|
|
1195
1376
|
settle?: boolean | { scale?: number; duration?: number; ease?: string }
|
|
1196
1377
|
zIndex?: number
|
|
1197
1378
|
}
|
|
1198
1379
|
|
|
1380
|
+
// ─── Morph surfaces — continuous vector material and DOM continuity ──────
|
|
1381
|
+
|
|
1382
|
+
export type MorphSurfaceLayer = {
|
|
1383
|
+
from: string; to: string; color?: string; endColor?: string
|
|
1384
|
+
stroke?: number; opacity?: number; endOpacity?: number
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
export type MorphSurfaceOpts = MorphTreeOpts & {
|
|
1388
|
+
layers: MorphSurfaceLayer[]
|
|
1389
|
+
/** Coordinates of the authored vector surface; roots provide its CSS size. */
|
|
1390
|
+
viewBox?: string
|
|
1391
|
+
/** Keep decoration inside the changing outer shape; null disables clipping. */
|
|
1392
|
+
clipToLayer?: number | null
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
export type MorphSurfaceResult = MorphTreeResult & { surfaceLayers: number }
|
|
1396
|
+
|
|
1397
|
+
// ─── Morph tree — recursive continuity ───────────────────────────────────
|
|
1398
|
+
|
|
1399
|
+
export type MorphTreeOpts = {
|
|
1400
|
+
duration?: number
|
|
1401
|
+
ease?: string
|
|
1402
|
+
/** How deep to recurse before children ride their parent rigidly. */
|
|
1403
|
+
maxDepth?: number
|
|
1404
|
+
/** Perceptual budget: how many nodes may move independently. Past ~6 the
|
|
1405
|
+
* eye stops reading structure and starts seeing a swarm. */
|
|
1406
|
+
maxMoving?: number
|
|
1407
|
+
/** Minimum affinity to call two elements the same thing. Lower = more
|
|
1408
|
+
* matches and more motion; higher = more collapse-and-emerge. */
|
|
1409
|
+
floor?: number
|
|
1410
|
+
/** Fraction of the flight each depth level waits before it resolves.
|
|
1411
|
+
* Parents commit first; children follow. 0 disables the cascade. */
|
|
1412
|
+
lead?: number
|
|
1413
|
+
/** Gather N sources into one target when the counts are lopsided. */
|
|
1414
|
+
cluster?: boolean
|
|
1415
|
+
arc?: number
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
export type MorphTreeResult = {
|
|
1419
|
+
duration: number
|
|
1420
|
+
moving: number
|
|
1421
|
+
debug: string
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1199
1424
|
// ─── Engine — the direction context you write against ────────────────────
|
|
1200
1425
|
|
|
1201
1426
|
/** Render directives a scene (or a kit, during the direction) hands to the
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1427
|
+
* exporter. The channel already exists end to end: `export const output` in
|
|
1428
|
+
* direction.ts → loadScene → buildScene → window.__config → the exporter's
|
|
1429
|
+
* probe — and because __config is assigned AFTER the direction runs, a kit
|
|
1430
|
+
* can set these from inside a direction call. Everything optional; absent
|
|
1431
|
+
* means today's behaviour exactly. */
|
|
1207
1432
|
export type RenderConfig = {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1433
|
+
/** Temporal motion blur (Phase 6). `off` (default) renders one sample per
|
|
1434
|
+
* frame. Tiers fix samples-per-frame; `runs` limits blur to intervals so a
|
|
1435
|
+
* 30s clip with 6s of camera moves does not pay the multiplier throughout. */
|
|
1211
1436
|
blur?: {
|
|
1212
1437
|
tier?: 'off' | 'light' | 'full'
|
|
1213
|
-
|
|
1214
|
-
|
|
1438
|
+
/** Shutter angle in degrees, 0–360. Default 180. Costs nothing — it only
|
|
1439
|
+
* changes WHICH times get sampled, never how many. */
|
|
1215
1440
|
shutter?: number
|
|
1216
|
-
|
|
1441
|
+
/** Blur only these intervals, in seconds. Absent = the whole timeline. */
|
|
1217
1442
|
runs?: Array<{ from: number; to: number }>
|
|
1218
1443
|
}
|
|
1219
1444
|
}
|
|
1220
1445
|
|
|
1221
1446
|
/** Committed output of scripts/analyze-music.mjs — one JSON per track in
|
|
1222
|
-
|
|
1223
|
-
|
|
1447
|
+
* music/analysis/. A static file, identical in every export shard, which is
|
|
1448
|
+
* what makes audio-reactive motion compatible with seek-anywhere rendering. */
|
|
1224
1449
|
export type TrackAnalysis = {
|
|
1225
1450
|
analyzerVersion: number
|
|
1226
1451
|
rate: number // envelope grid Hz
|
|
@@ -1236,25 +1461,25 @@ export type TrackAnalysis = {
|
|
|
1236
1461
|
}
|
|
1237
1462
|
|
|
1238
1463
|
export type AudioKit = {
|
|
1239
|
-
|
|
1240
|
-
|
|
1464
|
+
/** True when a real analysis file was loaded. When false every envelope
|
|
1465
|
+
* reads 0 and the beat grid is synthetic — directions never throw. */
|
|
1241
1466
|
live: boolean
|
|
1242
1467
|
bpm: number
|
|
1243
1468
|
beats: number[]
|
|
1244
1469
|
downbeats: number[]
|
|
1245
1470
|
onsets: number[]
|
|
1246
1471
|
sections: Array<{ start: number; end: number; energy: number }>
|
|
1247
|
-
|
|
1248
|
-
|
|
1472
|
+
/** Overall loudness at timeline time t, 0..1. Pure lookup — the exporter
|
|
1473
|
+
* loops the track under a longer video, so t wraps at the track length. */
|
|
1249
1474
|
energy: (t: number) => number
|
|
1250
1475
|
band: (t: number, band: 'low' | 'mid' | 'high') => number
|
|
1251
|
-
|
|
1476
|
+
/** Timeline time of beat n (downbeat-phase corrected). */
|
|
1252
1477
|
beat: (n: number) => number
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1478
|
+
/** Map the energy envelope onto element properties: one proxy tween whose
|
|
1479
|
+
* onUpdate reads energy(t) — a pure function of playhead, so it renders
|
|
1480
|
+
* identically from any seek. `map` receives 0..1 and returns gsap vars to
|
|
1481
|
+
* set, e.g. (e) => ({ scale: 1 + e * 0.06 }). NO smoothing at read time:
|
|
1482
|
+
* an IIR filter over successive frames is the shard-seam bug. */
|
|
1258
1483
|
drive: (
|
|
1259
1484
|
target: string | Element,
|
|
1260
1485
|
map: (energy: number) => Record<string, number | string>,
|
|
@@ -1264,7 +1489,7 @@ export type AudioKit = {
|
|
|
1264
1489
|
}
|
|
1265
1490
|
|
|
1266
1491
|
export type Output = {
|
|
1267
|
-
|
|
1492
|
+
/** Delivery raster dimensions. They do not change scene layout. */
|
|
1268
1493
|
width: number
|
|
1269
1494
|
height: number
|
|
1270
1495
|
fps: number
|
|
@@ -1272,7 +1497,7 @@ export type Output = {
|
|
|
1272
1497
|
}
|
|
1273
1498
|
|
|
1274
1499
|
/** Resolved renderer contract. `width`/`height` are always logical scene
|
|
1275
|
-
|
|
1500
|
+
* coordinates; only the exporter consumes `delivery` as raster dimensions. */
|
|
1276
1501
|
export type StageConfig = Output & {
|
|
1277
1502
|
composition: { width: number; height: number }
|
|
1278
1503
|
delivery: { width: number; height: number }
|
|
@@ -1285,21 +1510,21 @@ export type FramingTarget = string | Element | Rect | FramingTarget[]
|
|
|
1285
1510
|
export type Framing = { scale: number; x: number; y: number }
|
|
1286
1511
|
|
|
1287
1512
|
export type CameraFocusOpts = {
|
|
1288
|
-
|
|
1513
|
+
/** Fraction of the composition height occupied by the subject. Default 0.7. */
|
|
1289
1514
|
height?: number
|
|
1290
|
-
|
|
1515
|
+
/** `contain` also caps width; `height` guarantees height and may crop horizontally. */
|
|
1291
1516
|
mode?: 'contain' | 'height'
|
|
1292
|
-
|
|
1517
|
+
/** Width cap used by `contain`. Default 0.9. */
|
|
1293
1518
|
maxWidth?: number
|
|
1294
|
-
|
|
1519
|
+
/** Optional safety ceiling. When hit, exact occupancy cannot be guaranteed. */
|
|
1295
1520
|
maxScale?: number
|
|
1296
1521
|
offsetX?: number
|
|
1297
1522
|
offsetY?: number
|
|
1298
1523
|
clamp?: boolean
|
|
1299
|
-
|
|
1524
|
+
/** Camera travel time. Default 0.8 seconds. */
|
|
1300
1525
|
duration?: number
|
|
1301
|
-
|
|
1302
|
-
|
|
1526
|
+
/** Camera travel ease. Defaults to the house dolly (`expo.inOut`); pass one
|
|
1527
|
+
* only when the user explicitly asks for a different camera feel. */
|
|
1303
1528
|
ease?: string
|
|
1304
1529
|
}
|
|
1305
1530
|
|
|
@@ -1316,12 +1541,12 @@ export type FramingOpts = {
|
|
|
1316
1541
|
export type TypeOpts = {
|
|
1317
1542
|
cps?: number // characters per second
|
|
1318
1543
|
caret?: boolean
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1544
|
+
/** Opt-in invisible tracking head that glides smoothly along the typed
|
|
1545
|
+
* text — constant speed within each visual line, a quick eased drop at
|
|
1546
|
+
* every wrap. Hand it to `track()` or a camera framing; the caret itself
|
|
1547
|
+
* steps per character and wraps, so never track the caret directly.
|
|
1548
|
+
* `width` is the head's scene-px width (default 300); `height` is a
|
|
1549
|
+
* multiple of the line height (default 2.9). */
|
|
1325
1550
|
head?: boolean | { width?: number; height?: number }
|
|
1326
1551
|
}
|
|
1327
1552
|
|
|
@@ -1342,8 +1567,8 @@ export type TextStyleOverride = {
|
|
|
1342
1567
|
color?: string
|
|
1343
1568
|
x?: number
|
|
1344
1569
|
y?: number
|
|
1345
|
-
|
|
1346
|
-
|
|
1570
|
+
/** Wrapping width in scene px — the side bars reflow text instead of
|
|
1571
|
+
* scale-squeezing it. Applied as inline-block + width. */
|
|
1347
1572
|
width?: number
|
|
1348
1573
|
hidden?: boolean
|
|
1349
1574
|
}
|
|
@@ -1355,13 +1580,13 @@ export type ObjectPositionOverride = {
|
|
|
1355
1580
|
scaleY?: number
|
|
1356
1581
|
rotation?: number
|
|
1357
1582
|
hidden?: boolean
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1583
|
+
/** Plain CSS declarations the studio applies to the object's element —
|
|
1584
|
+
* border-radius, background-color, box-shadow and friends. Applied at
|
|
1585
|
+
* stage prepare, so they ride the structural rebuild route. */
|
|
1361
1586
|
styles?: Record<string, string>
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1587
|
+
/** Replacement source for the image or video the object carries (the
|
|
1588
|
+
* element itself or its first img/video descendant), usually a data URI so
|
|
1589
|
+
* renders never depend on the network. */
|
|
1365
1590
|
src?: string
|
|
1366
1591
|
}
|
|
1367
1592
|
|
|
@@ -1379,10 +1604,10 @@ export type SceneOverrides = {
|
|
|
1379
1604
|
textStyles?: Record<string, TextStyleOverride>
|
|
1380
1605
|
addedText?: Record<string, AddedTextLayer>
|
|
1381
1606
|
objects?: Record<string, ObjectPositionOverride> // stable UI wrapper id -> scene-pixel offset
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1607
|
+
/** Explicit paint order for editable siblings. Keys include the editable
|
|
1608
|
+
* kind (`object:…` / `text:…`) so independently generated ids cannot
|
|
1609
|
+
* collide. Values are normalized by the studio whenever an item moves in
|
|
1610
|
+
* the stack. */
|
|
1386
1611
|
layerOrder?: Record<string, number>
|
|
1387
1612
|
}
|
|
1388
1613
|
|
|
@@ -1402,6 +1627,54 @@ export type TweenKind =
|
|
|
1402
1627
|
| 'axis'
|
|
1403
1628
|
| 'three'
|
|
1404
1629
|
|
|
1630
|
+
export type EaseFingerprint = [number, number, number, number]
|
|
1631
|
+
|
|
1632
|
+
export type EaseResolutionInfo = {
|
|
1633
|
+
requested: string
|
|
1634
|
+
resolvedId: string
|
|
1635
|
+
status: 'default' | 'resolved' | 'custom' | 'unresolved'
|
|
1636
|
+
resolvedFingerprint: EaseFingerprint
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
export type MotionOperationInfo = {
|
|
1640
|
+
operationId: string
|
|
1641
|
+
primitive: string
|
|
1642
|
+
target?: string
|
|
1643
|
+
targetCount?: number
|
|
1644
|
+
requestedOptionKeys: string[]
|
|
1645
|
+
acceptedOptionKeys: string[]
|
|
1646
|
+
droppedOptionKeys: string[]
|
|
1647
|
+
ease?: EaseResolutionInfo
|
|
1648
|
+
effective?: {
|
|
1649
|
+
transition?: {
|
|
1650
|
+
style: string
|
|
1651
|
+
direction: string
|
|
1652
|
+
color: string
|
|
1653
|
+
duration: number
|
|
1654
|
+
at: { x: number; y: number }
|
|
1655
|
+
}
|
|
1656
|
+
blur?: {
|
|
1657
|
+
from: number
|
|
1658
|
+
to: number
|
|
1659
|
+
tier: string
|
|
1660
|
+
samples: number
|
|
1661
|
+
shutter: number
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
export type ExecutionDiagnostic = {
|
|
1667
|
+
code: 'unresolved-ease' | 'unknown-option-key' | 'dead-selector'
|
|
1668
|
+
operationId: string
|
|
1669
|
+
primitive: string
|
|
1670
|
+
message: string
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
export type ExecutionIntegrityInspection = {
|
|
1674
|
+
operations: MotionOperationInfo[]
|
|
1675
|
+
diagnostics: ExecutionDiagnostic[]
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1405
1678
|
export type TweenInfo = {
|
|
1406
1679
|
id: number
|
|
1407
1680
|
sig: string
|
|
@@ -1411,11 +1684,17 @@ export type TweenInfo = {
|
|
|
1411
1684
|
duration: number // per-target duration (excludes stagger span)
|
|
1412
1685
|
total: number // full span including stagger
|
|
1413
1686
|
ease: string // '' = gsap default
|
|
1687
|
+
target: string
|
|
1688
|
+
targetCount: number
|
|
1689
|
+
easeRequested: string
|
|
1690
|
+
easeResolvedId: string
|
|
1691
|
+
easeResolutionStatus: 'default' | 'resolved' | 'custom' | 'unresolved'
|
|
1692
|
+
easeFingerprint: EaseFingerprint
|
|
1414
1693
|
stagger?: number
|
|
1415
1694
|
vars: Record<string, number | string>
|
|
1416
1695
|
overridden: boolean
|
|
1417
|
-
|
|
1418
|
-
|
|
1696
|
+
/** The editable layer this tween animates, when its first Element target
|
|
1697
|
+
* sits inside one — lets the studio show a layer's choreography. */
|
|
1419
1698
|
targetObjectId?: string
|
|
1420
1699
|
targetTextId?: string
|
|
1421
1700
|
}
|
|
@@ -1428,14 +1707,50 @@ export type SceneInspection = {
|
|
|
1428
1707
|
tweens: TweenInfo[]
|
|
1429
1708
|
cameraSets: CameraSetInfo[]
|
|
1430
1709
|
overrides: SceneOverrides
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1710
|
+
/**
|
|
1711
|
+
* Non-fatal build complaints — a step whose element is gone, a text effect
|
|
1712
|
+
* with nothing left to split. The scene still plays; these say what silently
|
|
1713
|
+
* does nothing so a stale selector can't rot unnoticed.
|
|
1714
|
+
*/
|
|
1436
1715
|
warnings: string[]
|
|
1437
|
-
|
|
1438
|
-
|
|
1716
|
+
/** Observational diagnostics; never rejects or changes authored motion. */
|
|
1717
|
+
execution: ExecutionIntegrityInspection
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
/**
|
|
1721
|
+
* The context a direction is written against.
|
|
1722
|
+
*
|
|
1723
|
+
* HOW A FILM IS WRITTEN. `createFilmSequencer` (kit/film) owns semantic
|
|
1724
|
+
* ordering. `createWorldCamera` (kit/world) owns named-object framing, depth,
|
|
1725
|
+
* and camera gestures—never content animation or editorial rhythm:
|
|
1726
|
+
*
|
|
1727
|
+
* const film = createFilmSequencer({ tl })
|
|
1728
|
+
* const world = createWorldCamera({ tl, stage, camera, register: 'promo' })
|
|
1729
|
+
* const copy = createTextPlans(text)
|
|
1730
|
+
* const result = film.do(manner.realize('[data-obj="RESULT_ROWS"]'))
|
|
1731
|
+
* film.do(world.camera.whipTo('RESULT_ROWS').landWith(result))
|
|
1732
|
+
* film.do(copy.exit('[data-claim]', 'rise'))
|
|
1733
|
+
* film.cut(editorial.cut(layers, next), world.camera.cutTo('NEXT'))
|
|
1734
|
+
*
|
|
1735
|
+
* Camera verbs are `cutTo` / `moveTo` / `zoomTo` / `whipTo` / `departTo` /
|
|
1736
|
+
* `angleTo` / `focusOn`. Every verb centres the named data-obj bounding box
|
|
1737
|
+
* and maximizes it inside a padded frame. The optional framing words are
|
|
1738
|
+
* only `'tight'` | `'normal'` | `'loose'`; they change breathing room, never
|
|
1739
|
+
* select or cap magnification. `film.do` advances to settle,
|
|
1740
|
+
* `film.together` shares a start, and
|
|
1741
|
+
* `film.at(handle.impact)` attaches a consequence to its cause. Any existing
|
|
1742
|
+
* kit call or bespoke GSAP gesture can be lifted with `film.plan()`. Primary
|
|
1743
|
+
* actions sequence from SETTLE; `film.cut()` uses the stricter END horizon, so
|
|
1744
|
+
* a scene can never hide an unfinished stagger or exit. Only
|
|
1745
|
+
* `film.coveredCut(handle, marker, reason, ...)` may cross that horizon, and it
|
|
1746
|
+
* makes the carrying motion explicit.
|
|
1747
|
+
*
|
|
1748
|
+
* The raw camera below (`framing` / `full` / `crop` / `set` / `move`) is the
|
|
1749
|
+
* ESCAPE HATCH, not the default: measured or skeleton work that needs an exact
|
|
1750
|
+
* pose at an exact time, and anything the kit has no vocabulary for. Reach for
|
|
1751
|
+
* it deliberately; a film that reaches for it by habit is composing shot by
|
|
1752
|
+
* shot with no subject, no containment and no continuity.
|
|
1753
|
+
*/
|
|
1439
1754
|
export type DirectionContext = {
|
|
1440
1755
|
gsap: typeof gsap
|
|
1441
1756
|
tl: gsap.core.Timeline // the master timeline (created paused)
|
|
@@ -1443,6 +1758,11 @@ export type DirectionContext = {
|
|
|
1443
1758
|
config: StageConfig
|
|
1444
1759
|
q: <T extends HTMLElement>(selector: string) => T
|
|
1445
1760
|
qa: (selector: string) => HTMLElement[]
|
|
1761
|
+
/** The RAW camera: an exact pose at an exact time. Prefer World Camera's
|
|
1762
|
+
* named verbs for shots (see the note on `DirectionContext` above) — they
|
|
1763
|
+
* maximize and centre the named subject with deterministic padding, which
|
|
1764
|
+
* none of the calls here do for you. Use these for measured/skeleton work,
|
|
1765
|
+
* and inside kits. */
|
|
1446
1766
|
camera: {
|
|
1447
1767
|
framing: (target: FramingTarget, opts?: FramingOpts) => Framing
|
|
1448
1768
|
focus: (
|
|
@@ -1451,13 +1771,18 @@ export type DirectionContext = {
|
|
|
1451
1771
|
position?: gsap.Position
|
|
1452
1772
|
) => Framing
|
|
1453
1773
|
full: () => Framing
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1774
|
+
/** CROP framing, the raw-camera close shot: the target deliberately
|
|
1775
|
+
* OVERFLOWS the frame (`fill` × cover, default 1.4) and one side hugs a
|
|
1776
|
+
* frame edge (`anchor`: 'left' | 'right' | 'top' | 'bottom' | corners |
|
|
1777
|
+
* {x,y} fraction), the rest bleeding off. Returns a Framing you compose
|
|
1778
|
+
* with camera.set (the cut) or camera.move (the motivated push).
|
|
1779
|
+
*
|
|
1780
|
+
* IN A WORLD-KIT FILM YOU DO NOT CALL THIS. The declared equivalents are
|
|
1781
|
+
* `fit: 'closeup'` (cropping IS the shot — it goes past the edge-safety
|
|
1782
|
+
* wall on a subject that declares `data-detail="fine"`) and `fit: 'wide'`
|
|
1783
|
+
* for the whole-product establishing shot, and the kit centres the named
|
|
1784
|
+
* subject instead of anchoring it to a frame edge by hand. Use `crop`
|
|
1785
|
+
* when you are hand-composing a pose the kit has no vocabulary for. */
|
|
1461
1786
|
crop: (
|
|
1462
1787
|
target: FramingTarget,
|
|
1463
1788
|
opts?: {
|
|
@@ -1470,338 +1795,346 @@ export type DirectionContext = {
|
|
|
1470
1795
|
}
|
|
1471
1796
|
) => Framing
|
|
1472
1797
|
set: (framing: Framing) => void
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1798
|
+
/** The house dolly. DEFAULTS: `duration: 0.8`, `ease: 'expo.inOut'` —
|
|
1799
|
+
* a long dwell at both ends and a confident glide through the middle.
|
|
1800
|
+
* These defaults ARE the recommended camera physics: do NOT pass ease
|
|
1801
|
+
* or duration here unless the user explicitly asks for a different
|
|
1802
|
+
* camera feel. The one sanctioned exception: an instant reframe behind
|
|
1803
|
+
* a covering element (`duration: 0.01`). A slow linear drift
|
|
1804
|
+
* (`duration: 2–5, ease: 'none'`) is NOT one — sub-pixel motion on the
|
|
1805
|
+
* whole stage re-rasterizes every glyph each frame and renders as
|
|
1806
|
+
* micro-flicker; the motion lab flags it as DRIFT and export blocks.
|
|
1807
|
+
* Hold the camera still instead. Anything between 0.05–0.4s is
|
|
1808
|
+
* neither a move nor a cut — avoid it. `ease-in-out-cubic` is for
|
|
1809
|
+
* ELEMENT travel, never the camera.
|
|
1810
|
+
*
|
|
1811
|
+
* A world-kit beat supplies all of this from its declared register, and
|
|
1812
|
+
* `camera: { cut: true }` is how a beat asks for the snap instead of the
|
|
1813
|
+
* glide — so a film built on the kit never writes a camera duration or
|
|
1814
|
+
* ease at all. */
|
|
1485
1815
|
move: (
|
|
1486
1816
|
framing: Framing,
|
|
1487
1817
|
vars?: gsap.TweenVars,
|
|
1488
1818
|
position?: gsap.Position
|
|
1489
1819
|
) => void
|
|
1490
1820
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1821
|
+
/** Deterministic, seek-driven typing with a fake caret. The caret follows
|
|
1822
|
+
* real layout (mirror-probed), so it wraps with the text in a textarea.
|
|
1823
|
+
* `head` (opt-in) is a smooth glide marker for camera tracking. */
|
|
1494
1824
|
typeInto: (
|
|
1495
1825
|
target: string | HTMLTextAreaElement | HTMLInputElement,
|
|
1496
1826
|
text: string,
|
|
1497
1827
|
opts?: TypeOpts,
|
|
1498
1828
|
position?: gsap.Position
|
|
1499
1829
|
) => { duration: number; caret: HTMLElement | null; head: HTMLElement | null }
|
|
1500
|
-
|
|
1830
|
+
/** Rect the finished text will occupy in `target` (for framing the close-up). */
|
|
1501
1831
|
measureTypedLine: (
|
|
1502
1832
|
target: string | HTMLTextAreaElement | HTMLInputElement,
|
|
1503
1833
|
text: string
|
|
1504
1834
|
) => Rect
|
|
1505
|
-
|
|
1506
|
-
|
|
1835
|
+
/** Rect of an element's rendered text content (a block element's box
|
|
1836
|
+
* spans its full column — this measures just the text). */
|
|
1507
1837
|
textRect: (target: string | Element) => Rect
|
|
1508
|
-
|
|
1838
|
+
/** gsap SplitText — animate .chars / .words / .lines. */
|
|
1509
1839
|
split: (
|
|
1510
1840
|
target: string | Element,
|
|
1511
1841
|
vars?: SplitText.Vars
|
|
1512
1842
|
) => SplitText
|
|
1513
|
-
|
|
1843
|
+
/** Empty tween that extends the timeline (a beat / settle). */
|
|
1514
1844
|
hold: (duration: number, position?: gsap.Position) => void
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1845
|
+
/** Timeline-driven MP4/WebM playback: scrubs the element's currentTime,
|
|
1846
|
+
* so scrubbing and export frames are exact (a free-running <video> would
|
|
1847
|
+
* shimmer — never autoplay one). `from` = source offset in seconds;
|
|
1848
|
+
* `duration` defaults to the rest of the source. Returns `{ duration }`.
|
|
1849
|
+
* The element is muted and paused; exports wait for each frame's decode. */
|
|
1520
1850
|
video: (
|
|
1521
1851
|
target: string | HTMLVideoElement,
|
|
1522
1852
|
opts?: { from?: number; duration?: number; playbackRate?: number },
|
|
1523
1853
|
position?: gsap.Position
|
|
1524
1854
|
) => { duration: number }
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1855
|
+
/** Named text animations (built on SplitText, all deterministic).
|
|
1856
|
+
* `text.reveal(el, effect)` in: rise, fall, blur, wave, flip, pop, slide,
|
|
1857
|
+
* typewriter, scramble, wipe, bounce, elastic, arc, streak. `text.exit(el,
|
|
1858
|
+
* effect)` out: rise, fall, blur, fade, pop, scramble, wipe, scatter,
|
|
1859
|
+
* streak. `text.count(el, { to, from?, duration? })` counts a number up on
|
|
1860
|
+
* the timeline (stat pills). `text.emphasize(el, 'pulse'|'wiggle'|'shake')`
|
|
1861
|
+
* is a settle-back gesture for CTAs/accent words. `text.underline(el)`
|
|
1862
|
+
* draws a sketched stroke under a tight element.
|
|
1863
|
+
*
|
|
1864
|
+
* `transition.out(style)` / `transition.in(style)` cover/reveal the whole
|
|
1865
|
+
* FRAME with a color (overlay outside the camera): styles 'wipe',
|
|
1866
|
+
* 'pixels', 'iris', opts `{ color?, direction?: 'left'|'right'|'up'|
|
|
1867
|
+
* 'down', duration?, cell?, at? }`. `out` with no position appends at the
|
|
1868
|
+
* clip's end — the standard way to end a clip for a cut; `in` at position
|
|
1869
|
+
* 0 starts the clip covered and reveals it.
|
|
1870
|
+
* `transition.through(target, { via?, char?, word?, swap?, land?, color?,
|
|
1871
|
+
* duration?, outDuration? }, position?)` is the GO-THROUGH: dive INTO
|
|
1872
|
+
* something until its color takes over the frame, world-swap under the
|
|
1873
|
+
* cover, pull out into the new world. `via` picks the dive geometry:
|
|
1874
|
+
* 'surface' (default, the element's face), 'glyph' (`{ via: 'glyph',
|
|
1875
|
+
* char: 'o' }` — through the character's enclosed counter, measured from
|
|
1876
|
+
* the real glyph outline on plain text: no wrapper spans, no hand-tuned
|
|
1877
|
+
* margins), 'word' (`{ via: 'word', word: 'world' }` — the word's rect),
|
|
1878
|
+
* 'anchor' (match-cut: the subject persists across the cut — the same
|
|
1879
|
+
* selector's twin in the new world is the landing — while everything
|
|
1880
|
+
* around it changes). Never hand-roll a dive (a magic-scale camera.move +
|
|
1881
|
+
* a hand-timed swap): through() computes the exact cover scale, measures
|
|
1882
|
+
* at the dive's own start time, and guarantees full coverage at the cut.
|
|
1883
|
+
* All return `{ duration }` (full span including
|
|
1884
|
+
* stagger) for sequencing, and accept `(target, effect, opts?, position?)`
|
|
1885
|
+
* with opts `{ by?: 'chars'|'words'|'lines', duration?, stagger?, ease?,
|
|
1886
|
+
* from?: 'start'|'center'|'end'|'edges' }`. */
|
|
1557
1887
|
text: TextKit
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1888
|
+
/** Named logo/brand-mark animations (all deterministic). Target the
|
|
1889
|
+
* element wrapping the mark — an inline `<svg>`, an `<img>`, or a div
|
|
1890
|
+
* around one. `logo.reveal(el, effect)` in: pop, blur, rise, wipe, iris,
|
|
1891
|
+
* slit, spin, flip, bounce, glitch, draw, build. `draw` (strokes trace the
|
|
1892
|
+
* SVG paths, fills crossfade in — works even on fills-only logos) and
|
|
1893
|
+
* `build` (shapes assemble one by one) need INLINE SVG with path/shape
|
|
1894
|
+
* elements; on an `<img>` or path-less SVG they fall back to `wipe` with a
|
|
1895
|
+
* console warning. `logo.exit(el, effect)` out: pop, blur, fade, wipe,
|
|
1896
|
+
* iris, drop. `logo.emphasize(el, 'pulse'|'breathe'|'wiggle')` is a
|
|
1897
|
+
* settle-back gesture (breathe takes `repeats`, finite). `logo.shine(el)`
|
|
1898
|
+
* sweeps a glint across the mark, masked by the logo's own silhouette.
|
|
1899
|
+
* All return `{ duration }` (full span including stagger) and accept
|
|
1900
|
+
* `(target, effect, opts?, position?)` with opts `{ duration?, ease?,
|
|
1901
|
+
* stagger?, direction?: 'left'|'right'|'up'|'down' }`. */
|
|
1572
1902
|
logo: LogoKit
|
|
1573
1903
|
transition: TransitionKit
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1904
|
+
/** Real 3D — a Three.js layer under the same paused timeline. Use for
|
|
1905
|
+
* depth After Effects fakes and CSS perspective cannot reach: real
|
|
1906
|
+
* cameras, lights, materials. `const L = three.layer({ camera: {z: 6} })`
|
|
1907
|
+
* gives `{ scene, camera, canvas, drive }` — add meshes and lights to
|
|
1908
|
+
* `L.scene` (import * as THREE from 'three'), animate positions/rotations
|
|
1909
|
+
* with ordinary `tl.to(mesh.position, {...})` tweens, then
|
|
1910
|
+
* `L.drive(duration, (t) => {...}, opts?, position?)` renders the layer
|
|
1911
|
+
* over that span (set uniforms/camera from `t` ALONE — no clocks, no
|
|
1912
|
+
* setAnimationLoop, no accumulation; the frame must be a pure function of
|
|
1913
|
+
* timeline position). The canvas is a normal stage element: tween its
|
|
1914
|
+
* opacity, put fx/mask on it, cut it with tl.set. */
|
|
1585
1915
|
three: ThreeKit
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1916
|
+
/** Variable-font axis animation — type whose WEIGHT is the thing that
|
|
1917
|
+
* moves. After Effects cannot do this at all. `axis.animate(el, axis,
|
|
1918
|
+
* effect, opts?, position?)`: axes `wght` (weight), `wdth` (width), `slnt`
|
|
1919
|
+
* (slant), `opsz` (optical size), or any custom axis name; effects `ramp`
|
|
1920
|
+
* (go and hold), `pulse` (one breath), `wave` (travels through the
|
|
1921
|
+
* characters). Opts `{ from?, to?, duration?, ease?, stagger? }`. On a
|
|
1922
|
+
* non-variable font it degrades honestly (wght -> weight staircase, wdth ->
|
|
1923
|
+
* scaleX, slnt -> skewX) with a console warning, never a throw. Geist (the
|
|
1924
|
+
* house family) IS variable — weight 100-900. */
|
|
1595
1925
|
axis: AxisKit
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1926
|
+
/** The soundtrack's offline analysis (music/analysis/<track>.json).
|
|
1927
|
+
* `audio.beat(n)` / `audio.downbeats` place cuts on the REAL grid — the
|
|
1928
|
+
* downbeat phase is measured, not assumed to start at 0. `audio.onsets`
|
|
1929
|
+
* are the strongest hits; land impact beats on them. `audio.drive(el, map)`
|
|
1930
|
+
* makes a property breathe with the track. When no analysis exists,
|
|
1931
|
+
* `audio.live` is false and every envelope reads 0 — never throws. */
|
|
1602
1932
|
audio: AudioKit
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1933
|
+
/** Ask the exporter for TRUE temporal motion blur over an interval —
|
|
1934
|
+
* sub-frame supersampling at render time, the thing a real-time tool
|
|
1935
|
+
* cannot do. Put it on fast camera moves and whip cuts; the preview does
|
|
1936
|
+
* not show it (it renders one sample per frame), the MP4 does.
|
|
1937
|
+
* `ctx.blur(from, to)` marks [from, to] seconds. Opts: `shutter` degrees
|
|
1938
|
+
* (0-360, default 180 — wider smears more), `tier` 'light' (4 samples) or
|
|
1939
|
+
* 'full' (8, final renders). Costs render time roughly x samples over the
|
|
1940
|
+
* marked intervals only, so mark moves, not holds. */
|
|
1611
1941
|
blur: (
|
|
1612
1942
|
from: number,
|
|
1613
1943
|
to: number,
|
|
1614
1944
|
opts?: { shutter?: number; tier?: 'light' | 'full' }
|
|
1615
1945
|
) => void
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1946
|
+
/** Register async prep (texture decode, analysis fetch) started inside the
|
|
1947
|
+
* direction. buildScene awaits every registered promise before setting
|
|
1948
|
+
* window.__ready, so the exporter never screenshots a half-loaded scene.
|
|
1949
|
+
* For kit authors — a direction rarely needs it directly. */
|
|
1620
1950
|
__await: (work: Promise<unknown>) => void
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1951
|
+
/** Visual effects — CSS filters and SVG filter primitives, all
|
|
1952
|
+
* deterministic. Three verbs, all `(target, effect, opts?, position?)`:
|
|
1953
|
+
* `fx.apply` ramps an effect on and leaves it, `fx.clear` ramps it off,
|
|
1954
|
+
* and `fx.spike` does both in ONE gesture (one Tweak row, not two).
|
|
1955
|
+
*
|
|
1956
|
+
* Effects: `blur` (gaussian), `glow` (halo in the element's own colour),
|
|
1957
|
+
* `saturate`, `dim` (brightness), `hue` (rotation), `smear` (DIRECTIONAL
|
|
1958
|
+
* blur along `angle` — CSS blur cannot do this), `chroma` (chromatic
|
|
1959
|
+
* aberration, the glitch look), `displace` (turbulence warp: heat haze,
|
|
1960
|
+
* liquid dissolve), `grain` (film grain), `bloom` (bright-pass glow),
|
|
1961
|
+
* `threshold` (posterize to N levels).
|
|
1962
|
+
*
|
|
1963
|
+
* Opts `{ amount?, detail?, duration?, ease?, angle?, color? }` — `amount`
|
|
1964
|
+
* is the effect's peak, `detail` its secondary constant (turbulence
|
|
1965
|
+
* frequency, bloom radius), `angle` in degrees for smear/chroma.
|
|
1966
|
+
*
|
|
1967
|
+
* The whip-pan cut is `fx.spike(stage, 'blur', {}, 'whip1')` alongside a
|
|
1968
|
+
* fast `camera.move` — do NOT hand-write a pair of
|
|
1969
|
+
* `tl.to(stage, { filter: 'blur(12px)' })` tweens, which produces two
|
|
1970
|
+
* unsynced rows in the Tweak panel. */
|
|
1641
1971
|
fx: FilterKit
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1972
|
+
/** Interaction actions — the cursor is a STATEFUL ACTOR, not a per-call
|
|
1973
|
+
* sprite. The engine tracks where every cursor is and whether it is on
|
|
1974
|
+
* screen: continuation from the last resting position is the default, and
|
|
1975
|
+
* entry/exit are explicit moments. First action enters from the nearest
|
|
1976
|
+
* frame edge (`enter: 'fade'` materializes near the target instead); the
|
|
1977
|
+
* next action continues from wherever the last one parked; `cursor.exit()`
|
|
1978
|
+
* leaves via the nearest edge. `cursor.click(el)` travels, auto-morphs
|
|
1979
|
+
* arrow→pointer on approach, and squashes cursor AND target (squash derived
|
|
1980
|
+
* from target size). `cursor.drag(payload, to)` carries the payload locked
|
|
1981
|
+
* to the cursor in one tween pair. `then: 'stay'|'exit'|'park'|selector`
|
|
1982
|
+
* says where the cursor goes after an action. `cursor.actor(name, { icon,
|
|
1983
|
+
* color, label })` declares independent named cursors (multi-user scenes —
|
|
1984
|
+
* 'collab' icon carries a Figma-style name chip); two actors animate
|
|
1985
|
+
* concurrently just by overlapping positions. Icons carry REGISTERED
|
|
1986
|
+
* hotspots, so click math cannot drift. Every action defaults to
|
|
1987
|
+
* `ease-in-out-cubic` and takes `duration`. */
|
|
1658
1988
|
cursor: CursorKit
|
|
1659
|
-
|
|
1660
|
-
|
|
1989
|
+
/** Squash without a cursor — keyboard sends, programmatic actions. Amount
|
|
1990
|
+
* defaults from the element's size (big surfaces squash less). */
|
|
1661
1991
|
press: ActionsKit['press']
|
|
1662
|
-
|
|
1663
|
-
|
|
1992
|
+
/** Animated scroll of a container until a target is in view (`duration: 0`
|
|
1993
|
+
* sets it instantly for initial state). */
|
|
1664
1994
|
scroll: ActionsKit['scroll']
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1995
|
+
/** Declarative hard UI state change on the timeline, seek-reversible:
|
|
1996
|
+
* `ui.set(el, { text?, value?, src?, attrs?, classes?, flash? })`. Instant
|
|
1997
|
+
* by design — a cut is hard; `flash` adds the one animated accent. */
|
|
1668
1998
|
ui: ActionsKit['ui']
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1999
|
+
/** Annotation primitives for pre-existing components — the "look here"
|
|
2000
|
+
* vocabulary. `highlight.ring(el)` draws a rounded ring around the element
|
|
2001
|
+
* (follows its corner radius); `circle` is the hand-drawn red-pen loop with
|
|
2002
|
+
* overshoot; `marker` is a highlighter swipe behind the glyphs (multiply
|
|
2003
|
+
* blend, sized from line height); `underline(el, 'sketch'|'straight'|
|
|
2004
|
+
* 'marker')`; `glow` is one soft halo pulse; `spotlight(el)` dims
|
|
2005
|
+
* everything EXCEPT the target behind a feathered cutout — the strongest
|
|
2006
|
+
* "look here" available. All geometry derives from the target; every
|
|
2007
|
+
* highlight takes `persist: 'hold' | seconds` and `release: 'fade' |
|
|
2008
|
+
* 'undraw'`, so nothing loiters. Pairs with the cursor kit: cursor.click +
|
|
2009
|
+
* highlight.ring is the product-tour grammar. */
|
|
1680
2010
|
highlight: HighlightKit
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
2011
|
+
/** Scene lighting. `light.set({ angle, warmth })` declares THE light once —
|
|
2012
|
+
* sweeps, rims, and cast shadows all derive their direction from it, so
|
|
2013
|
+
* shadows can never disagree. `light.sweep(el)` runs a glint across any
|
|
2014
|
+
* surface; `light.rim(el)` is the premium edge light. */
|
|
1685
2015
|
light: LooksKit['light']
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
2016
|
+
/** Shading and grade. `shade.cast(el)` — soft shadow offset along the
|
|
2017
|
+
* declared scene light; `shade.vignette()` — one held frame vignette;
|
|
2018
|
+
* `shade.grade('warm-product'|'cool-saas'|'noir')` — named color grade on
|
|
2019
|
+
* the scene content (overlays stay ungraded and legible). */
|
|
1690
2020
|
shade: LooksKit['shade']
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2021
|
+
/** Ambient animated backdrop mounted BEHIND the scene root (the scene's
|
|
2022
|
+
* own background must let it show through). Styles: 'gradient-drift',
|
|
2023
|
+
* 'orbs', 'dot-grid'. Presets are tuned to stay alive but sub-salient
|
|
2024
|
+
* (~0.5–2% pixel change per interval); `energy` scales within the budget.
|
|
2025
|
+
* The returned handle's `calm()`/`lift()` duck the backdrop under dense
|
|
2026
|
+
* foreground beats — audio ducking for pixels. */
|
|
1697
2027
|
backdrop: LooksKit['backdrop']
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
2028
|
+
/** The angle-zoom product shot: the camera pushes toward one corner of the
|
|
2029
|
+
* target (`anchor: 'bottom-right'` = the submit-button corner) WHILE the
|
|
2030
|
+
* panel tilts a few degrees in perspective, a sheen sweeps the surface as
|
|
2031
|
+
* the angle changes, and the shadow deepens. Full round trip by default
|
|
2032
|
+
* (in → hold → settle); `settle: false` stays in the pose. Perspective is
|
|
2033
|
+
* applied to the target only, never the stage. */
|
|
1704
2034
|
hero: LooksKit['hero']
|
|
1705
|
-
|
|
1706
|
-
|
|
2035
|
+
/** Standalone perspective pose for an element: `tilt(el, { pitch, yaw })`,
|
|
2036
|
+
* tilt back to 0 to flatten. */
|
|
1707
2037
|
tilt: LooksKit['tilt']
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2038
|
+
/** Camera FOLLOWS a moving element — per-frame re-centering at a locked
|
|
2039
|
+
* occupancy, seek-deterministic (the catch-up is an eased blend from a
|
|
2040
|
+
* declared start pose, not stateful smoothing). AUTHOR IT AFTER the motion
|
|
2041
|
+
* it follows. `track(el, { duration, height, release: 'full'|'hold' })`. */
|
|
1712
2042
|
track: LooksKit['track']
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
2043
|
+
/** A sentence as a stateful object. `sentence(el)` takes over the element's
|
|
2044
|
+
* text (word spans) and returns a handle: `.append(words)` — words land
|
|
2045
|
+
* into the held line; `.swap(slot, [...])` — one word cycles in place;
|
|
2046
|
+
* `.stress(word)` — the one-accent-color semantic emphasis; `.morphWord`
|
|
2047
|
+
* ("it!" → "it?"); `.chip(word, media)` — a word slot holds a media chip.
|
|
2048
|
+
* New words are mounted hidden at build (layout stays stable); `recenter`
|
|
2049
|
+
* eases the container instead of live reflow. */
|
|
1720
2050
|
sentence: SentenceKit['sentence']
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
* sub-components, N→1 gathers, 1→N splits — install `kit/morphtree`. */
|
|
2051
|
+
/** Universal shape morph: any element becomes any other element, a rect, or
|
|
2052
|
+
* `'frame'`. Both real elements ride ONE interpolated box (position, size,
|
|
2053
|
+
* corner radius, arc lift) while the source's chrome is driven onto the
|
|
2054
|
+
* target's — at `handoff` the two are the same picture, so the surfaces
|
|
2055
|
+
* HARD-CUT and never crossfade. Matched sub-components (`data-morph="name"`
|
|
2056
|
+
* on both sides) travel; unmatched content displaces along the travel
|
|
2057
|
+
* vector; children are counter-scaled so nothing stretches.
|
|
2058
|
+
*
|
|
2059
|
+
* Author the TARGET hidden (`autoAlpha: 0`); the morph reveals it at
|
|
2060
|
+
* arrival. Every visual state is a pure function of eased progress, so it
|
|
2061
|
+
* is export-worker safe. For recursive structure, N→1 gathers and 1→N splits use `morphTree()`. */
|
|
1733
2062
|
morph: MorphKit['morph']
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2063
|
+
/** Continuous vector material + recursive DOM continuity, on the master timeline. */
|
|
2064
|
+
morphSurface: (from:string|Element,to:string|Element,opts:MorphSurfaceOpts,position?:gsap.Position)=>MorphSurfaceResult
|
|
2065
|
+
/** Recursive correspondence, clustering and seek-safe semantic handoff. */
|
|
2066
|
+
morphTree: (from: string | Element, to: string | Element | HTMLElement[], opts?: MorphTreeOpts, position?: gsap.Position) => MorphTreeResult
|
|
2067
|
+
/** The chip⇄full-bleed round trip: grows the element to cover the frame,
|
|
2068
|
+
* holds, returns home (`back: false` to stay). Scale IS the transition —
|
|
2069
|
+
* the object persists (Apple News+ grammar). */
|
|
1737
2070
|
expand: MorphKit['expand']
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
2071
|
+
/** Elements arrive from BEHIND THE CAMERA onto the layout: each starts
|
|
2072
|
+
* oversized and offset radially (the whole UI plane blown up past the
|
|
2073
|
+
* lens), soft and translucent, then flies into its exact layout slot,
|
|
2074
|
+
* staggered. `dropIn('[data-panel] > *', { from: 3.2, stagger: 0.1 })`.
|
|
2075
|
+
* The container should already be visible; its CHILDREN drop in. */
|
|
1743
2076
|
dropIn: EnsembleKit['dropIn']
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
2077
|
+
/** Elements fly in from scattered planar directions ('edges' | 'center' |
|
|
2078
|
+
* 'above') and settle into their exact layout slots — the tile-wall /
|
|
2079
|
+
* place-on-canvas grammar. Ends at exact identity. */
|
|
1747
2080
|
assemble: EnsembleKit['assemble']
|
|
1748
|
-
|
|
1749
|
-
|
|
2081
|
+
/** Inverse of assemble: elements explode radially out of their layout and
|
|
2082
|
+
* fade. Layout is left clean (hidden, transforms reset). */
|
|
1750
2083
|
scatter: EnsembleKit['scatter']
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
2084
|
+
/** SHOWPIECE — elements fly ballistically through the frame: tossed 'up'
|
|
2085
|
+
* and falling back out, raining 'down', or 'burst'. Items EXIT. Use on a
|
|
2086
|
+
* story payoff (something launched/generated/released), at most 1–2
|
|
2087
|
+
* showpieces per film — on an ambient beat this is noise. */
|
|
1755
2088
|
fountain: EnsembleKit['fountain']
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
2089
|
+
/** SHOWPIECE — generated celebration burst (no authored elements):
|
|
2090
|
+
* particles with deterministic ballistics, spin, flutter, fade.
|
|
2091
|
+
* `confetti({ at: '[data-submit]' })`. ONE per film, ONLY on a genuine
|
|
2092
|
+
* payoff beat (shipped / delivered / won). */
|
|
1760
2093
|
confetti: EnsembleKit['confetti']
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2094
|
+
/** Gallery belt: the container's row of items scrolls continuously through
|
|
2095
|
+
* a clipped viewport (children duplicated once; the wrap is seamless).
|
|
2096
|
+
* A showcase beat — logos, covers, results — never ambient filler. */
|
|
1764
2097
|
river: EnsembleKit['river']
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
2098
|
+
/** Bento reflow: tiles in one grid continuously trade space — spans change
|
|
2099
|
+
* and every tile FLIPs to its measured new slot, the grid staying full
|
|
2100
|
+
* while cells resize around each other. Ends on the authored layout. */
|
|
1768
2101
|
bento: EnsembleKit['bento']
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
2102
|
+
/** Choreographed waiting — the beat AI products actually live in.
|
|
2103
|
+
* `stream.answer(el, text)` arrives word by word (machine writing, no
|
|
2104
|
+
* caret); `stream.skeleton(el)` shimmers placeholders then resolves into
|
|
2105
|
+
* the real content; `stream.status(el, ['Analyzing', 'Searching 3 sources'])`
|
|
2106
|
+
* names the work. All seek-deterministic. */
|
|
1774
2107
|
stream: StreamKit
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
2108
|
+
/** The impact move. `slam(el)` lands type/cards hard from a perspective
|
|
2109
|
+
* turn; `slam(el, { direction: 'away' })` tips them back out of frame.
|
|
2110
|
+
* DISTORTION BELONGS TO MOTION: 'in' ends at exact identity, 'away' ends
|
|
2111
|
+
* invisible — an element must never come to rest sheared. Pair with
|
|
2112
|
+
* `blur()` across the fast window so the speed reads as smear, not a pop. */
|
|
1780
2113
|
slam: ImpactKit['slam']
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2114
|
+
/** Masks, mattes, and blend composition — the things `clip-path` cannot do.
|
|
2115
|
+
*
|
|
2116
|
+
* `mask.reveal(el, shape, opts?, position?)` and `mask.hide(...)` animate
|
|
2117
|
+
* any element through a moving mask. Shapes: `wipe` (hard directional
|
|
2118
|
+
* edge), `feather` (SOFT directional edge — clip-path cannot do this),
|
|
2119
|
+
* `iris` (hard circle), `radial` (soft circle), `slit` (letterbox opening),
|
|
2120
|
+
* `barn` (panels part from centre), `blinds` (N bands), `shape` (your own
|
|
2121
|
+
* clip-path via `opts.shape`). Opts `{ direction?: 'left'|'right'|'up'|
|
|
2122
|
+
* 'down', softness?: 0-1, bands?, at?: {x,y}, duration?, ease? }`.
|
|
2123
|
+
* Prefer `feather` over `wipe` for anything soft or premium — a hard edge
|
|
2124
|
+
* on a full-frame reveal reads as cheap.
|
|
2125
|
+
*
|
|
2126
|
+
* `mask.matte(target, source, opts?, position?)` makes `source`'s
|
|
2127
|
+
* silhouette the target's visibility — v1 snapshots the source ONCE, so
|
|
2128
|
+
* animating the source will not move the matte; use `{ sweep: true }` to
|
|
2129
|
+
* travel it across the target instead.
|
|
2130
|
+
*
|
|
2131
|
+
* `mask.fill(el, { gradient? | image?, drift? })` fills an element's GLYPHS
|
|
2132
|
+
* with a live layer and drifts it (background-clip: text).
|
|
2133
|
+
*
|
|
2134
|
+
* `mask.blend(el, mode, opts?, position?)` composites a layer against its
|
|
2135
|
+
* beat. Modes: screen, multiply, overlay, difference, exclusion,
|
|
2136
|
+
* color-dodge, soft-light, luminosity, plus-lighter, normal. `screen` for
|
|
2137
|
+
* light on dark, `multiply` for ink on cream, `luminosity` for duotone. */
|
|
1805
2138
|
mask: MaskKit
|
|
1806
2139
|
}
|
|
1807
2140
|
|