kuinetic 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +93 -10
  2. package/dist/esm/advanced/index.mjs +4727 -0
  3. package/dist/esm/chunk-AEQGC2KM.mjs +189 -0
  4. package/dist/esm/chunk-MQSNMYRP.mjs +9332 -0
  5. package/dist/esm/chunk-RVTYN6NZ.mjs +3446 -0
  6. package/dist/esm/chunk-U43BX5O2.mjs +2984 -0
  7. package/dist/esm/core/index.mjs +16 -9
  8. package/dist/esm/effects/index.mjs +19 -4
  9. package/dist/esm/index.mjs +17 -10
  10. package/dist/kuinetic.advanced.js +5070 -0
  11. package/dist/kuinetic.advanced.min.js +749 -0
  12. package/dist/kuinetic.all.js +15218 -5595
  13. package/dist/kuinetic.all.min.js +2 -0
  14. package/dist/kuinetic.css +2900 -360
  15. package/dist/kuinetic.js +15217 -5593
  16. package/dist/kuinetic.min.css +1 -0
  17. package/dist/kuinetic.min.js +7 -0
  18. package/dist/types/3d/angles.d.ts +30 -0
  19. package/dist/types/3d/flattening.d.ts +49 -0
  20. package/dist/types/3d/index.d.ts +36 -0
  21. package/dist/types/3d/model-3d.d.ts +115 -0
  22. package/dist/types/3d/register.d.ts +26 -0
  23. package/dist/types/3d/webgl.d.ts +24 -0
  24. package/dist/types/advanced/audio.d.ts +322 -0
  25. package/dist/types/advanced/base.d.ts +233 -0
  26. package/dist/types/advanced/camera-3d.d.ts +236 -0
  27. package/dist/types/advanced/fluid-cursor.d.ts +149 -0
  28. package/dist/types/advanced/gl-utils.d.ts +52 -0
  29. package/dist/types/advanced/glsl.d.ts +60 -0
  30. package/dist/types/advanced/index.d.ts +23 -0
  31. package/dist/types/advanced/particles.d.ts +108 -0
  32. package/dist/types/advanced/scenes.d.ts +207 -0
  33. package/dist/types/advanced/shaders.d.ts +972 -0
  34. package/dist/types/browser/boot.d.ts +72 -0
  35. package/dist/types/core/activation.d.ts +227 -2
  36. package/dist/types/core/animator.d.ts +446 -11
  37. package/dist/types/core/attrs.d.ts +9 -0
  38. package/dist/types/core/breakpoints.d.ts +260 -0
  39. package/dist/types/core/bundles.d.ts +71 -0
  40. package/dist/types/core/callback.d.ts +64 -0
  41. package/dist/types/core/capabilities.d.ts +47 -0
  42. package/dist/types/core/channels.d.ts +120 -2
  43. package/dist/types/core/cloak-selectors.d.ts +47 -0
  44. package/dist/types/core/compile.d.ts +207 -2
  45. package/dist/types/core/control.d.ts +218 -0
  46. package/dist/types/core/declarations.d.ts +128 -0
  47. package/dist/types/core/derived/aggregate.d.ts +43 -0
  48. package/dist/types/core/derived/book.d.ts +30 -0
  49. package/dist/types/core/derived/diagnostics.d.ts +51 -0
  50. package/dist/types/core/derived/group-gate.d.ts +62 -0
  51. package/dist/types/core/derived/install.d.ts +110 -0
  52. package/dist/types/core/derived/late-matches.d.ts +19 -0
  53. package/dist/types/core/derived/types.d.ts +108 -0
  54. package/dist/types/core/easing.d.ts +57 -0
  55. package/dist/types/core/element-config.d.ts +47 -6
  56. package/dist/types/core/element-size.d.ts +23 -0
  57. package/dist/types/core/event-sources.d.ts +39 -0
  58. package/dist/types/core/flip.d.ts +34 -1
  59. package/dist/types/core/gesture.d.ts +12 -0
  60. package/dist/types/core/host-facts.d.ts +155 -0
  61. package/dist/types/core/index.d.ts +7 -3
  62. package/dist/types/core/owned-styles.d.ts +129 -2
  63. package/dist/types/core/params.d.ts +48 -0
  64. package/dist/types/core/parse.d.ts +28 -1
  65. package/dist/types/core/register-into.d.ts +29 -0
  66. package/dist/types/core/registry.d.ts +10 -3
  67. package/dist/types/core/repeat.d.ts +91 -0
  68. package/dist/types/core/sequence.d.ts +126 -0
  69. package/dist/types/core/stagger-config.d.ts +199 -0
  70. package/dist/types/core/stagger-keys.d.ts +63 -0
  71. package/dist/types/core/stagger.d.ts +187 -6
  72. package/dist/types/core/style-plan.d.ts +21 -16
  73. package/dist/types/core/target.d.ts +128 -0
  74. package/dist/types/core/threshold-reachability.d.ts +45 -0
  75. package/dist/types/core/time-scale.d.ts +9 -0
  76. package/dist/types/core/toggle-actions.d.ts +141 -0
  77. package/dist/types/core/travel.d.ts +59 -0
  78. package/dist/types/core/types.d.ts +705 -13
  79. package/dist/types/core/unquoted-selectors.d.ts +53 -0
  80. package/dist/types/effects/carousel/drag.d.ts +80 -0
  81. package/dist/types/effects/carousel/index.d.ts +149 -0
  82. package/dist/types/effects/catalog/background-media.d.ts +152 -0
  83. package/dist/types/effects/catalog/discrete.d.ts +13 -0
  84. package/dist/types/effects/catalog/interaction-proximity.d.ts +24 -0
  85. package/dist/types/effects/catalog/interaction-reveal.d.ts +248 -0
  86. package/dist/types/effects/catalog/interaction-shared.d.ts +42 -0
  87. package/dist/types/effects/catalog/interaction-states.d.ts +68 -0
  88. package/dist/types/effects/catalog/materials.d.ts +67 -0
  89. package/dist/types/effects/catalog/media-shared.d.ts +3 -2
  90. package/dist/types/effects/catalog/numbers-shared.d.ts +8 -5
  91. package/dist/types/effects/catalog/shared.d.ts +2 -2
  92. package/dist/types/effects/catalog/text-shared.d.ts +92 -10
  93. package/dist/types/effects/catalog/transforms.d.ts +13 -0
  94. package/dist/types/effects/catalog/view-transitions.d.ts +13 -0
  95. package/dist/types/effects/forms/primitives.d.ts +15 -4
  96. package/dist/types/effects/gestures/index.d.ts +53 -0
  97. package/dist/types/effects/index.d.ts +5 -0
  98. package/dist/types/effects/layout/presets.d.ts +35 -0
  99. package/dist/types/effects/motion-path/index.d.ts +35 -0
  100. package/dist/types/effects/scroll-mechanics/params.d.ts +23 -0
  101. package/dist/types/effects/scroll-mechanics/presets.d.ts +51 -0
  102. package/dist/types/effects/scroll-mechanics/section-index.d.ts +47 -0
  103. package/dist/types/effects/shared.d.ts +162 -2
  104. package/dist/types/effects/step-index.d.ts +121 -0
  105. package/dist/types/effects/step-marking.d.ts +41 -33
  106. package/dist/types/effects/svg/icon-parts.d.ts +23 -0
  107. package/dist/types/effects/three-d/flip-parts.d.ts +46 -0
  108. package/dist/types/effects/tween/index.d.ts +57 -0
  109. package/dist/types/effects/tween/properties.d.ts +107 -0
  110. package/dist/types/effects/tween/waypoints.d.ts +106 -0
  111. package/dist/types/showcase/compare.d.ts +4 -0
  112. package/dist/types/showcase/device-frame.d.ts +3 -0
  113. package/dist/types/showcase/hotspots.d.ts +3 -0
  114. package/dist/types/showcase/index.d.ts +30 -0
  115. package/dist/types/showcase/lightbox.d.ts +3 -0
  116. package/dist/types/showcase/media-source.d.ts +13 -0
  117. package/dist/types/showcase/modal-shell.d.ts +22 -0
  118. package/dist/types/showcase/scroll-story.d.ts +4 -0
  119. package/dist/types/showcase/shared.d.ts +47 -0
  120. package/dist/types/showcase/slideshow.d.ts +3 -0
  121. package/dist/types/showcase/slow-mo.d.ts +5 -0
  122. package/package.json +14 -7
  123. package/dist/esm/chunk-5DON3UFQ.mjs +0 -4380
  124. package/dist/esm/chunk-JT4PZL3A.mjs +0 -773
  125. package/dist/esm/chunk-QUVFODSQ.mjs +0 -1278
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Shared primitives and runtime utilities for kUInetic Advanced Modules.
3
+ */
4
+ import type { EffectInstance, PrepareContext, Preset, Primitive } from '../core/types.js';
5
+ import type { StyleLedger } from '../core/owned-styles.js';
6
+ import type { Registry } from '../core/registry.js';
7
+ import type { Animator } from '../core/animator.js';
8
+ export interface WindowLike {
9
+ innerWidth?: number;
10
+ innerHeight?: number;
11
+ devicePixelRatio?: number;
12
+ performance?: {
13
+ now?: () => number;
14
+ };
15
+ addEventListener?: (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) => void;
16
+ removeEventListener?: (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) => void;
17
+ requestAnimationFrame?: (fn: FrameRequestCallback) => number;
18
+ cancelAnimationFrame?: (id: number) => void;
19
+ getComputedStyle?: (el: Element) => CSSStyleDeclaration;
20
+ }
21
+ export interface DocumentLike {
22
+ body?: {
23
+ appendChild?: (node: unknown) => unknown;
24
+ } | null;
25
+ createElement?: (tagName: string) => unknown;
26
+ }
27
+ export type AnyWindow = Window | WindowLike | null;
28
+ export type AnyDocument = Document | DocumentLike | null;
29
+ export type RafFunction = (fn: FrameRequestCallback) => number | null;
30
+ export type CafFunction = (id: number | null) => void | null;
31
+ /**
32
+ * A wall-clock delay, and its cancel — the env's answer to "later, once".
33
+ *
34
+ * Separate from {@link RafFunction} because the two answer different questions: `raf` schedules the
35
+ * *next frame*, which is what a loop wants, while this schedules a fixed duration, which is what a
36
+ * grace period wants. A grace period counted in frames is a grace period that never elapses in a
37
+ * background tab — the one place where holding the resource costs the most.
38
+ *
39
+ * It is injectable for the same reason `raf` is: it is a timing decision the host can own, and a
40
+ * suite that cannot drive it either waits out the real duration or leaves the timer pending. Do
41
+ * *not* reach for `window.setTimeout` instead — {@link WindowLike} does not declare it, and the
42
+ * partial window doubles these suites inject would silently never fire.
43
+ *
44
+ * Named to match `defaultGestureDeps` in `core/gesture.ts`, which is the same pattern in core.
45
+ */
46
+ export type SetTimerFunction = (fn: () => void, ms: number) => number | null;
47
+ export type ClearTimerFunction = (id: number | null) => void;
48
+ export interface AdvancedEnv {
49
+ window?: AnyWindow;
50
+ document?: AnyDocument;
51
+ createCanvas?: () => HTMLCanvasElement | null;
52
+ raf?: RafFunction;
53
+ caf?: CafFunction;
54
+ setTimer?: SetTimerFunction;
55
+ clearTimer?: ClearTimerFunction;
56
+ }
57
+ export interface ResolvedEnv {
58
+ window: AnyWindow;
59
+ document: AnyDocument;
60
+ createCanvas: () => HTMLCanvasElement | null;
61
+ raf: RafFunction;
62
+ caf: CafFunction;
63
+ setTimer: SetTimerFunction;
64
+ clearTimer: ClearTimerFunction;
65
+ }
66
+ export interface EffectInstanceOptions {
67
+ continuous?: boolean;
68
+ activate?: () => void;
69
+ cancel?: () => void;
70
+ finish?: () => void;
71
+ destroy?: () => void;
72
+ }
73
+ export declare function clamp(val: number, min: number, max: number): number;
74
+ /**
75
+ * Anything that can hand back one element's inline-style ledger.
76
+ *
77
+ * Deliberately narrower than `LedgerSet`: core's set (`createLedgerSet`) and this directory's
78
+ * shared registry (`createAdvancedLedgers`) both satisfy it, so `styleOf` is one guard for both
79
+ * and a module can be moved from one to the other without touching a single write site.
80
+ */
81
+ export interface ElementLedgers {
82
+ style(el: Element): StyleLedger;
83
+ }
84
+ /**
85
+ * This element's owned-write ledger, or `null` when the object cannot carry one.
86
+ *
87
+ * Every module here manages the author's inline styles through `core/owned-styles.ts` rather than
88
+ * a snapshot of its own, and the reason is timing: `style(el)` builds that element's ledger the
89
+ * first time it is *asked for*, and the ledger records a property's prior value the first time
90
+ * that property is *written*. Asking at the write site is therefore the same thing as "capture
91
+ * immediately before the first write", which is the invariant five rounds of review kept finding
92
+ * broken — a snapshot taken at construction or preparation, restored much later over an author
93
+ * value that had changed in between.
94
+ *
95
+ * The guard exists because `createStyleLedger` reads `hasAttribute('style')` the moment it is
96
+ * built, and these controllers are constructed directly rather than by the animator: `prepare`
97
+ * hands them whatever element the author wrote the attribute on, and the suites hand them `null`,
98
+ * `{}`, and `{ getAttribute: () => null }` to exercise the degenerate paths. One check here beats
99
+ * the same two clauses repeated at every write site.
100
+ *
101
+ * @param ledgers - The controller's ledger source, opened over its host element.
102
+ * @param el - The element about to be written to.
103
+ * @returns Its memoised ledger, or `null` when there is nothing to write to.
104
+ * @complexity O(1).
105
+ */
106
+ export declare function styleOf(ledgers: ElementLedgers, el: Element | null | undefined): StyleLedger | null;
107
+ /**
108
+ * A controller's claim on the shared per-element ledgers.
109
+ *
110
+ * `style` is not guarded — go through {@link styleOf}, which is the one place the "can this object
111
+ * carry a ledger at all" question is answered.
112
+ */
113
+ export interface AdvancedLedgers {
114
+ /** This element's shared ledger, created on first ask by anyone. */
115
+ style(el: Element): StyleLedger;
116
+ /**
117
+ * Give up this controller's claim on every element it wrote to, restoring each one that no other
118
+ * controller still holds.
119
+ *
120
+ * Every element is attempted. One that throws on the way out does not cost the ones after it; the
121
+ * failures are re-thrown together as an `AggregateError` once the sweep is complete.
122
+ */
123
+ restore(): void;
124
+ /** Elements this controller has asked for. Diagnostics and leak assertions. */
125
+ elements(): Element[];
126
+ }
127
+ /**
128
+ * Open one controller's claim on the shared, ref-counted per-element ledgers.
129
+ *
130
+ * The registry itself lives in `core/owned-styles.ts` ({@link createStyleClaim}), and that is not
131
+ * an implementation detail — it is the fix. This tier used to keep a `WeakMap` of its own, holding
132
+ * one animator's private ledger on behalf of everybody, with a per-entry `foreign` flag deciding
133
+ * whether the entry was ever restored. Both halves were decided *once*, by whichever controller
134
+ * arrived first: a second controller's own `hostLedger` was silently ignored, and anything the
135
+ * first controller flagged foreign was never restored by this path at all, so a property written
136
+ * by a controller that outlived the animator outlived every restore that would have unwound it.
137
+ *
138
+ * Ref-counting removes the flag rather than fixing it. Core's `LedgerSet` is now an owner in the
139
+ * same registry, so "the animator will restore this one" is no longer a special case a boolean has
140
+ * to remember — it is simply another claim, and the element is unwound by whichever owner lets go
141
+ * last, controller or animator.
142
+ *
143
+ * There is also no `hostLedger` parameter any more, and its absence is the point. It existed
144
+ * because this tier ships as its own bundle with its own copy of `owned-styles.ts`, so the
145
+ * animator's ledger for the host had to be *handed over* to be shared. That made sharing opt-in at
146
+ * the call site and left two ways to open a capture. The registry now lives on the element itself,
147
+ * under a `Symbol.for` key both bundles compute identically, so a controller and the animator find
148
+ * the same entry by asking — and the only way to open a capture is to ask.
149
+ *
150
+ * What this tier still needs the registry *for* is what it always did. These controllers write to
151
+ * elements they merely **found** — a `camera-layer` under a `camera-scene`, a `scene-step` under a
152
+ * `scene` — and one found element can belong to two controllers. {@link ownedDescendants} removed
153
+ * the *same-kind* half of that overlap; the cross-kind half is a supported authoring shape (an
154
+ * element authored `camera-layer z:-100, scene-step from:0 to:1` genuinely has a camera and a scene
155
+ * writing to it), and a controller constructed directly rather than through `prepare` never went
156
+ * through the scan at all. Both still need one capture between them.
157
+ *
158
+ * @param host - The authored element this controller was prepared on. Released last, for the
159
+ * reason `createLedgerSet` documents: the host carries `data-kui-state`, which is the cloak
160
+ * layer's release key, so it must not become visible before the subtree under it is back to the
161
+ * author's markup.
162
+ * @complexity O(1) per lookup; O(n) space and O(n) time to restore, in elements written to.
163
+ */
164
+ export declare function createAdvancedLedgers(host: Element): AdvancedLedgers;
165
+ /**
166
+ * The descendants carrying `descendantEffect` that `host` actually owns.
167
+ *
168
+ * > A host claims a descendant only when no nearer host **of the same kind** sits between them.
169
+ *
170
+ * `prepareScene` and `prepareCameraScene` used the raw `[data-kui*="…"]` query, which is
171
+ * descendant-wide and does not stop at a nested host — so an **outer** host over-reached into an
172
+ * inner one and both controllers wrote the inner host's children every frame, with different
173
+ * progress values. Listener and rAF order decided which write survived, which is to say nothing
174
+ * decided it. {@link createAdvancedLedgers} made that survivable on *teardown* by sharing one
175
+ * capture per element; it never arbitrated the writes themselves, because `ledger.set()` writes
176
+ * straight through. This is the other half.
177
+ *
178
+ * "Of the same kind" is load-bearing and is not a tidy-up of the rule. An element authored
179
+ * `camera-layer z:-100, scene-step from:0 to:1` under a `camera-scene` under a `scene` genuinely
180
+ * has two owners afterwards — a scene and a camera, writing different properties for different
181
+ * reasons — and `ownership.test.ts`'s 'an element that is both a scene step and a camera layer'
182
+ * is the standing proof that it still does.
183
+ *
184
+ * The `*=` query is a native prefilter and nothing else, so both halves of the rule are predicates
185
+ * run over what it returned: the candidate must itself *declare* `descendantEffect`, and no nearer
186
+ * host of `hostEffect`'s kind may stand between it and `host`. The first half is not redundant with
187
+ * the query. `[data-kui*="camera-layer"]` matches any attribute *containing* that text, which
188
+ * includes a parameter value — `<img data-kui='fade-up target:".camera-layer"'>` merely names the
189
+ * class it animates, and the scan used to claim it and write `preserve-3d` and a `translate3d` over
190
+ * the author's own effect. {@link declaresEffect} is the same name test the parent walk already
191
+ * uses, applied to the element the walk is about.
192
+ *
193
+ * The cost stays O(matched descendants × depth), not a walk of the subtree.
194
+ *
195
+ * @param host - The authored element the controller was prepared on.
196
+ * @param hostEffect - The host's own primitive name, e.g. `scene`. A nearer element declaring it
197
+ * is what blocks the claim.
198
+ * @param descendantEffect - The primitive name to collect, e.g. `scene-step`.
199
+ * @returns The owned descendants, in document order. Empty when `host` cannot be queried.
200
+ */
201
+ export declare function ownedDescendants<T extends Element>(host: Element, hostEffect: string, descendantEffect: string): T[];
202
+ export declare function lerp(start: number, end: number, progress: number): number;
203
+ export declare function defaultRaf(win: Window | WindowLike | null | undefined, fn: FrameRequestCallback): number | null;
204
+ export declare function defaultCaf(win: Window | WindowLike | null | undefined, id: number | null | undefined): null;
205
+ /**
206
+ * `globalThis` rather than the resolved window: the window here is routinely a `WindowLike` double
207
+ * with four properties on it, and `setTimeout` is not one of them. Falling back to the real global
208
+ * keeps the default working in every host that has one and leaves injection as the way to control
209
+ * it, instead of making the default depend on how complete the caller's window happens to be.
210
+ *
211
+ * The `as unknown as number` is the usual Node-vs-DOM split — Node's `setTimeout` answers a
212
+ * `Timeout` object, the DOM's a number — and only the round trip back through `clearTimer` cares.
213
+ */
214
+ export declare function defaultSetTimer(fn: () => void, ms: number): number | null;
215
+ export declare function defaultClearTimer(id: number | null | undefined): void;
216
+ interface ContextWithEnv {
217
+ win?: AnyWindow;
218
+ doc?: AnyDocument;
219
+ reducedMotion?: boolean | {
220
+ enabled?: boolean;
221
+ };
222
+ createCanvas?: () => HTMLCanvasElement | null;
223
+ raf?: RafFunction;
224
+ caf?: CafFunction;
225
+ setTimer?: SetTimerFunction;
226
+ clearTimer?: ClearTimerFunction;
227
+ }
228
+ export declare function resolveEnv(ctx?: ContextWithEnv | PrepareContext | null, env?: AdvancedEnv): ResolvedEnv;
229
+ export declare function isReducedMotion(ctx?: ContextWithEnv | PrepareContext | null): boolean;
230
+ export declare function createEffectInstance(opts?: EffectInstanceOptions): EffectInstance;
231
+ export declare function createInertInstance(destroy?: () => void): EffectInstance;
232
+ export declare function registerInto(target: unknown, primitives: Primitive | Primitive[] | unknown, presets: Preset | Preset[] | unknown, name?: string): Registry | Animator;
233
+ export {};
@@ -0,0 +1,236 @@
1
+ /**
2
+ * kUInetic 3D Virtual Camera & Depth Projection Module
3
+ */
4
+ import type { EffectInstance, EffectParams, PrepareContext, Preset, Primitive } from '../core/types.js';
5
+ import type { Registry } from '../core/registry.js';
6
+ import type { Animator } from '../core/animator.js';
7
+ import { type AdvancedEnv, type WindowLike, type RafFunction, type CafFunction, clamp } from './base.js';
8
+ import { type AudioBand } from './audio.js';
9
+ export { clamp };
10
+ export type CameraParamAccessor = EffectParams | {
11
+ num?: (name: string, fallback?: number) => number;
12
+ text?: (name: string, fallback?: string) => string;
13
+ };
14
+ export interface CameraLayer {
15
+ element: HTMLElement;
16
+ depthZ: number;
17
+ }
18
+ export interface CameraOptions {
19
+ depth?: number;
20
+ mouseTilt?: boolean;
21
+ /** Which `audio-source` band pushes the camera forward, or absent/null for none. */
22
+ audio?: AudioBand | null;
23
+ }
24
+ /**
25
+ * Parse depth value from CSS string.
26
+ */
27
+ export declare function parseDepth(str?: string | null): number;
28
+ /**
29
+ * Pure 3D transform calculation for a single layer.
30
+ */
31
+ export declare function computeLayerTransform(depthZ: number, cameraZ: number, tiltX?: number, tiltY?: number): string;
32
+ export declare class CameraController {
33
+ container: HTMLElement;
34
+ options: CameraOptions;
35
+ env: AdvancedEnv;
36
+ window: Window | WindowLike | null;
37
+ raf: RafFunction;
38
+ caf: CafFunction;
39
+ layers: CameraLayer[];
40
+ cameraZ: number;
41
+ mouse: {
42
+ x: number;
43
+ y: number;
44
+ targetX: number;
45
+ targetY: number;
46
+ };
47
+ rafId: number | null;
48
+ scrollRafId: number | null;
49
+ audioRafId: number | null;
50
+ /** Last value read for the selected band, 0..1. Stays 0 for a scene with no `audio:`. */
51
+ audioLevel: number;
52
+ isListening: boolean;
53
+ /**
54
+ * Container and every depth layer, each ledger opened at that element's first write and shared
55
+ * with every other controller writing to the same element — see `createAdvancedLedgers`.
56
+ *
57
+ * This used to say "an inner scene claims the outer scene's layers", which is backwards: an
58
+ * inner `querySelectorAll` only ever sees its own descendants, so it is the **outer** scene that
59
+ * over-reaches into the inner one. `prepareCameraScene` now scopes the scan with
60
+ * {@link ownedDescendants}, so an outer camera stops at a nested `camera-scene` and the two no
61
+ * longer write one layer's `transform` on alternate frames.
62
+ *
63
+ * Sharing still matters after that fix, and not only for the controllers built directly by the
64
+ * suites: a `camera-layer` that is *also* a `scene-step` has two owners by design, because they
65
+ * are different kinds of host. The ledger is what keeps that case honest.
66
+ */
67
+ private ledgers;
68
+ constructor(container: HTMLElement, options?: CameraOptions, env?: AdvancedEnv);
69
+ addLayer(element: HTMLElement, depthZ: number): void;
70
+ applyLayer3D(): void;
71
+ bindEvents(): void;
72
+ /**
73
+ * The 3D space itself: the container's perspective and every layer's `preserve-3d`.
74
+ *
75
+ * Separated from {@link start} because the reduced-motion path needs exactly this and none of
76
+ * the listeners or loops below it — see {@link renderStatic}.
77
+ */
78
+ private applyStage;
79
+ /**
80
+ * The scene composed once, with the camera parked where its travel begins.
81
+ *
82
+ * What a visitor who asked for reduced motion gets instead of nothing, following the decision
83
+ * `shaders.ts` records: a static frame where one genuinely exists. Here it does, and it is most
84
+ * of the effect — `depth` and each layer's `z:` *are* the composition, and the scroll only moves
85
+ * a camera through an arrangement the author already described. One `render()` at `cameraZ = 0`
86
+ * puts every layer at its authored depth through the container's perspective, so the scene keeps
87
+ * its real scale hierarchy and simply does not travel. Without it the layers are written nothing
88
+ * at all and the whole scene collapses flat — the same bug `start()`'s own `render()` call fixed
89
+ * for the live path.
90
+ *
91
+ * Deliberately *not* the scene's end state, which is where the animator leaves a CSS effect under
92
+ * this policy (`openGate`, `core/animator.ts`). "The end" here is `cameraZ = depth`, i.e. the
93
+ * camera pushed all the way through: a layer then sits at `z = depthZ + depth / 2`, which against
94
+ * a `perspective` of `depth` is a 2x blow-up at `z:0` and 10x at `z:400px` of a 1000px scene, with
95
+ * nearer layers passing the viewer entirely. An end state nobody can see is not a still frame.
96
+ *
97
+ * Runs during `prepare`, because it has to — under reduced motion `openGate` marks the element
98
+ * finished and never activates any instance. See `prepareReducedMotion` in `shaders.ts`.
99
+ */
100
+ renderStatic(): void;
101
+ start(): void;
102
+ stop(): void;
103
+ updateScroll(): void;
104
+ /** The selected band's current value, or 0 for a scene that asked for none. */
105
+ readAudio(): void;
106
+ /**
107
+ * The one frame loop an audio-driven scene runs, doing every read before every write.
108
+ *
109
+ * An audio band changes on its own, without a scroll or a pointer move to hang a frame on, so
110
+ * this scene needs a loop of its own — and then it needs to be the *only* loop, which is why
111
+ * `onScroll` and `startLoop` both stand down while it is running. Three callbacks each reading
112
+ * geometry (`getBoundingClientRect`) or style (`readAudioBand`'s computed fallback) and then
113
+ * writing transforms would force a layout or style recalculation per callback per frame; one
114
+ * pass that reads scroll position, reads the band, steps the pointer spring and only then
115
+ * renders forces at most one.
116
+ */
117
+ startAudioLoop(): void;
118
+ onScroll(): void;
119
+ onMouseMove(e: MouseEvent | {
120
+ clientX: number;
121
+ clientY: number;
122
+ }): void;
123
+ stepMouse(): boolean;
124
+ startLoop(): void;
125
+ /**
126
+ * Tilt the whole stage toward the pointer.
127
+ *
128
+ * The author's own transform is read here, at the first frame that overwrites it, rather than
129
+ * when the effect activated. Between those two moments sits at least one frame in which the
130
+ * page's own code can write a transform, and a capture taken at activation would both overwrite
131
+ * that write and hand the older value back on destroy as if it had never happened.
132
+ *
133
+ * `claim()` is what makes that true without a private snapshot field: it remembers `transform`'s
134
+ * current value the first time this runs and is a no-op on every call after, so `peek()` always
135
+ * answers with whatever was there just before this method's first write — the ledger already
136
+ * tracks that for `restore()`; `peek()` is just asking it without writing.
137
+ */
138
+ private renderStageTilt;
139
+ /**
140
+ * Hand `transform` back when the tilt settles at exactly zero.
141
+ *
142
+ * `peek()` reports only what `restore()` would put back, which is the **inline** capture and
143
+ * nothing else — by design (`owned-styles.ts`, and `test/owned-styles.test.ts` asserts it). So
144
+ * `peek() || 'rotateX(0deg) rotateY(0deg)'` was right only for an author who had written the
145
+ * transform inline. For the ordinary case — `.hero { transform: translateY(-20px) }` in a
146
+ * stylesheet — `peek()` answers `''` and the fallback wrote a *fabricated* identity inline,
147
+ * which outranks the class and switches the author's own transform off the moment the effect
148
+ * activates. `start()` renders immediately with the pointer at the synthetic centre, so that is
149
+ * activation, not some edge case.
150
+ *
151
+ * Removing the declaration instead is what actually lands on "the author's own transform": their
152
+ * inline value if they had one, their stylesheet's if they did not, and nothing at all if
153
+ * neither. The `removeProperty` is not a ledger bypass — `claim()` above has already recorded
154
+ * the property, `peek()` answering `''` *is* the ledger saying it found no inline declaration
155
+ * here, and removal is precisely what its own `restore()` does for that capture.
156
+ *
157
+ * Not fixed here: an inline `transform: scale(1.2) !important` comes back through `peek()`
158
+ * without its priority, so while the effect is live at rest a competing `!important` rule the
159
+ * author had deliberately beaten wins again. `restore()` puts the priority back, so it ends at
160
+ * the right value. Closing that needs a per-property give-back on `StyleLedger` (core).
161
+ *
162
+ * @complexity O(1).
163
+ */
164
+ private restStageTransform;
165
+ render(): void;
166
+ /**
167
+ * `try`/`finally` because the restore must not be hostage to the listener teardown.
168
+ *
169
+ * The animator swallows a throwing instance teardown (`runQuietly(() => instance.destroy())`),
170
+ * and every element this controller reached on its own — the layers — is in no ledger core will
171
+ * ever restore. A `removeEventListener` the page has replaced, or a window torn down under a
172
+ * detached document, would otherwise leave `transform` on each layer permanently.
173
+ */
174
+ destroy(): void;
175
+ }
176
+ export declare function prepareCameraScene(el: Element, params: EffectParams, ctx?: PrepareContext | null): EffectInstance;
177
+ export declare const CAMERA_PARAMETERS: {
178
+ depth: {
179
+ type: "number";
180
+ default: string;
181
+ minimum: number;
182
+ maximum: number;
183
+ cssProperty: string;
184
+ };
185
+ 'mouse-tilt': {
186
+ type: "keyword";
187
+ default: string;
188
+ keywords: string[];
189
+ cssProperty: string;
190
+ };
191
+ /**
192
+ * Which `audio-source` band pushes the camera forward, if any.
193
+ *
194
+ * Same name, same words and the same `off` default as the `shaders` primitive's: one spelling
195
+ * for "follow this band" across this directory rather than a second word per consumer. Opt-in
196
+ * for the reason that one is — the value is written by another primitive, and a scene that read
197
+ * it unasked would start pulsing the moment an unrelated `audio-source` appeared above it.
198
+ *
199
+ * Turning it on also changes *how* the scene renders: see `startAudioLoop`, which becomes the
200
+ * scene's only frame loop.
201
+ */
202
+ audio: {
203
+ type: "keyword";
204
+ default: string;
205
+ keywords: string[];
206
+ cssProperty: string;
207
+ };
208
+ };
209
+ /**
210
+ * `camera-layer`'s own parameters. `z` is read straight off the raw `data-kui` attribute by
211
+ * `prepareCameraScene`'s own regex (see its `layerEls` loop above), not through this primitive's
212
+ * `prepare` — declaring it here is only what lets the compiler accept `z:-400` on a layer element
213
+ * instead of warning "unknown parameter" once "unknown effect" (below) is fixed.
214
+ */
215
+ export declare const CAMERA_LAYER_PARAMETERS: {
216
+ z: {
217
+ type: "number";
218
+ default: string;
219
+ minimum: number;
220
+ maximum: number;
221
+ cssProperty: string;
222
+ };
223
+ };
224
+ /**
225
+ * `camera-layer` never runs on its own: `prepareCameraScene`'s `[data-kui*="camera-layer"]` scan
226
+ * reads each layer's `z:` directly off its attribute, and the parent `CameraController` is what
227
+ * writes `transform`/`transform-style` to it every frame (`applyLayer3D`, `render`). Without a
228
+ * registered primitive for the name, every `camera-layer` element compiled as an unrecognised
229
+ * effect — the scene still positioned it correctly, but the console warned "unknown effect
230
+ * camera-layer" once per layer. This primitive exists only so the name resolves; it deliberately
231
+ * does nothing of its own.
232
+ */
233
+ export declare function prepareCameraLayer(): EffectInstance;
234
+ export declare const CAMERA_PRIMITIVES: Primitive[];
235
+ export declare const CAMERA_PRESETS: Preset[];
236
+ export declare function registerCamera(target: unknown): Registry | Animator;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * kUInetic Fluid Cursor - Interactive Liquid Cursor Trails
3
+ */
4
+ import type { EffectInstance, EffectParams, PrepareContext, Preset, Primitive } from '../core/types.js';
5
+ import type { Registry } from '../core/registry.js';
6
+ import type { Animator } from '../core/animator.js';
7
+ import { type AdvancedEnv, type WindowLike, type DocumentLike, type RafFunction, type CafFunction, clamp } from './base.js';
8
+ export { clamp };
9
+ export type FluidParamAccessor = EffectParams | {
10
+ num?: (name: string, fallback?: number) => number;
11
+ text?: (name: string, fallback?: string) => string;
12
+ };
13
+ export interface FluidDrop {
14
+ x: number;
15
+ y: number;
16
+ vx: number;
17
+ vy: number;
18
+ size: number;
19
+ alpha: number;
20
+ color: string;
21
+ viscosity?: number;
22
+ }
23
+ export interface FluidTrailOptions {
24
+ size?: number;
25
+ viscosity?: number;
26
+ color?: string;
27
+ }
28
+ export interface FluidClient {
29
+ options: FluidTrailOptions;
30
+ el?: Element;
31
+ }
32
+ export declare function stepFluidDrop(drop: FluidDrop, friction: number): boolean;
33
+ /**
34
+ * Size the canvas backing store *and* its CSS box from the same numbers.
35
+ *
36
+ * The CSS box used to be a fixed `100vw`/`100vh` in {@link FluidTrail.setupCanvas}, which reads
37
+ * from the visual/layout viewport rather than `window.innerWidth`/`innerHeight`. On iOS Safari with
38
+ * the address bar visible those two disagree, so the browser scales the backing store to fit the
39
+ * CSS box and a drop spawned at `clientY` renders off from the pointer — the same defect
40
+ * `syncCanvasDimensions` (`shaders.ts`) fixes for the shader tier, by the same move: pin the CSS box
41
+ * to the exact `innerWidth`/`innerHeight` the backing store is derived from, every time either is
42
+ * synced (see {@link syncCanvasStyleBox}).
43
+ */
44
+ export declare function syncCanvasSize(canvas: HTMLCanvasElement | null, win: Window | WindowLike | null): number;
45
+ export declare class FluidTrail {
46
+ options: FluidTrailOptions;
47
+ env: AdvancedEnv;
48
+ window: Window | WindowLike | null;
49
+ document: Document | DocumentLike | null;
50
+ createCanvas: () => HTMLCanvasElement | null;
51
+ raf: RafFunction;
52
+ caf: CafFunction;
53
+ drops: FluidDrop[];
54
+ canvas: HTMLCanvasElement | null;
55
+ ctx: CanvasRenderingContext2D | null;
56
+ rafId: number | null;
57
+ isListening: boolean;
58
+ lastMouse: {
59
+ x: number;
60
+ y: number;
61
+ };
62
+ hasLastMouse: boolean;
63
+ wasOutside: boolean;
64
+ refCount: number;
65
+ clients: Map<number, FluidClient>;
66
+ activeClients: Map<number, FluidClient>;
67
+ private nextClientId;
68
+ onResize?: () => void;
69
+ onPointerMove?: (e: PointerEvent | {
70
+ clientX: number;
71
+ clientY: number;
72
+ }) => void;
73
+ mapKey: object;
74
+ constructor(options?: FluidTrailOptions, env?: AdvancedEnv);
75
+ acquire(): boolean;
76
+ release(): void;
77
+ registerClient(options: FluidTrailOptions, el?: Element): number;
78
+ activateClient(id: number): boolean;
79
+ deactivateClient(id: number): void;
80
+ /**
81
+ * Wipe the overlay when the last client stands down.
82
+ *
83
+ * `draw()` clears the canvas at the *start* of a frame, so stopping the loop leaves whatever was
84
+ * in flight painted. The overlay is `position: fixed` at `z-index: 9998` across the whole
85
+ * viewport, so a `fluid-trail` on `on:pointerenter`/`pointerleave` — or an author's
86
+ * `control.cancel()` mid-move — left a frozen blob of drops over the entire page until something
87
+ * reactivated it or the element was destroyed.
88
+ *
89
+ * The canvas itself stays mounted on purpose: registration happens at prepare, so the last
90
+ * *unregister* owns the teardown and no client can have the canvas pulled from under it.
91
+ *
92
+ * @complexity O(1).
93
+ */
94
+ private clearOverlay;
95
+ unregisterClient(id: number): void;
96
+ setupCanvas(): boolean;
97
+ attachListeners(): void;
98
+ detachListeners(): void;
99
+ init(): boolean;
100
+ private findClientByTarget;
101
+ private findClientByCoords;
102
+ resolveClientOwner(e: {
103
+ clientX: number;
104
+ clientY: number;
105
+ target?: EventTarget | null;
106
+ }): FluidClient | null;
107
+ getOptionsAt(x: number, y: number): FluidTrailOptions | null;
108
+ spawnDrop(x: number, y: number, vx: number, vy: number, customOpts?: FluidTrailOptions): void;
109
+ startLoop(): void;
110
+ stopLoop(): void;
111
+ update(): void;
112
+ draw(): void;
113
+ destroy(): void;
114
+ }
115
+ export declare function getSharedFluidTrail(env?: AdvancedEnv): FluidTrail;
116
+ /**
117
+ * Install (or clear) the trail for one document.
118
+ *
119
+ * Same asymmetry `setSharedShaderRenderer` closes, for the same reason: `destroy()` deletes only
120
+ * `this.mapKey`, so an instance stored under a different document's key outlives its own teardown
121
+ * and is handed to every later lookup for that document with its canvas already removed. The
122
+ * instance's own key wins; a mismatched caller key is cleared rather than left dangling.
123
+ */
124
+ export declare function setSharedFluidTrail(instance: FluidTrail | null, env?: AdvancedEnv): void;
125
+ export declare function prepareFluidCursor(el: Element, params: EffectParams, ctx?: PrepareContext | null): EffectInstance;
126
+ export declare const FLUID_PARAMETERS: {
127
+ size: {
128
+ type: "number";
129
+ default: string;
130
+ minimum: number;
131
+ maximum: number;
132
+ cssProperty: string;
133
+ };
134
+ viscosity: {
135
+ type: "number";
136
+ default: string;
137
+ minimum: number;
138
+ maximum: number;
139
+ cssProperty: string;
140
+ };
141
+ color: {
142
+ type: "color";
143
+ default: string;
144
+ cssProperty: string;
145
+ };
146
+ };
147
+ export declare const FLUID_PRIMITIVES: Primitive[];
148
+ export declare const FLUID_PRESETS: Preset[];
149
+ export declare function registerFluidCursor(target: unknown): Registry | Animator;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Low-level WebGL helper utilities for kUInetic Shaders
3
+ */
4
+ export interface ProgramLocations {
5
+ program?: WebGLProgram;
6
+ a_position?: number;
7
+ u_image?: WebGLUniformLocation | null;
8
+ u_image_to?: WebGLUniformLocation | null;
9
+ u_uvOrigin?: WebGLUniformLocation | null;
10
+ u_uvScale?: WebGLUniformLocation | null;
11
+ u_maskBox?: WebGLUniformLocation | null;
12
+ u_maskRx?: WebGLUniformLocation | null;
13
+ u_maskRy?: WebGLUniformLocation | null;
14
+ u_maskAlpha?: WebGLUniformLocation | null;
15
+ u_time?: WebGLUniformLocation | null;
16
+ u_strength?: WebGLUniformLocation | null;
17
+ u_frequency?: WebGLUniformLocation | null;
18
+ u_chromatic?: WebGLUniformLocation | null;
19
+ u_iridescence?: WebGLUniformLocation | null;
20
+ u_mouse?: WebGLUniformLocation | null;
21
+ u_tint?: WebGLUniformLocation | null;
22
+ u_color1?: WebGLUniformLocation | null;
23
+ u_color2?: WebGLUniformLocation | null;
24
+ u_duotone?: WebGLUniformLocation | null;
25
+ u_blend?: WebGLUniformLocation | null;
26
+ u_progress?: WebGLUniformLocation | null;
27
+ u_seed?: WebGLUniformLocation | null;
28
+ u_scale?: WebGLUniformLocation | null;
29
+ u_warp?: WebGLUniformLocation | null;
30
+ u_grain?: WebGLUniformLocation | null;
31
+ u_hue?: WebGLUniformLocation | null;
32
+ u_detail?: WebGLUniformLocation | null;
33
+ u_bands?: WebGLUniformLocation | null;
34
+ u_colorCount?: WebGLUniformLocation | null;
35
+ /** Element 0 of `vec4 u_colors[5]`; one `uniform4fv` of 20 floats fills the whole array. */
36
+ u_colors?: WebGLUniformLocation | null;
37
+ /** Which stencil the generative program cuts its field with. See `glyphMask()` in `glsl.ts`. */
38
+ u_maskMode?: WebGLUniformLocation | null;
39
+ /** `hover:` as bits — 1 stir, 2 light, 3 both. See `kuiHoverFalloff()` in `glsl.ts`. */
40
+ u_hover?: WebGLUniformLocation | null;
41
+ /** The colour the generative field is composited over, straight (non-premultiplied) RGBA. */
42
+ u_backdrop?: WebGLUniformLocation | null;
43
+ /** The field's orientation in **radians**; also the axis `motion: drift` travels along. */
44
+ u_angle?: WebGLUniformLocation | null;
45
+ /** `motion:` — 0 evolve, 1 drift, 2 swirl. */
46
+ u_motion?: WebGLUniformLocation | null;
47
+ }
48
+ export declare function compileShader(gl: WebGLRenderingContext | WebGL2RenderingContext, type: number, source: string): WebGLShader | null;
49
+ export declare function createProgram(gl: WebGLRenderingContext | WebGL2RenderingContext, vsSource: string, fsSource: string): WebGLProgram | null;
50
+ export declare function extractLocations(gl: WebGLRenderingContext | WebGL2RenderingContext, program: WebGLProgram | null): ProgramLocations | null;
51
+ export declare function disposeGLResources(gl: WebGLRenderingContext | WebGL2RenderingContext | null, quadBuffer: WebGLBuffer | null, programs: Record<string, ProgramLocations | WebGLProgram | null | undefined>): void;
52
+ export declare function createGLTexture(gl: WebGLRenderingContext | WebGL2RenderingContext, source: TexImageSource): WebGLTexture | null;