import { C as Mat2x3, r as DisplayListBuilder } from "./displayList.js"; import { U as NodeProps, V as Node, W as PropInit, o as Group, u as Path, z as EvalContext } from "./nodes.js"; import { s as Region } from "./diff.js"; import { l as Place } from "./each.js"; import { BindableSignal, EaseSpec, PathValue, ReadonlySignal, Rng, Track, Vec2, Vec2Signal } from "@glissade/core"; //#region src/motionPath.d.ts /** An arc-length-parameterized sampler over a path. */ interface PathSampler { /** total arc length */ readonly length: number; /** point at arc-length s (clamped to [0, length]) */ at(s: number): Vec2; /** unit tangent at arc-length s (forward direction of travel) */ tangentAt(s: number): Vec2; /** point at normalized progress u in [0, 1] */ atProgress(u: number): Vec2; /** unit tangent at normalized progress u in [0, 1] */ tangentAtProgress(u: number): Vec2; } /** * Build a reusable arc-length sampler. Densely samples each cubic into a * cumulative-length polyline (samplesPerSegment, default 32) so `at`/`tangent` * are simple span lerps — smooth enough for motion, no per-call bezier solve. */ declare function motionPath(path: PathValue, opts?: { samplesPerSegment?: number; }): PathSampler; /** Total arc length of a path. */ declare function pathLength(path: PathValue): number; /** Point at arc-length s along a path (clamped to [0, length]). */ declare function pointAtLength(path: PathValue, s: number): Vec2; interface FollowPathProps extends NodeProps { /** the node to move along the path; its position (and rotation, if orient) is owned by this */ target: Node; /** a static PathValue, or a Path node followed LIVE (re-sampled as its `data` morphs) */ path: PathValue | Path; /** 0→1 position along the path's arc length; default 1 (the end). Track `/progress`. */ progress?: PropInit; /** rotate the target to the path tangent — a cursor that points where it heads; default false */ orient?: boolean; /** degrees added to the orient angle (e.g. if the sprite points up at rest) */ orientOffset?: number; samplesPerSegment?: number; } /** * A companion node that drives `target` along `path` as `progress` animates. * Owns the target's `position` (and `rotation` when `orient`) via pull-based * binding, so there's no eval-order side effect. Add it to the scene (its * `progress` is the animatable target); it draws nothing itself. */ declare class FollowPath extends Node { readonly target: Node; readonly progress: BindableSignal; constructor(props: FollowPathProps); protected draw(): void; } /** `children: [route, cursor, followPath(cursor, route, { orient: true })]` — cursor traces the route. * Pass the Path *node* to follow it as it morphs; pass a PathValue for a fixed route. */ declare function followPath(target: Node, path: PathValue | Path, props?: Omit): FollowPath; //#endregion //#region src/orient.d.ts interface OrientToPathProps extends NodeProps { /** the node whose `rotation` this owns (position is left to whatever drives it) */ target: Node; /** a static PathValue, or a Path node followed LIVE (re-sampled as its `data` morphs) */ path: PathValue | Path; /** 0→1 arc-length position whose TANGENT sets the angle; default 1. Track `/progress`. */ progress?: PropInit; /** degrees added to the tangent angle (e.g. if the sprite points up at rest) */ offset?: number; samplesPerSegment?: number; } /** * Owns `target.rotation`, binding it to the path tangent at `progress` — a node * banks to face the direction of travel while its POSITION is driven elsewhere * (keyframes, layout, or a sibling followPath sharing the same `progress`). The * rotation-only half of followPath's `orient`. Add it to the scene; it draws * nothing. Animate `/progress`. */ declare class OrientToPath extends Node { readonly target: Node; readonly progress: BindableSignal; constructor(props: OrientToPathProps); protected draw(): void; } /** `children: [node, orientToPath(node, route, { progress: 0.5 })]` — node banks * to the route's direction at progress 0.5 while its position comes from elsewhere. */ declare function orientToPath(target: Node, path: PathValue | Path, props?: Omit): OrientToPath; interface LookAtProps extends NodeProps { /** the node whose `rotation` this owns */ target: Node; /** the node to face — `target` rotates so its +x axis points at `at`'s origin */ at: Node; /** degrees added to the facing angle (e.g. if the sprite points up at rest, pass -90) */ offset?: number; } /** * Owns `target.rotation`, aiming target's local +x axis at `at`'s world origin — * a turret tracking a mover, an arrow pointing at a label. The angle is computed * in WORLD space and applied as `target`'s LOCAL rotation, which is exact when * target's parent is unrotated (the common case); a rotated parent would need * the parent's world rotation subtracted (v1 does not). Pure: rotation re-derives * from both nodes' positions each read, no stored state. Add it to the scene; it * draws nothing. */ declare class LookAt extends Node { readonly target: Node; readonly at: Node; constructor(props: LookAtProps); protected draw(): void; } /** `children: [turret, mover, lookAt(turret, mover)]` — turret always faces the mover. */ declare function lookAt(target: Node, at: Node, props?: Omit): LookAt; //#endregion //#region src/shake.d.ts interface ShakeSpec { /** Seed for the deterministic noise — same seed ⇒ same wobble, every run. */ seed: number; /** Peak translation amplitude in px (±); default 0 (no positional jitter). */ translate?: number; /** Peak rotation amplitude in degrees (±); default 0 (no rotational jitter). */ rotate?: number; /** Noise cycles per second (higher = twitchier); default 8. */ frequency?: number; } /** The shake spec applied to `node` via {@link shake}, or undefined — the seam an * exporter uses to emit an honest "shake is render-only" warn (never a silent drop). */ declare function shakenSpec(node: Node): ShakeSpec | undefined; /** * The pure per-time shake offset for a spec: `{ dx, dy }` px + `dr` degrees, each * a deterministic function of `(seed, t)`. Both the {@link shake} node driver and * the Camera whole-frame shake fold this in. */ declare function shakeOffset(spec: ShakeSpec, t: number): { dx: number; dy: number; dr: number; }; /** * A shake transform about the point `p` (parent space): translate by `(dx, dy)` * then rotate `dr` degrees about `p`, so a rotational jitter spins the node around * its own origin rather than the parent's. */ /** * Jitter `node`'s pose with deterministic value noise, then return it (mutate-and- * return, like Grid/orientToPath). SEPARATE `translate` (px) / `rotate` (deg) / * `frequency` (Hz) amplitudes; pass at least one nonzero amplitude. The jitter is * a parent-space offset applied at emit, so it composes with any existing driver. * * `children: [shake(cursor, { seed: 7, translate: 3 })]` — the cursor wobbles ±3px * around wherever else it is (its position track, a followPath, …). */ declare function shake(node: Node, spec: ShakeSpec): Node; //#endregion //#region src/camera.d.ts /** Thrown for a mis-built or off-safe-area camera (fail loud, never a silent no-op). */ declare class CameraError extends Error { constructor(message: string); } /** * One depth layer of a camera rig. `depth` lives on the WRAPPER (not a per-Node * prop, so the base Node/golden contract is untouched): 1 = the focal plane * (default), <1 = farther (parallax: pans less), >1 = nearer (pans more). */ interface CameraLayer { content: Node; depth?: number; } interface CameraProps extends NodeProps { /** Focal / pan target in RELATIVE viewport coords ([0.5,0.5]=center); default center. */ center?: PropInit; /** Scale about the focal point; default 1. */ zoom?: PropInit; /** Camera roll in degrees; default 0. */ roll?: PropInit; /** Optional whole-frame shake folded into the pose. */ shake?: ShakeSpec; /** * 0.65 — NODE-FRAMING: center the focal point on the node with this id (its * WORLD center in px), resolved at emit through `ctx.resolveNode`. When set, the * relative `center` is ignored — the camera tracks the node wherever it moves. * The world focal is fed DIRECTLY to the pose (no px→rel→px round-trip), and the * resolved focal point is readable inspection-only via `resolveAt(scene, * '/resolvedCenter', t)`. */ centerOn?: string; /** * 0.65 — with `centerOn`, nudge the focal point (vertically) so the target * node's BOUNDS clear this reserved {@link Region} (e.g. a caption band). The * signed minimal push that removes the overlap; direction is DERIVED (toward the * larger free region, ties → up), integer-stable. Fails loud if the node is * taller than the clearable area. Ingested through the SAME `validateRegion` * boundary critique's `safeAreas` uses — a hand-built Region ≡ a * `captionSafeArea(size)` Region. */ clear?: Region; } /** * The per-layer inverse-camera-pose matrix (pure — the render math, exported for * unit tests). Maps WORLD → SCREEN as * T(screenCenter) · scale(zoom) · rotate(roll) · T(−effectiveCenter) * where `effectiveCenter = screenCenter + (focalPx − screenCenter)·depth` scales * the PAN by the layer's depth (far layers, depth<1, pan less). `centerRel` is the * RELATIVE focal point ([0.5,0.5]=screen center); `roll` is degrees. */ declare function cameraLayerMatrix(size: { w: number; h: number; }, centerRel: Vec2, zoom: number, roll: number, depth: number): Mat2x3; /** * The px-native per-layer pose (the canonical core {@link cameraLayerMatrix} calls * through). `focalPx` is the ABSOLUTE world-px focal point — the relative-center * path multiplies by `size` to reach it, while `centerOn` supplies the node's * WORLD center directly (no px→rel→px round-trip, so no double-division drift). The * size-derived focal is NEVER written back into the trackable `center` Vec2Signal. */ declare class Camera extends Group { #private; get describeType(): string; /** Focal / pan target, RELATIVE viewport coords. Track `cam/center(.x/.y)`. */ readonly center: Vec2Signal; /** Scale about the focal point. Track `cam/zoom`. */ readonly zoom: BindableSignal; /** Camera roll, degrees. Track `cam/roll`. */ readonly roll: BindableSignal; /** Resolved layers (content + depth), parallel to `children`. */ readonly layers: readonly Required[]; /** 0.65 — the node id the focal point tracks (world-space), or undefined. */ readonly centerOn: string | undefined; /** * 0.65 — INSPECTION-ONLY resolved focal point (world px, INCLUDING the clear * nudge) — the SINGLE computed sample the render actually uses. Read it via * `resolveAt(scene, '/resolvedCenter', t)`. DERIVED / read-only: it is not * author-settable (binding/setting it fails loud). Present only when `centerOn` * is set, so a plain camera registers no new target and stays byte-identical. */ readonly resolvedCenter: ReadonlySignal | undefined; /** The whole-frame shake spec, if any — read by exporters (render-only, so it is * warned + not baked into Lottie keyframes). */ get shakeSpec(): ShakeSpec | undefined; constructor(layers: CameraLayer[], props?: CameraProps); protected draw(out: DisplayListBuilder, ctx: EvalContext): void; } /** * Build a {@link Camera} rig (lowercase FACTORY — no `new`): `camera(layers, props?)`. * `layers` are depth planes (`{ content, depth? }`); animate `cam/center(.x/.y)`, * `cam/zoom`, `cam/roll` with tracks for push-ins, pans, and rolls. * * `children: [camera([{ content: bg, depth: 0.3 }, { content: fg }], { id: 'cam' }), caption]` * — `caption` is a SIBLING (outside the rig), so it stays pinned while the camera moves. */ declare function camera(layers: CameraLayer[], props?: CameraProps): Camera; //#endregion //#region src/particles.d.ts /** Hard cap on the slot pool. `count` over this THROWS (never silent-clamps). */ declare const MAX_PARTICLE_COUNT = 200; /** A life-fraction curve: `u` in [0,1] (age/lifetime) → a scalar (opacity/scale). */ type OverLife = (u: number) => number; /** A spawn area spread around the origin (px), for scattering (drift) vs a point (sparks). */ type AreaSpec = { kind: 'box'; w: number; h: number; } | { kind: 'disc'; radius: number; }; /** Constant accelerations folded into the per-step integration. */ interface ParticleForces { /** px/s², applied on +y (down). */ gravity?: number; /** velocity damping coefficient (1/s): `v -= v*drag*dt`. */ drag?: number; /** px/s² wind acceleration `[ax, ay]`. */ wind?: readonly [number, number]; } /** The mutable per-particle physics state (also what the `step` escape-hatch mutates). */ interface ParticleState { x: number; y: number; vx: number; vy: number; /** rotation, degrees. */ rot: number; /** angular velocity, deg/s. */ spin: number; /** seconds since emit. */ age: number; /** this particle's lifetime, seconds. */ life: number; } /** The per-slot authoring context handed to `appearance`. */ interface ParticleAppearanceContext { /** Slot index, 0..count-1. */ i: number; /** Slot pool size (== count). */ n: number; /** Seeded per-slot generator (`each`'s `random(mix(seed, i))`). */ rng: Rng; /** The resolved base seed. */ seed: number; } /** * The appearance of one slot: a node, optionally with per-slot over-life curves * (which override the spec-level defaults). The escape hatch — any Node subtree * (a themed dot, a glyph Text, a small Group) works. */ interface ParticleAppearance { node: Node; opacityOverLife?: OverLife; scaleOverLife?: OverLife; } interface ParticleSpec { /** Stable id prefix — slots are `${id}/${i}`, the wrapping group is `${id}`. */ id: string; /** MAX-CONCURRENT live-particle pool size (ring buffer). Bounded by MAX_PARTICLE_COUNT. */ count: number; /** Seed for the physics rng; defaults to a stable `hashStr(id)`. Reseeded per call. */ seed?: number; /** The pixel frame the RELATIVE origin resolves against (typically the scene size). */ box: { w: number; h: number; }; /** Continuous emission (particles/sec). Supply this and/or `burst`. */ rate?: number; /** Instantaneous emission — `n` particles at t=0, or timed bursts. */ burst?: number | readonly { at: number; n: number; }[]; /** Per-particle lifetime, seconds — a scalar or a `[min,max]` range. */ lifetime: number | readonly [number, number]; /** Total sim seconds (the bake duration). */ duration: number; /** Bake frame grid (match the render fps). */ fps: number; /** Spawn point in RELATIVE viewport coords ([0.5,0.5]=center), resolved against `box`. */ origin: Place; /** Optional spread around the origin (px). */ area?: AreaSpec; /** * Safe-area clamp (0.57.1): no particle spawns BELOW this RELATIVE Y (`safeBottom * * box.h`), so ambient motes never drift into a lower-third caption band. Relative * [0,1] — NOT a pixel Y. Must sit at/below the spawn band's top (a `safeBottom` above * the band top leaves no valid spawn region → throws). The framework can't know a * consumer's captionTop, so this is the opt-in PRECISE clamp; the `drift` preset also * ships a conservative DEFAULT band that clears a standard lower-third by itself. */ safeBottom?: number; /** Polar initial velocity — `speed` px/s, `angle` degrees (0 = +x / right). */ velocity: { speed: readonly [number, number]; angle: readonly [number, number]; }; /** Constant forces. */ forces?: ParticleForces; /** Optional angular-velocity range (deg/s) — emits a rotation channel when present. */ spin?: readonly [number, number]; /** Slot appearance — a Node, or `{ node, opacityOverLife?, scaleOverLife? }`. Escape hatch. */ appearance: (i: number, ctx: ParticleAppearanceContext) => Node | ParticleAppearance; /** Spec-level opacity-over-life default (per-slot appearance wins). */ opacityOverLife?: OverLife; /** Spec-level scale-over-life default — emits a scale channel when present. */ scaleOverLife?: OverLife; /** ESCAPE HATCH: replace the built-in force integration with a raw per-particle step. */ step?: (p: ParticleState, dt: number, rng: Rng) => void; } interface ParticlesResult { /** The wrapping group (`id`) holding every VISIBLE slot node. Draw THIS. */ node: Group; /** The baked position/opacity/(scale/rotation) tracks — inject with `tl.tracks(...)`. */ tracks: Track[]; /** The sim end (== duration). */ end: number; } /** Thrown for a mis-built emitter (fail loud, never a silent no-op / clamp). */ declare class ParticleError extends Error { constructor(message: string); } declare function particles(spec: ParticleSpec): ParticlesResult; /** Overridable ParticleSpec fields common to the presets (the `...rest` escape hatch). */ interface ParticlePresetRest { seed?: number; lifetime?: number | readonly [number, number]; velocity?: { speed: readonly [number, number]; angle: readonly [number, number]; }; forces?: ParticleForces; spin?: readonly [number, number]; area?: AreaSpec; /** Safe-area clamp (relative [0,1]) — no spawn below this Y. See ParticleSpec.safeBottom. */ safeBottom?: number; opacityOverLife?: OverLife; scaleOverLife?: OverLife; appearance?: (i: number, ctx: ParticleAppearanceContext) => Node | ParticleAppearance; step?: (p: ParticleState, dt: number, rng: Rng) => void; } interface DriftOptions extends ParticlePresetRest { box: { w: number; h: number; }; duration: number; fps: number; /** Max-concurrent motes (default 24 — a corporate-safe low density, NOT 200). */ count?: number; /** Continuous emission rate, particles/sec (default 8). */ rate?: number; /** Spawn point, relative viewport coords (default centered [0.5,0.5] — the conservative caption-safe band). */ origin?: Place; /** Themed mote color (default a soft blue). */ color?: string; /** Mote radius px (default 2.5). */ radius?: number; /** Id prefix (default 'drift'). */ id?: string; } /** * `drift` — ambient low-opacity motes slowly floating up, complementing a bokeh * background. Continuous low-rate emission; DEFAULTS to a small max-concurrent * count so the exported layer count stays proportional to the live particles. */ declare function drift(opts: DriftOptions): ParticlesResult; interface SparksOptions extends ParticlePresetRest { box: { w: number; h: number; }; duration: number; fps: number; /** Max-concurrent (== burst) count (default 20). */ count?: number; /** Beat second the burst fires at (default 0). */ at?: number; /** Themed spark color (default a warm amber). */ color?: string; /** Spark radius px (default 2.5). */ radius?: number; /** Id prefix (default 'sparks'). */ id?: string; } /** * `sparks` — a subtle, corporate-safe radial impact burst (a win-beat / habit-stamp * flourish): short-life dots thrown outward from `origin`, shrinking + fading with * a touch of gravity. LOW density by default. */ declare function sparks(origin: Place, opts: SparksOptions): ParticlesResult; interface DispenseOptions extends SparksOptions { /** Emission direction, degrees (default 90 = downward, the vending "drop"). */ angle?: number; /** Half-spread around the direction, degrees (default 32). */ spread?: number; /** A themed GLYPH character to sparkle instead of a dot (e.g. '✦', '★'). */ glyph?: string; /** Glyph font size px (default 14). */ glyphSize?: number; /** Glyph font family (default 'DejaVu Sans'). */ glyphFamily?: string; } /** * `dispense` — a directional `sparks` variant: a small themed sparkle emanating in * one direction at a beat (the vending "AS ASKED" flourish ON the drop moment, not * a continuous stream). Directional angle bias + an optional glyph node-template. */ declare function dispense(origin: Place, opts: DispenseOptions): ParticlesResult; //#endregion //#region src/kenBurns.d.ts /** Thrown for a mis-called kenBurns (fail loud, never a silent no-op). */ declare class KenBurnsError extends Error { constructor(message: string); } interface KenBurnsOptions { /** * The zoom endpoints on `/scale` (uniform — applied to both axes). DEFAULT `[1, 1.1]` * (a gentle push-in). A bare number `N` means `[staticCurrentScale, N]` — "zoom in from the * node's rest scale to N" (the defaulted `from` reads the STATIC constructed `scale`). A tuple * `[from, to]` is explicit both ends (push-in `[1, 1.1]` OR pull-out `[1.1, 1]`). */ zoom?: number | [from: number, to: number]; /** * The pan on `/position`. DEFAULT: no pan (zoom-only is valid). `[dx, dy]` is an OFFSET * drift — `from` = the STATIC constructed `position`, `to` = `from + [dx, dy]`. `{ from, to }` * is explicit endpoints. */ pan?: [dx: number, dy: number] | { from: Vec2; to: Vec2; }; /** The shot span in seconds; the tracks run `[at, at + duration]`. DEFAULT `5`. */ duration?: number; /** The easing arriving at the end pose. DEFAULT `'easeInOutSine'`. */ ease?: EaseSpec; /** Start time in seconds. DEFAULT `0`. */ at?: number; } interface KenBurnsResult { /** The emitted `/scale` (+ optional `/position`) tracks — inject with `tl.tracks(...)`. */ tracks: Track[]; /** The shot end (`at + duration`). */ end: number; } /** * Emit a Ken Burns pan/zoom on an EXISTING node's own `/scale` + `/position`. * * const photo = new Image({ id: 'photo', assetId: 'sunset', width: 1280, height: 720 }); * tl.tracks(kenBurns(photo, { zoom: [1, 1.15], pan: [-40, 20], duration: 6 }).tracks); * * `target` MUST have an `id` (the track targets are `/scale` / `/position`). The * defaulted `from` reads the node's STATIC rest value; if you've also authored a * scale/position track on this node, pass an explicit `from`. */ declare function kenBurns(target: Node, opts?: KenBurnsOptions): KenBurnsResult; //#endregion export { type AreaSpec, Camera, CameraError, type CameraLayer, type CameraProps, type DispenseOptions, type DriftOptions, FollowPath, type FollowPathProps, KenBurnsError, type KenBurnsOptions, type KenBurnsResult, LookAt, type LookAtProps, MAX_PARTICLE_COUNT, OrientToPath, type OrientToPathProps, type OverLife, type ParticleAppearance, type ParticleAppearanceContext, ParticleError, type ParticleForces, type ParticlePresetRest, type ParticleSpec, type ParticleState, type ParticlesResult, type PathSampler, type ShakeSpec, type SparksOptions, camera, cameraLayerMatrix, dispense, drift, followPath, kenBurns, lookAt, motionPath, orientToPath, particles, pathLength, pointAtLength, shake, shakeOffset, shakenSpec, sparks };