@lovo/matter 0.4.0 → 0.4.1
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/CHANGELOG.md +6 -0
- package/dist/index.cjs +18 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @lovo/matter
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b4ecdda: Reorganize engine source into kebab-case module folders under `inputs/`, `primitives/`, and `runtime/` (matching `matter-react` and `registry` layout). No public API changes.
|
|
8
|
+
|
|
3
9
|
## 0.4.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -42,7 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
});
|
|
43
43
|
module.exports = __toCommonJS(index_exports);
|
|
44
44
|
|
|
45
|
-
// src/runtime/
|
|
45
|
+
// src/runtime/create-renderer/create-renderer.ts
|
|
46
46
|
var import_three = require("three");
|
|
47
47
|
var import_webgpu = require("three/webgpu");
|
|
48
48
|
async function createRenderer(canvas, opts = {}) {
|
|
@@ -80,7 +80,7 @@ async function createRenderer(canvas, opts = {}) {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// src/inputs/
|
|
83
|
+
// src/inputs/cursor-input/cursor-input.ts
|
|
84
84
|
var CursorInput = class {
|
|
85
85
|
value;
|
|
86
86
|
target;
|
|
@@ -154,7 +154,7 @@ var CursorInput = class {
|
|
|
154
154
|
var clamp01 = (n) => Math.max(0, Math.min(1, n));
|
|
155
155
|
var lerp = (a, b, t) => a + (b - a) * t;
|
|
156
156
|
|
|
157
|
-
// src/primitives/
|
|
157
|
+
// src/primitives/color-ramp/color-ramp.ts
|
|
158
158
|
var import_tsl = require("three/tsl");
|
|
159
159
|
var import_tsl2 = require("three/tsl");
|
|
160
160
|
function colorRamp(t, stops) {
|
|
@@ -174,13 +174,13 @@ function colorRamp(t, stops) {
|
|
|
174
174
|
return result;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// src/primitives/noise.ts
|
|
177
|
+
// src/primitives/noise/noise.ts
|
|
178
178
|
var import_tsl3 = require("three/tsl");
|
|
179
179
|
function noise(p) {
|
|
180
180
|
return (0, import_tsl3.mx_noise_float)(p);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
// src/primitives/fbm.ts
|
|
183
|
+
// src/primitives/fbm/fbm.ts
|
|
184
184
|
var import_tsl4 = require("three/tsl");
|
|
185
185
|
function fbm(p, opts = {}) {
|
|
186
186
|
const octaves = opts.octaves ?? 4;
|
|
@@ -201,13 +201,13 @@ function fbm(p, opts = {}) {
|
|
|
201
201
|
return sum.div(total);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
// src/primitives/voronoi.ts
|
|
204
|
+
// src/primitives/voronoi/voronoi.ts
|
|
205
205
|
var import_tsl5 = require("three/tsl");
|
|
206
206
|
function voronoi(p) {
|
|
207
207
|
return (0, import_tsl5.mx_worley_noise_float)(p);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// src/primitives/quantize.ts
|
|
210
|
+
// src/primitives/quantize/quantize.ts
|
|
211
211
|
function quantize(t, steps) {
|
|
212
212
|
if (steps <= 1) {
|
|
213
213
|
return t.mul(0);
|
|
@@ -216,25 +216,25 @@ function quantize(t, steps) {
|
|
|
216
216
|
return t.mul(denom).add(0.5).floor().div(denom);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
// src/primitives/
|
|
219
|
+
// src/primitives/sdf-circle/sdf-circle.ts
|
|
220
220
|
var import_tsl6 = require("three/tsl");
|
|
221
221
|
function sdfCircle(p, radius) {
|
|
222
222
|
return (0, import_tsl6.length)(p).sub(radius);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
// src/primitives/displace.ts
|
|
225
|
+
// src/primitives/displace/displace.ts
|
|
226
226
|
var import_tsl7 = require("three/tsl");
|
|
227
227
|
function displace(p, by) {
|
|
228
228
|
return (0, import_tsl7.add)(p, by);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
// src/primitives/
|
|
231
|
+
// src/primitives/cursor-ripple/cursor-ripple.ts
|
|
232
232
|
var import_tsl10 = require("three/tsl");
|
|
233
233
|
|
|
234
|
-
// src/primitives/time.ts
|
|
234
|
+
// src/primitives/time/time.ts
|
|
235
235
|
var import_tsl9 = require("three/tsl");
|
|
236
236
|
|
|
237
|
-
// src/runtime/
|
|
237
|
+
// src/runtime/reduced-motion/reduced-motion.ts
|
|
238
238
|
var import_tsl8 = require("three/tsl");
|
|
239
239
|
var state = {
|
|
240
240
|
policy: "auto",
|
|
@@ -321,10 +321,10 @@ function getReducedMotionTimeScale() {
|
|
|
321
321
|
return globalScaleUniform;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
// src/primitives/time.ts
|
|
324
|
+
// src/primitives/time/time.ts
|
|
325
325
|
var time = import_tsl9.time.mul(getReducedMotionTimeScale());
|
|
326
326
|
|
|
327
|
-
// src/primitives/
|
|
327
|
+
// src/primitives/cursor-ripple/cursor-ripple.ts
|
|
328
328
|
function cursorRipple(p, center, opts = {}) {
|
|
329
329
|
const reach = opts.reach ?? 0.4;
|
|
330
330
|
const frequency = opts.frequency ?? 30;
|
|
@@ -336,7 +336,7 @@ function cursorRipple(p, center, opts = {}) {
|
|
|
336
336
|
return wave.mul(amplitude).mul(decay);
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
// src/primitives/
|
|
339
|
+
// src/primitives/film-grain/film-grain.ts
|
|
340
340
|
var import_tsl11 = require("three/tsl");
|
|
341
341
|
function filmGrain(uvNode, intensity, timeOffset = 0) {
|
|
342
342
|
const HASH_C1 = (0, import_tsl11.vec2)(2127.1, 81.17);
|
|
@@ -346,7 +346,7 @@ function filmGrain(uvNode, intensity, timeOffset = 0) {
|
|
|
346
346
|
return (0, import_tsl11.length)(hash).sub(0.765).mul(intensity);
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
// src/runtime/visibility.ts
|
|
349
|
+
// src/runtime/visibility/visibility.ts
|
|
350
350
|
function createVisibilityWatcher() {
|
|
351
351
|
if (typeof document === "undefined") {
|
|
352
352
|
return {
|
|
@@ -376,7 +376,7 @@ function createVisibilityWatcher() {
|
|
|
376
376
|
};
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
-
// src/runtime/intersection.ts
|
|
379
|
+
// src/runtime/intersection/intersection.ts
|
|
380
380
|
function createIntersectionWatcher(canvas) {
|
|
381
381
|
if (typeof IntersectionObserver === "undefined") {
|
|
382
382
|
return {
|
|
@@ -412,7 +412,7 @@ function createIntersectionWatcher(canvas) {
|
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
-
// src/runtime/frame-scheduler.ts
|
|
415
|
+
// src/runtime/frame-scheduler/frame-scheduler.ts
|
|
416
416
|
var FrameScheduler = class {
|
|
417
417
|
clients = /* @__PURE__ */ new Set();
|
|
418
418
|
rafId = null;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/runtime/createRenderer.ts","../src/inputs/CursorInput.ts","../src/primitives/colorRamp.ts","../src/primitives/noise.ts","../src/primitives/fbm.ts","../src/primitives/voronoi.ts","../src/primitives/quantize.ts","../src/primitives/sdfCircle.ts","../src/primitives/displace.ts","../src/primitives/cursorRipple.ts","../src/primitives/time.ts","../src/runtime/reducedMotion.ts","../src/primitives/filmGrain.ts","../src/runtime/visibility.ts","../src/runtime/intersection.ts","../src/runtime/frame-scheduler.ts"],"sourcesContent":["// @lovo/matter — engine package public API.\n\nexport { createRenderer } from './runtime/createRenderer.js'\nexport type { GpuRenderer, GpuBackend, CreateRendererOptions } from './runtime/createRenderer.js'\n\nexport { CursorInput } from './inputs/CursorInput.js'\nexport type { CursorInputOptions, Vec2 } from './inputs/CursorInput.js'\n\nexport { colorRamp } from './primitives/colorRamp.js'\nexport type { ColorRampStop, TSLNode } from './primitives/colorRamp.js'\n\nexport { noise } from './primitives/noise.js'\n\nexport { fbm } from './primitives/fbm.js'\nexport type { FBMOptions } from './primitives/fbm.js'\n\nexport { voronoi } from './primitives/voronoi.js'\n\nexport { quantize } from './primitives/quantize.js'\n\nexport { sdfCircle } from './primitives/sdfCircle.js'\n\nexport { displace } from './primitives/displace.js'\n\nexport { cursorRipple } from './primitives/cursorRipple.js'\nexport type { CursorRippleOptions } from './primitives/cursorRipple.js'\n\nexport { time } from './primitives/time.js'\n\nexport { filmGrain } from './primitives/filmGrain.js'\n\nexport {\n setReducedMotionPolicy,\n getReducedMotionPolicy,\n getReducedMotionTimeScale,\n createReducedMotionWatcher,\n} from './runtime/reducedMotion.js'\nexport type { ReducedMotionPolicy, ReducedMotionWatcher } from './runtime/reducedMotion.js'\n\nexport { createVisibilityWatcher } from './runtime/visibility.js'\nexport type { VisibilityWatcher } from './runtime/visibility.js'\n\nexport { createIntersectionWatcher } from './runtime/intersection.js'\nexport type { IntersectionWatcher } from './runtime/intersection.js'\n\nexport { FrameScheduler } from './runtime/frame-scheduler.js'\nexport type { SchedulerTick, SchedulerClient } from './runtime/frame-scheduler.js'\n","import { Color } from 'three'\nimport { WebGPURenderer } from 'three/webgpu'\n\nexport type GpuBackend = 'webgpu' | 'webgl2'\n\nexport interface CreateRendererOptions {\n /** Anti-alias the framebuffer. Default: true. */\n antialias?: boolean\n /** Force WebGL2 even if WebGPU is available (useful for testing fallback). Default: false. */\n forceWebGL?: boolean\n /** Clear color (hex, CSS string, or THREE.Color). Default: transparent. */\n clearColor?: number | string | Color\n /** Clear alpha (0–1). Default: 0 (transparent). */\n clearAlpha?: number\n /** Cap on devicePixelRatio. Default: 2. Pass Infinity to disable. */\n maxDPR?: number\n}\n\nexport interface GpuRenderer {\n /** The underlying Three.js WebGPURenderer (which may be running on a WebGL2 backend). */\n three: WebGPURenderer\n /** Which backend the renderer initialized with. */\n backend: GpuBackend\n /** Tear down the renderer and release GPU resources. */\n dispose: () => void\n /** Resize the renderer to the canvas's current client dimensions. */\n resize: () => void\n}\n\n/**\n * Create a Matter renderer wrapping THREE.WebGPURenderer.\n *\n * Tries WebGPU first; falls back to WebGL2 automatically if WebGPU is\n * unavailable on the host. The returned object exposes the underlying\n * three renderer plus a small wrapper for resize and disposal.\n */\nexport async function createRenderer(\n canvas: HTMLCanvasElement,\n opts: CreateRendererOptions = {},\n): Promise<GpuRenderer> {\n const {\n antialias = true,\n forceWebGL = false,\n clearColor = 0x000000,\n clearAlpha = 0,\n maxDPR = 2,\n } = opts\n\n const three = new WebGPURenderer({\n canvas,\n antialias,\n forceWebGL,\n })\n\n await three.init()\n\n three.setPixelRatio(Math.min(window.devicePixelRatio, maxDPR))\n const resolvedClearColor = clearColor instanceof Color ? clearColor : new Color(clearColor)\n\n three.setClearColor(resolvedClearColor, clearAlpha)\n\n const resize = () => {\n const w = canvas.clientWidth\n const h = canvas.clientHeight\n\n if (canvas.width !== w * three.getPixelRatio() || canvas.height !== h * three.getPixelRatio()) {\n three.setSize(w, h, false)\n }\n }\n\n resize()\n\n // Detect backend after init. `isWebGLBackend` is an internal duck-type flag\n // not declared in three's public Backend type — probe via `in` rather than\n // a property access that would trip strict typing.\n const isWebGL = 'isWebGLBackend' in three.backend && three.backend.isWebGLBackend === true\n const backend: GpuBackend = forceWebGL || isWebGL ? 'webgl2' : 'webgpu'\n\n return {\n three,\n backend,\n dispose: () => three.dispose(),\n resize,\n }\n}\n","export type Vec2 = readonly [number, number]\n\nexport interface CursorInputOptions {\n /**\n * Smoothing factor: 0 = no smoothing (snap to target instantly).\n * 1 = max smoothing (essentially never reaches target).\n * Sensible default: 0.1.\n *\n * Implementation: per-frame, value moves toward target by `(1 - smoothing) * delta * 60`,\n * roughly meaning \"at smoothing=0.1, ~90% of the gap is closed in 1 second at 60fps.\"\n */\n smoothing?: number\n /** Starting position. Default: [0.5, 0.5] (center). */\n initial?: Vec2\n /** Listen on this target. Default: window. */\n target?: EventTarget\n /**\n * Element to normalize cursor coordinates against. Default: window viewport.\n *\n * When set, cursor x/y are in [0,1] across the element's bounding rect, with\n * extrapolation outside (negative when left/above, >1 when right/below). This\n * matches what shader UV space expects: a cursor at the canvas's top-left\n * corner reads as (0, 0); at bottom-right as (1, 1); regardless of where the\n * canvas sits in the viewport. Without this, components inside a partial-\n * viewport scene (e.g. a 70vh hero section) see a cursor offset that scales\n * with the canvas's vertical position on the page.\n */\n element?: {\n getBoundingClientRect(): { left: number; top: number; width: number; height: number }\n }\n}\n\ntype ChangeListener = (value: Vec2) => void\n\n/**\n * Smoothed pointer tracker emitting a normalized (0..1) Vec2 position.\n * Implements the AnimatableSignal protocol (`get()` + `on('change', cb)`)\n * so it composes with Motion's `useTransform` and similar tools.\n */\nexport class CursorInput {\n private value: [number, number]\n private target: [number, number]\n private targetDirty = false\n private readonly smoothing: number\n private readonly listeners = new Set<ChangeListener>()\n private readonly eventTarget: EventTarget\n private readonly element: CursorInputOptions['element']\n private readonly handleMouseMove: (e: Event) => void\n private disposed = false\n\n constructor(opts: CursorInputOptions = {}) {\n const { smoothing = 0.1, initial = [0.5, 0.5], target, element } = opts\n\n this.smoothing = clamp01(smoothing)\n this.value = [initial[0], initial[1]]\n this.target = [initial[0], initial[1]]\n this.eventTarget = target ?? (typeof window !== 'undefined' ? window : new EventTarget())\n this.element = element\n\n this.handleMouseMove = (e: Event) => {\n if (!(e instanceof MouseEvent)) return\n const me = e\n\n if (this.element) {\n // Normalize to 0..1 across the element's bounding rect. Reading the\n // rect on every move is fine — `getBoundingClientRect` is cheap and\n // mousemove is already throttled to ~60Hz by the browser. The benefit\n // is tracking the element's position even if it moved/scrolled since\n // the last frame.\n const r = this.element.getBoundingClientRect()\n const w = r.width || 1\n const h = r.height || 1\n\n this.target = [(me.clientX - r.left) / w, (me.clientY - r.top) / h]\n } else {\n // Fallback: viewport-normalized. Used when no element is supplied —\n // mostly the standalone-API case for users not consuming through\n // <ShaderScene>'s context.\n const w = (typeof window !== 'undefined' && window.innerWidth) || 1\n const h = (typeof window !== 'undefined' && window.innerHeight) || 1\n\n this.target = [me.clientX / w, me.clientY / h]\n }\n this.targetDirty = true\n }\n\n this.eventTarget.addEventListener('mousemove', this.handleMouseMove)\n }\n\n /** Current smoothed position. Implements AnimatableSignal protocol. */\n get(): Vec2 {\n return this.value\n }\n\n /** Subscribe to change events. Returns an unsubscribe function. */\n on(_event: 'change', cb: ChangeListener): () => void {\n this.listeners.add(cb)\n\n return () => this.listeners.delete(cb)\n }\n\n /**\n * Advance the smoothing one tick. Called by the host scheduler; not\n * typically called directly except in tests.\n */\n tick(delta: number): void {\n if (this.disposed) return\n const factor = this.smoothing === 0 ? 1 : 1 - Math.pow(this.smoothing, delta * 60)\n const prev0 = this.value[0]\n const prev1 = this.value[1]\n const next0 = lerp(prev0, this.target[0], factor)\n const next1 = lerp(prev1, this.target[1], factor)\n const moved = next0 !== prev0 || next1 !== prev1\n\n if (moved || this.targetDirty) {\n this.value = [next0, next1]\n this.targetDirty = false\n const snapshot: Vec2 = [next0, next1]\n\n for (const listener of this.listeners) listener(snapshot)\n }\n }\n\n /** Tear down listeners. */\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.eventTarget.removeEventListener('mousemove', this.handleMouseMove)\n this.listeners.clear()\n }\n}\n\nconst clamp01 = (n: number) => Math.max(0, Math.min(1, n))\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t\n","import type { ShaderNodeObject } from 'three/tsl'\nimport { mix, vec3 } from 'three/tsl'\nimport { clamp, div, sub } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Canonical TSL-node *input* shape used throughout `@lovo/matter`.\n *\n * Stays as the broad `Node | ShaderNodeObject<Node>` union so callers can\n * pass uniform-typed nodes (e.g. `ShaderNodeObject<UniformNode<Vector2>>`)\n * without casting at the call site — those are subtypes of `Node` but NOT\n * subtypes of `ShaderNodeObject<Node>` due to invariant generic parameters.\n *\n * Wrappers should return the narrower `ShaderNodeObject<Node>` so the\n * **output** is always chainable without casts.\n */\nexport type TSLNode = Node | ShaderNodeObject<Node>\n\nexport interface ColorRampStop {\n /** Color expressed as a TSL node (typically `vec3(r,g,b)`). */\n color: TSLNode\n /** Position 0..1 along the ramp. */\n position: number\n}\n\n/**\n * Multi-stop color interpolation. Given a t in [0..1] and N color stops at\n * fixed positions, returns the smoothly-interpolated color.\n *\n * Falls back to the first/last stop's color outside the bracketing positions.\n */\nexport function colorRamp(t: TSLNode, stops: ColorRampStop[]): ShaderNodeObject<Node> {\n // TSLNode is wider than ShaderNodeObject<Node> in TSL's published types\n // (see CLAUDE.md gotcha #5). Wrapping with mix(node, node, 0) yields a\n // chainable ShaderNodeObject<Node> without a cast — the GPU shader compiler\n // folds the no-op interpolation away.\n const first = stops[0]\n\n if (first === undefined) return vec3(0, 0, 0)\n if (stops.length === 1) return mix(first.color, first.color, 0)\n\n // Build a chain of nested mixes, one per adjacent pair of stops.\n // For three stops at positions 0, 0.5, 1:\n // inner = mix(stop0, stop1, smoothstep(0, 0.5, t))\n // outer = mix(inner, stop2, smoothstep(0.5, 1, t))\n let result = mix(first.color, first.color, 0)\n\n for (let i = 1; i < stops.length; i += 1) {\n const prev = stops[i - 1]\n const next = stops[i]\n\n if (prev === undefined || next === undefined) continue\n const span = next.position - prev.position\n\n if (span <= 0) continue\n // Localize t into the [prev..next] range. `t` is TSLNode (the union),\n // so we use functional-form ops to avoid needing a chain-method receiver.\n const localT = clamp(div(sub(t, prev.position), span), 0, 1)\n\n result = mix(result, next.color, localT)\n }\n\n return result\n}\n","// packages/matter/src/primitives/noise.ts\nimport { mx_noise_float } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\n\n/**\n * 2D simplex noise sampled at a point. Returns a scalar TSL node in\n * approximately [-1, 1] (MaterialX's mx_noise_float is roughly that range).\n *\n * @param p — Vec2 TSL node (typically `uv()` or a scaled/offset uv).\n *\n * Built on top of three's `mx_noise_float`; we wrap it so consumers have a\n * stable import path through `@lovo/matter` and we can swap the\n * implementation if a different noise primitive proves better in practice.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) rather than the broader\n * `TSLNode` union, so callers can `.add(...)`/`.mul(...)` without casting.\n */\nexport function noise(p: TSLNode): ShaderNodeObject<Node> {\n return mx_noise_float(p)\n}\n","// packages/matter/src/primitives/fbm.ts\nimport { add, mul } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\nimport { noise } from './noise.js'\n\nexport interface FBMOptions {\n /** Number of octaves to sum. JS-side number — fixed at TSL build time, not a uniform. Default: 4. */\n octaves?: number\n /** Per-octave frequency multiplier. JS-side number. Default: 2. */\n lacunarity?: number\n /** Per-octave amplitude multiplier. JS-side number. Default: 0.5. */\n gain?: number\n}\n\n/**\n * Fractal Brownian Motion — sum of N octaves of 2D simplex noise.\n *\n * Each octave samples noise at a higher frequency (× `lacunarity`) and lower\n * amplitude (× `gain`) than the previous one, AND at a translated coordinate\n * so the octaves sample uncorrelated regions of noise space. Without the\n * per-octave translation, octaves at related frequencies tend to pile up\n * peaks and troughs at the same input coordinates, producing visibly muddy\n * \"spotty\" output. With it, the octaves look like independent noise patterns\n * layered together — Inigo Quilez's classic FBM technique.\n *\n * `octaves`, `lacunarity`, and `gain` are JavaScript numbers (NOT TSL\n * uniforms) because the loop must be unrolled at TSL-build time — TSL has\n * no dynamic-length loop primitive that maps cleanly to all backends.\n * Animatable parameters that *do* survive on the GPU are the input UV\n * (which the caller can scale/translate per frame) and `time`.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) for cast-free call sites.\n *\n * @param p — Vec2 or Vec3 TSL node (UV-space position).\n * @returns scalar TSL node, normalized to roughly [-1..1] regardless of\n * octave count thanks to the amplitude-sum division at the end.\n */\nexport function fbm(p: TSLNode, opts: FBMOptions = {}): ShaderNodeObject<Node> {\n const octaves = opts.octaves ?? 4\n const lacunarity = opts.lacunarity ?? 2\n const gain = opts.gain ?? 0.5\n\n let sum: ShaderNodeObject<Node> = noise(p)\n let amp = 1\n let freq = 1\n let total = amp\n\n for (let i = 1; i < octaves; i += 1) {\n freq *= lacunarity\n amp *= gain\n total += amp\n // Per-octave decorrelation: translate the sample point by a growing\n // offset so this octave reads from a totally different region of noise\n // space than the previous one. Magnitude 100 is well past simplex\n // noise's ~1-unit feature size, so adjacent octaves are fully\n // decorrelated. The scalar broadcasts across all components of `p`\n // (works for vec2 and vec3 inputs alike).\n //\n // Build the chain functionally from `p`: gotcha #12 doesn't apply\n // because `p` is uv-rooted, but the TSLNode union still requires\n // functional form on this hop.\n const pAtFreq = add(mul(p, freq), i * 100)\n const layer = noise(pAtFreq).mul(amp)\n\n sum = sum.add(layer)\n }\n\n // Normalize to approximate [-1..1] regardless of octave count / gain.\n return sum.div(total)\n}\n","// packages/matter/src/primitives/voronoi.ts\nimport { mx_worley_noise_float } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\n\n/**\n * 2D voronoi (Worley) noise — distance to the nearest jittered cell point,\n * normalized roughly to [0, 1]. Higher values = farther from any cell point\n * (cell interiors); lower values = near a cell boundary.\n *\n * Built on three's `mx_worley_noise_float`. Combine with `colorRamp` for\n * a multi-color cellular pattern; threshold via `step`/`smoothstep` for\n * hard cell shapes.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) for cast-free call sites.\n *\n * @param p — Vec2 TSL node, typically `uv() * scale`.\n */\nexport function voronoi(p: TSLNode): ShaderNodeObject<Node> {\n return mx_worley_noise_float(p)\n}\n","// packages/matter/src/primitives/quantize.ts\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Quantize a scalar TSL node to `steps` discrete levels.\n *\n * quantize(t, 4) → values in {0, 0.25, 0.5, 0.75, 1.0}\n *\n * `steps` is a JS-side number (loop-equivalent at TSL build time, baked in).\n * If you need an animatable step count, rebuild the TSL fragment.\n */\nexport function quantize(t: ShaderNodeObject<Node>, steps: number): ShaderNodeObject<Node> {\n if (steps <= 1) {\n // Edge case: single step → constant 0. Return as-is wrapped in mul(0).\n return t.mul(0)\n }\n const denom = steps - 1\n\n // floor(t * (steps-1) + 0.5) / (steps-1)\n // Using floor(x + 0.5) instead of round() for TSL portability.\n return t.mul(denom).add(0.5).floor().div(denom)\n}\n","import { length } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\n\n/**\n * Signed distance field for a circle centered at the origin.\n *\n * sdfCircle(p, r) = length(p) - r\n *\n * Negative inside the circle, zero on the boundary, positive outside.\n * Combine with `smoothstep(-edge, +edge, sdf)` to render a soft-edged disk.\n *\n * @param p — Vec2 TSL node (typically a UV-space offset from the center).\n * @param radius — JS-side scalar OR a scalar TSL node.\n */\nexport function sdfCircle(p: TSLNode, radius: TSLNode | number): ShaderNodeObject<Node> {\n return length(p).sub(radius)\n}\n","import { add } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\n\n/**\n * Naive vector addition: returns `p + by`.\n *\n * displace(p, by) = p + by\n *\n * Thin wrapper that names the spatial intent of shifting a sample point.\n *\n * **SDF caveat:** when using this to translate an SDF render, pass the\n * NEGATED translation — `sdfCircle(displace(p, v.mul(-1)), r)` renders the\n * disk at position `+v` because SDF translation evaluates as\n * `length(p - center) - r`. Adding `+v` to the sample point shifts the\n * rendered shape in the OPPOSITE direction.\n *\n * @param p — Vec2 TSL node (the position being displaced).\n * @param by — Vec2 TSL node (the displacement vector).\n */\nexport function displace(p: TSLNode, by: TSLNode): ShaderNodeObject<Node> {\n return add(p, by)\n}\n","import { length, sin, smoothstep, sub } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\nimport { time } from './time.js'\n\nexport interface CursorRippleOptions {\n /** Decay radius (UV space). Beyond this, the ripple is ~0. Default: 0.4. */\n reach?: number\n /** Wavelength controls the ripple spacing. Default: 30. Larger = wider rings. */\n frequency?: number\n /** Time multiplier on the wave phase. Default: 6. Larger = faster oscillation. */\n speed?: number\n /** Output amplitude. Default: 0.5. Final result is in roughly [-amplitude, +amplitude]. */\n amplitude?: number\n}\n\n/**\n * A radial ripple emanating from `center`. Returns a scalar TSL node in\n * roughly [-amplitude, +amplitude] that decays to ~0 outside `reach`.\n *\n * ripple = sin(d*frequency - time*speed) * amplitude * smoothstep(reach, 0, d)\n *\n * Compose into a wave field by adding it to the underlying base wave.\n *\n * Note: `frequency` / `speed` / `reach` / `amplitude` are JS-side numbers\n * (baked into the TSL fragment at material-build time). The animatable\n * cursor position is the only live uniform consumed.\n *\n * @param p — Vec2 TSL node (typically `uv()`).\n * @param center — Vec2 TSL node (cursor uniform, in UV space).\n */\nexport function cursorRipple(\n p: TSLNode,\n center: TSLNode,\n opts: CursorRippleOptions = {},\n): ShaderNodeObject<Node> {\n const reach = opts.reach ?? 0.4\n const frequency = opts.frequency ?? 30\n const speed = opts.speed ?? 6\n const amplitude = opts.amplitude ?? 0.5\n\n // d = length(p - center). Use functional `sub(p, center)` because both\n // are typed as the broad TSLNode union (no chain receiver). Per gotcha #12,\n // building from a raw `uniform()` receiver silently produces wrong GPU\n // values, so the functional form is also safer for `center` being a uniform.\n const d = length(sub(p, center))\n // `time` is the engine-gated TSL node (from primitives/time.ts);\n // chains rooted in `time` automatically respect `prefers-reduced-motion` and\n // the runtime override set via `setReducedMotionPolicy`.\n const wave = sin(d.mul(frequency).sub(time.mul(speed)))\n const decay = smoothstep(reach, 0, d)\n\n return wave.mul(amplitude).mul(decay)\n}\n","// Engine-gated `time` — equals the TSL built-in `time` multiplied by the\n// reduced-motion scale uniform. Components consuming `time` from `@lovo/matter`\n// automatically respect `prefers-reduced-motion` and the policy override set\n// via `setReducedMotionPolicy`.\n//\n// If you want raw uncapped time (e.g. for a debug overlay), import `time`\n// from `three/tsl` directly.\n\nimport { time as _builtinTime } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport { getReducedMotionTimeScale } from '../runtime/reducedMotion.js'\n\nexport const time: ShaderNodeObject<Node> = _builtinTime.mul(getReducedMotionTimeScale())\n","import { uniform } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nexport type ReducedMotionPolicy = 'auto' | 'off' | 'slow' | 'paused'\n\n/**\n * Public surface exposed to package consumers. `recompute` is intentionally\n * absent — it is engine-internal and should not be callable from outside.\n */\nexport interface ReducedMotionWatcher {\n /** Current time scale: 0, 0.3, or 1. */\n scale(): number\n /** Subscribe to scale changes. Returns unsubscribe. */\n subscribe(cb: (scale: number) => void): () => void\n /** Tear down media-query listener. */\n dispose(): void\n}\n\n/**\n * Engine-internal extension of the public watcher. Only `setReducedMotionPolicy`\n * calls `recompute`; it is never part of the consumer-visible type.\n */\ninterface InternalWatcher extends ReducedMotionWatcher {\n recompute(): void\n}\n\ninterface PolicyState {\n policy: ReducedMotionPolicy\n watchers: Set<InternalWatcher>\n}\n\nconst state: PolicyState = {\n policy: 'auto',\n watchers: new Set(),\n}\n\n/**\n * Override Matter's default behavior of honoring `prefers-reduced-motion`.\n * - 'auto' — follow the OS media query (default)\n * - 'off' — full speed regardless of OS setting\n * - 'slow' — 30% speed regardless of OS setting\n * - 'paused' — 0 (animation effectively frozen) regardless of OS setting\n */\nexport function setReducedMotionPolicy(policy: ReducedMotionPolicy): void {\n if (state.policy === policy) return\n state.policy = policy\n for (const w of state.watchers) w.recompute()\n}\n\nexport function getReducedMotionPolicy(): ReducedMotionPolicy {\n return state.policy\n}\n\nconst computeScale = (mqlMatches: boolean): number => {\n switch (state.policy) {\n case 'off':\n return 1\n case 'slow':\n return 0.3\n case 'paused':\n return 0\n case 'auto':\n return mqlMatches ? 0.3 : 1\n }\n}\n\n/**\n * Create a watcher that tracks `prefers-reduced-motion: reduce` and the\n * global Matter policy override. Strict-mode-safe — callers create+dispose\n * one per mount cycle.\n */\nexport function createReducedMotionWatcher(): ReducedMotionWatcher {\n // SSR safety: bail to the no-op watcher if matchMedia is missing.\n // SSR watcher: scale() respects policy override but does not emit\n // subscription events (the engine has no way to notify SSR-created\n // watchers because they are not added to state.watchers — but in\n // practice CLAUDE.md gotcha #10 requires `ssr: false` for any component\n // that touches the matter engine).\n if (typeof matchMedia !== 'function') {\n return {\n scale: () => computeScale(false),\n subscribe: (cb) => {\n // No-op: SSR watchers are not in state.watchers and will never\n // receive policy-change notifications.\n void cb\n\n return () => {\n // SSR no-op unsubscribe\n }\n },\n /** SSR watcher does not emit policy-change notifications. */\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const mql = matchMedia('(prefers-reduced-motion: reduce)')\n const subs = new Set<(s: number) => void>()\n let last = computeScale(mql.matches)\n\n const onChange = () => {\n const next = computeScale(mql.matches)\n\n if (next !== last) {\n last = next\n for (const cb of subs) cb(next)\n }\n }\n\n mql.addEventListener('change', onChange)\n\n const watcher: InternalWatcher = {\n scale: () => last,\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n recompute() {\n const next = computeScale(mql.matches)\n\n if (next !== last) {\n last = next\n for (const cb of subs) cb(next)\n }\n },\n dispose() {\n mql.removeEventListener('change', onChange)\n subs.clear()\n state.watchers.delete(watcher)\n },\n }\n\n state.watchers.add(watcher)\n\n return watcher\n}\n\nlet globalScaleUniform: ReturnType<typeof uniform<number>> | null = null\nlet globalWatcher: ReducedMotionWatcher | null = null\n\n/**\n * Returns the engine-shared TSL uniform that `time` is multiplied by. Lazily\n * initialized on first read; reused across all materials. Mutating `.value`\n * imperatively when policy changes is safe — TSL re-reads the uniform every\n * frame.\n */\nexport function getReducedMotionTimeScale(): ShaderNodeObject<Node> {\n if (globalScaleUniform === null) {\n globalWatcher = createReducedMotionWatcher()\n globalScaleUniform = uniform(globalWatcher.scale())\n globalWatcher.subscribe((s) => {\n if (globalScaleUniform === null) return\n globalScaleUniform.value = s\n })\n }\n\n // ShaderNodeObject<UniformNode<number>> isn't structurally assignable to\n // ShaderNodeObject<Node> (invariant generic methods); chains work the same\n // at runtime, so widen at the return boundary.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n return globalScaleUniform as unknown as ShaderNodeObject<Node>\n}\n\n// Keep a typed reference for tests that may want to re-init between tests.\nexport const __resetReducedMotionForTests = () => {\n globalWatcher?.dispose()\n globalWatcher = null\n globalScaleUniform = null\n}\n","import { fract, length, sin, vec2 } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Hash-based film grain — chaotic, uncorrelated per-pixel noise sampled\n * from `uvNode`. The output is *centered* around zero so it acts as a\n * brightness-preserving texture overlay (half the pixels brighten by up\n * to `intensity`, half darken, mean unchanged). ADD the result to a color.\n *\n * filmGrain(uv, k) → static grain\n * filmGrain(uv, k, time) → twinkling grain. Pass a quantized time node\n * (e.g. `time.mul(speed).mul(60).floor()`) so\n * the grain re-randomizes at a controllable\n * \"shutter rate\" instead of every frame.\n *\n * Recipe:\n *\n * base = vec2(uv·c1, uv·c2) + timeOffset\n * hash = fract(sin(base) * 43758.5453)\n * out = (length(hash) - 0.765) * intensity\n *\n * `c1 = (2127.1, 81.17)` and `c2 = (1269.5, 283.37)` are arbitrary\n * near-prime constants that produce visually-uncorrelated noise. `0.765`\n * is the empirical mean of `length(vec2(u, v))` for uniform u, v ∈ [0, 1),\n * computed once so we don't have to subtract it at runtime per pixel.\n *\n * For a film-stock look (darkens as grain rises — silver-emulsion\n * physics) subtract the result from the color instead of adding.\n *\n * @param uvNode vec2 TSL node, typically `uv()`.\n * @param intensity number or TSL node in [0, 1]; scales the grain.\n * @param timeOffset optional number or TSL node added to each sample\n * before hashing. `0` (default) → static grain.\n */\nexport function filmGrain(\n uvNode: ShaderNodeObject<Node>,\n intensity: ShaderNodeObject<Node> | number,\n timeOffset: ShaderNodeObject<Node> | number = 0,\n): ShaderNodeObject<Node> {\n const HASH_C1 = vec2(2127.1, 81.17)\n const HASH_C2 = vec2(1269.5, 283.37)\n const base = vec2(uvNode.dot(HASH_C1).add(timeOffset), uvNode.dot(HASH_C2).add(timeOffset))\n\n const hash = fract(sin(base).mul(43758.5453))\n\n return length(hash).sub(0.765).mul(intensity)\n}\n","export interface VisibilityWatcher {\n isVisible(): boolean\n /** Subscribe to changes. Receives the new visibility state. Returns unsubscribe. */\n subscribe(cb: (visible: boolean) => void): () => void\n dispose(): void\n}\n\n/**\n * Watch `document.visibilityState`. Strict-mode-safe — callers create+dispose\n * one per mount cycle.\n *\n * SSR: if `document` is unavailable, returns a no-op watcher whose\n * `isVisible()` always returns `true` and whose `subscribe` does nothing.\n */\nexport function createVisibilityWatcher(): VisibilityWatcher {\n if (typeof document === 'undefined') {\n return {\n isVisible: () => true,\n subscribe: () => () => {\n // SSR no-op unsubscribe\n },\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const subs = new Set<(v: boolean) => void>()\n const onChange = () => {\n const v = document.visibilityState === 'visible'\n\n for (const cb of subs) cb(v)\n }\n\n document.addEventListener('visibilitychange', onChange)\n\n return {\n isVisible: () => document.visibilityState === 'visible',\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n dispose() {\n document.removeEventListener('visibilitychange', onChange)\n subs.clear()\n },\n }\n}\n","export interface IntersectionWatcher {\n isInView(): boolean\n /** Subscribe to changes. Receives the new in-view state. Returns unsubscribe. */\n subscribe(cb: (inView: boolean) => void): () => void\n dispose(): void\n}\n\n/**\n * Watch a canvas's viewport intersection. Pauses tied to this watcher should\n * be resumed when the canvas is *any* fraction visible. Strict-mode-safe.\n *\n * SSR: if `IntersectionObserver` is unavailable, returns a no-op watcher whose\n * `isInView()` always returns `true` and whose `subscribe` does nothing.\n */\nexport function createIntersectionWatcher(canvas: HTMLCanvasElement): IntersectionWatcher {\n if (typeof IntersectionObserver === 'undefined') {\n return {\n isInView: () => true,\n subscribe: () => () => {\n // SSR no-op unsubscribe\n },\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const subs = new Set<(v: boolean) => void>()\n let inView = true\n const obs = new IntersectionObserver(\n (entries) => {\n const next = entries.some((e) => e.isIntersecting)\n\n if (next === inView) return\n inView = next\n for (const cb of subs) cb(inView)\n },\n { threshold: 0 },\n )\n\n obs.observe(canvas)\n\n return {\n isInView: () => inView,\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n dispose() {\n obs.disconnect()\n subs.clear()\n },\n }\n}\n","export interface SchedulerTick {\n /** Seconds since the previous tick. 0 on the first call. */\n delta: number\n /** Total seconds since the scheduler started its current run. */\n elapsed: number\n /** The raw `performance.now()` timestamp the rAF callback received. */\n now: number\n}\n\nexport type SchedulerClient = (tick: SchedulerTick) => void\n\n/**\n * Batches `requestAnimationFrame` calls across all clients registered with\n * a single scheduler. One scheduler is created per <ShaderScene>; clients\n * are typically a Three.js renderer's render call.\n */\nexport class FrameScheduler {\n private readonly clients = new Set<SchedulerClient>()\n private rafId: number | null = null\n private running = false\n private paused = false\n private idle = false\n private flushPending = false\n private startedAt = 0\n private lastTickAt = 0\n\n /** Activate the scheduler. The rAF loop starts on the first client added. */\n start(): void {\n this.running = true\n this.paused = false\n this.maybeQueue()\n }\n\n /** Halt the rAF loop entirely. Use dispose() for permanent teardown. */\n stop(): void {\n this.running = false\n this.cancel()\n }\n\n /** Temporarily skip ticks without losing client registrations. */\n pause(): void {\n this.paused = true\n }\n\n /** Resume after pause(). */\n resume(): void {\n this.paused = false\n if (this.running) this.maybeQueue()\n }\n\n /** Register a client to be called every frame. */\n add(client: SchedulerClient): void {\n this.clients.add(client)\n if (this.running) this.maybeQueue()\n }\n\n /** Unregister a client. */\n remove(client: SchedulerClient): void {\n this.clients.delete(client)\n }\n\n /** Permanent teardown: stop the loop and drop all clients. */\n dispose(): void {\n this.stop()\n this.clients.clear()\n }\n\n /**\n * Mark the scheduler idle. The next tick still fires (a final flush so\n * uniform changes that triggered the idle state are rendered), then the\n * rAF loop halts. Use `requestRender()` or `setIdle(false)` to wake.\n */\n setIdle(idle: boolean): void {\n if (this.idle === idle) return\n this.idle = idle\n if (idle) {\n this.flushPending = true\n this.maybeQueue()\n } else {\n this.flushPending = false\n this.maybeQueue()\n }\n }\n\n /** Force a single tick while idle. Useful for prop-change invalidation. */\n requestRender(): void {\n if (!this.idle) return\n this.flushPending = true\n this.maybeQueue()\n }\n\n private maybeQueue(): void {\n if (this.rafId !== null) return\n if (!this.running) return\n if (this.clients.size === 0) return\n if (this.idle && !this.flushPending) return\n this.rafId = requestAnimationFrame(this.frame)\n }\n\n private cancel(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n }\n\n private readonly frame = (now: number): void => {\n this.rafId = null\n if (!this.running || this.paused) return\n\n if (this.startedAt === 0) {\n this.startedAt = now\n this.lastTickAt = now\n }\n const delta = (now - this.lastTickAt) / 1000\n const elapsed = (now - this.startedAt) / 1000\n\n this.lastTickAt = now\n\n const tick: SchedulerTick = { delta, elapsed, now }\n\n for (const client of this.clients) {\n client(tick)\n }\n\n this.flushPending = false\n this.maybeQueue()\n }\n}\n"],"mappings":"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","names":["import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","_builtinTime","import_tsl"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/runtime/create-renderer/create-renderer.ts","../src/inputs/cursor-input/cursor-input.ts","../src/primitives/color-ramp/color-ramp.ts","../src/primitives/noise/noise.ts","../src/primitives/fbm/fbm.ts","../src/primitives/voronoi/voronoi.ts","../src/primitives/quantize/quantize.ts","../src/primitives/sdf-circle/sdf-circle.ts","../src/primitives/displace/displace.ts","../src/primitives/cursor-ripple/cursor-ripple.ts","../src/primitives/time/time.ts","../src/runtime/reduced-motion/reduced-motion.ts","../src/primitives/film-grain/film-grain.ts","../src/runtime/visibility/visibility.ts","../src/runtime/intersection/intersection.ts","../src/runtime/frame-scheduler/frame-scheduler.ts"],"sourcesContent":["// @lovo/matter — engine package public API.\n\nexport { createRenderer } from './runtime/create-renderer/create-renderer.js'\nexport type {\n GpuRenderer,\n GpuBackend,\n CreateRendererOptions,\n} from './runtime/create-renderer/create-renderer.js'\n\nexport { CursorInput } from './inputs/cursor-input/cursor-input.js'\nexport type { CursorInputOptions, Vec2 } from './inputs/cursor-input/cursor-input.js'\n\nexport { colorRamp } from './primitives/color-ramp/color-ramp.js'\nexport type { ColorRampStop, TSLNode } from './primitives/color-ramp/color-ramp.js'\n\nexport { noise } from './primitives/noise/noise.js'\n\nexport { fbm } from './primitives/fbm/fbm.js'\nexport type { FBMOptions } from './primitives/fbm/fbm.js'\n\nexport { voronoi } from './primitives/voronoi/voronoi.js'\n\nexport { quantize } from './primitives/quantize/quantize.js'\n\nexport { sdfCircle } from './primitives/sdf-circle/sdf-circle.js'\n\nexport { displace } from './primitives/displace/displace.js'\n\nexport { cursorRipple } from './primitives/cursor-ripple/cursor-ripple.js'\nexport type { CursorRippleOptions } from './primitives/cursor-ripple/cursor-ripple.js'\n\nexport { time } from './primitives/time/time.js'\n\nexport { filmGrain } from './primitives/film-grain/film-grain.js'\n\nexport {\n setReducedMotionPolicy,\n getReducedMotionPolicy,\n getReducedMotionTimeScale,\n createReducedMotionWatcher,\n} from './runtime/reduced-motion/reduced-motion.js'\nexport type {\n ReducedMotionPolicy,\n ReducedMotionWatcher,\n} from './runtime/reduced-motion/reduced-motion.js'\n\nexport { createVisibilityWatcher } from './runtime/visibility/visibility.js'\nexport type { VisibilityWatcher } from './runtime/visibility/visibility.js'\n\nexport { createIntersectionWatcher } from './runtime/intersection/intersection.js'\nexport type { IntersectionWatcher } from './runtime/intersection/intersection.js'\n\nexport { FrameScheduler } from './runtime/frame-scheduler/frame-scheduler.js'\nexport type { SchedulerTick, SchedulerClient } from './runtime/frame-scheduler/frame-scheduler.js'\n","import { Color } from 'three'\nimport { WebGPURenderer } from 'three/webgpu'\n\nexport type GpuBackend = 'webgpu' | 'webgl2'\n\nexport interface CreateRendererOptions {\n /** Anti-alias the framebuffer. Default: true. */\n antialias?: boolean\n /** Force WebGL2 even if WebGPU is available (useful for testing fallback). Default: false. */\n forceWebGL?: boolean\n /** Clear color (hex, CSS string, or THREE.Color). Default: transparent. */\n clearColor?: number | string | Color\n /** Clear alpha (0–1). Default: 0 (transparent). */\n clearAlpha?: number\n /** Cap on devicePixelRatio. Default: 2. Pass Infinity to disable. */\n maxDPR?: number\n}\n\nexport interface GpuRenderer {\n /** The underlying Three.js WebGPURenderer (which may be running on a WebGL2 backend). */\n three: WebGPURenderer\n /** Which backend the renderer initialized with. */\n backend: GpuBackend\n /** Tear down the renderer and release GPU resources. */\n dispose: () => void\n /** Resize the renderer to the canvas's current client dimensions. */\n resize: () => void\n}\n\n/**\n * Create a Matter renderer wrapping THREE.WebGPURenderer.\n *\n * Tries WebGPU first; falls back to WebGL2 automatically if WebGPU is\n * unavailable on the host. The returned object exposes the underlying\n * three renderer plus a small wrapper for resize and disposal.\n */\nexport async function createRenderer(\n canvas: HTMLCanvasElement,\n opts: CreateRendererOptions = {},\n): Promise<GpuRenderer> {\n const {\n antialias = true,\n forceWebGL = false,\n clearColor = 0x000000,\n clearAlpha = 0,\n maxDPR = 2,\n } = opts\n\n const three = new WebGPURenderer({\n canvas,\n antialias,\n forceWebGL,\n })\n\n await three.init()\n\n three.setPixelRatio(Math.min(window.devicePixelRatio, maxDPR))\n const resolvedClearColor = clearColor instanceof Color ? clearColor : new Color(clearColor)\n\n three.setClearColor(resolvedClearColor, clearAlpha)\n\n const resize = () => {\n const w = canvas.clientWidth\n const h = canvas.clientHeight\n\n if (canvas.width !== w * three.getPixelRatio() || canvas.height !== h * three.getPixelRatio()) {\n three.setSize(w, h, false)\n }\n }\n\n resize()\n\n // Detect backend after init. `isWebGLBackend` is an internal duck-type flag\n // not declared in three's public Backend type — probe via `in` rather than\n // a property access that would trip strict typing.\n const isWebGL = 'isWebGLBackend' in three.backend && three.backend.isWebGLBackend === true\n const backend: GpuBackend = forceWebGL || isWebGL ? 'webgl2' : 'webgpu'\n\n return {\n three,\n backend,\n dispose: () => three.dispose(),\n resize,\n }\n}\n","export type Vec2 = readonly [number, number]\n\nexport interface CursorInputOptions {\n /**\n * Smoothing factor: 0 = no smoothing (snap to target instantly).\n * 1 = max smoothing (essentially never reaches target).\n * Sensible default: 0.1.\n *\n * Implementation: per-frame, value moves toward target by `(1 - smoothing) * delta * 60`,\n * roughly meaning \"at smoothing=0.1, ~90% of the gap is closed in 1 second at 60fps.\"\n */\n smoothing?: number\n /** Starting position. Default: [0.5, 0.5] (center). */\n initial?: Vec2\n /** Listen on this target. Default: window. */\n target?: EventTarget\n /**\n * Element to normalize cursor coordinates against. Default: window viewport.\n *\n * When set, cursor x/y are in [0,1] across the element's bounding rect, with\n * extrapolation outside (negative when left/above, >1 when right/below). This\n * matches what shader UV space expects: a cursor at the canvas's top-left\n * corner reads as (0, 0); at bottom-right as (1, 1); regardless of where the\n * canvas sits in the viewport. Without this, components inside a partial-\n * viewport scene (e.g. a 70vh hero section) see a cursor offset that scales\n * with the canvas's vertical position on the page.\n */\n element?: {\n getBoundingClientRect(): { left: number; top: number; width: number; height: number }\n }\n}\n\ntype ChangeListener = (value: Vec2) => void\n\n/**\n * Smoothed pointer tracker emitting a normalized (0..1) Vec2 position.\n * Implements the AnimatableSignal protocol (`get()` + `on('change', cb)`)\n * so it composes with Motion's `useTransform` and similar tools.\n */\nexport class CursorInput {\n private value: [number, number]\n private target: [number, number]\n private targetDirty = false\n private readonly smoothing: number\n private readonly listeners = new Set<ChangeListener>()\n private readonly eventTarget: EventTarget\n private readonly element: CursorInputOptions['element']\n private readonly handleMouseMove: (e: Event) => void\n private disposed = false\n\n constructor(opts: CursorInputOptions = {}) {\n const { smoothing = 0.1, initial = [0.5, 0.5], target, element } = opts\n\n this.smoothing = clamp01(smoothing)\n this.value = [initial[0], initial[1]]\n this.target = [initial[0], initial[1]]\n this.eventTarget = target ?? (typeof window !== 'undefined' ? window : new EventTarget())\n this.element = element\n\n this.handleMouseMove = (e: Event) => {\n if (!(e instanceof MouseEvent)) return\n const me = e\n\n if (this.element) {\n // Normalize to 0..1 across the element's bounding rect. Reading the\n // rect on every move is fine — `getBoundingClientRect` is cheap and\n // mousemove is already throttled to ~60Hz by the browser. The benefit\n // is tracking the element's position even if it moved/scrolled since\n // the last frame.\n const r = this.element.getBoundingClientRect()\n const w = r.width || 1\n const h = r.height || 1\n\n this.target = [(me.clientX - r.left) / w, (me.clientY - r.top) / h]\n } else {\n // Fallback: viewport-normalized. Used when no element is supplied —\n // mostly the standalone-API case for users not consuming through\n // <ShaderScene>'s context.\n const w = (typeof window !== 'undefined' && window.innerWidth) || 1\n const h = (typeof window !== 'undefined' && window.innerHeight) || 1\n\n this.target = [me.clientX / w, me.clientY / h]\n }\n this.targetDirty = true\n }\n\n this.eventTarget.addEventListener('mousemove', this.handleMouseMove)\n }\n\n /** Current smoothed position. Implements AnimatableSignal protocol. */\n get(): Vec2 {\n return this.value\n }\n\n /** Subscribe to change events. Returns an unsubscribe function. */\n on(_event: 'change', cb: ChangeListener): () => void {\n this.listeners.add(cb)\n\n return () => this.listeners.delete(cb)\n }\n\n /**\n * Advance the smoothing one tick. Called by the host scheduler; not\n * typically called directly except in tests.\n */\n tick(delta: number): void {\n if (this.disposed) return\n const factor = this.smoothing === 0 ? 1 : 1 - Math.pow(this.smoothing, delta * 60)\n const prev0 = this.value[0]\n const prev1 = this.value[1]\n const next0 = lerp(prev0, this.target[0], factor)\n const next1 = lerp(prev1, this.target[1], factor)\n const moved = next0 !== prev0 || next1 !== prev1\n\n if (moved || this.targetDirty) {\n this.value = [next0, next1]\n this.targetDirty = false\n const snapshot: Vec2 = [next0, next1]\n\n for (const listener of this.listeners) listener(snapshot)\n }\n }\n\n /** Tear down listeners. */\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.eventTarget.removeEventListener('mousemove', this.handleMouseMove)\n this.listeners.clear()\n }\n}\n\nconst clamp01 = (n: number) => Math.max(0, Math.min(1, n))\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t\n","import type { ShaderNodeObject } from 'three/tsl'\nimport { mix, vec3 } from 'three/tsl'\nimport { clamp, div, sub } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Canonical TSL-node *input* shape used throughout `@lovo/matter`.\n *\n * Stays as the broad `Node | ShaderNodeObject<Node>` union so callers can\n * pass uniform-typed nodes (e.g. `ShaderNodeObject<UniformNode<Vector2>>`)\n * without casting at the call site — those are subtypes of `Node` but NOT\n * subtypes of `ShaderNodeObject<Node>` due to invariant generic parameters.\n *\n * Wrappers should return the narrower `ShaderNodeObject<Node>` so the\n * **output** is always chainable without casts.\n */\nexport type TSLNode = Node | ShaderNodeObject<Node>\n\nexport interface ColorRampStop {\n /** Color expressed as a TSL node (typically `vec3(r,g,b)`). */\n color: TSLNode\n /** Position 0..1 along the ramp. */\n position: number\n}\n\n/**\n * Multi-stop color interpolation. Given a t in [0..1] and N color stops at\n * fixed positions, returns the smoothly-interpolated color.\n *\n * Falls back to the first/last stop's color outside the bracketing positions.\n */\nexport function colorRamp(t: TSLNode, stops: ColorRampStop[]): ShaderNodeObject<Node> {\n // TSLNode is wider than ShaderNodeObject<Node> in TSL's published types\n // (see CLAUDE.md gotcha #5). Wrapping with mix(node, node, 0) yields a\n // chainable ShaderNodeObject<Node> without a cast — the GPU shader compiler\n // folds the no-op interpolation away.\n const first = stops[0]\n\n if (first === undefined) return vec3(0, 0, 0)\n if (stops.length === 1) return mix(first.color, first.color, 0)\n\n // Build a chain of nested mixes, one per adjacent pair of stops.\n // For three stops at positions 0, 0.5, 1:\n // inner = mix(stop0, stop1, smoothstep(0, 0.5, t))\n // outer = mix(inner, stop2, smoothstep(0.5, 1, t))\n let result = mix(first.color, first.color, 0)\n\n for (let i = 1; i < stops.length; i += 1) {\n const prev = stops[i - 1]\n const next = stops[i]\n\n if (prev === undefined || next === undefined) continue\n const span = next.position - prev.position\n\n if (span <= 0) continue\n // Localize t into the [prev..next] range. `t` is TSLNode (the union),\n // so we use functional-form ops to avoid needing a chain-method receiver.\n const localT = clamp(div(sub(t, prev.position), span), 0, 1)\n\n result = mix(result, next.color, localT)\n }\n\n return result\n}\n","// packages/matter/src/primitives/noise/noise.ts\nimport { mx_noise_float } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\n\n/**\n * 2D simplex noise sampled at a point. Returns a scalar TSL node in\n * approximately [-1, 1] (MaterialX's mx_noise_float is roughly that range).\n *\n * @param p — Vec2 TSL node (typically `uv()` or a scaled/offset uv).\n *\n * Built on top of three's `mx_noise_float`; we wrap it so consumers have a\n * stable import path through `@lovo/matter` and we can swap the\n * implementation if a different noise primitive proves better in practice.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) rather than the broader\n * `TSLNode` union, so callers can `.add(...)`/`.mul(...)` without casting.\n */\nexport function noise(p: TSLNode): ShaderNodeObject<Node> {\n return mx_noise_float(p)\n}\n","// packages/matter/src/primitives/fbm/fbm.ts\nimport { add, mul } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\nimport { noise } from '../noise/noise.js'\n\nexport interface FBMOptions {\n /** Number of octaves to sum. JS-side number — fixed at TSL build time, not a uniform. Default: 4. */\n octaves?: number\n /** Per-octave frequency multiplier. JS-side number. Default: 2. */\n lacunarity?: number\n /** Per-octave amplitude multiplier. JS-side number. Default: 0.5. */\n gain?: number\n}\n\n/**\n * Fractal Brownian Motion — sum of N octaves of 2D simplex noise.\n *\n * Each octave samples noise at a higher frequency (× `lacunarity`) and lower\n * amplitude (× `gain`) than the previous one, AND at a translated coordinate\n * so the octaves sample uncorrelated regions of noise space. Without the\n * per-octave translation, octaves at related frequencies tend to pile up\n * peaks and troughs at the same input coordinates, producing visibly muddy\n * \"spotty\" output. With it, the octaves look like independent noise patterns\n * layered together — Inigo Quilez's classic FBM technique.\n *\n * `octaves`, `lacunarity`, and `gain` are JavaScript numbers (NOT TSL\n * uniforms) because the loop must be unrolled at TSL-build time — TSL has\n * no dynamic-length loop primitive that maps cleanly to all backends.\n * Animatable parameters that *do* survive on the GPU are the input UV\n * (which the caller can scale/translate per frame) and `time`.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) for cast-free call sites.\n *\n * @param p — Vec2 or Vec3 TSL node (UV-space position).\n * @returns scalar TSL node, normalized to roughly [-1..1] regardless of\n * octave count thanks to the amplitude-sum division at the end.\n */\nexport function fbm(p: TSLNode, opts: FBMOptions = {}): ShaderNodeObject<Node> {\n const octaves = opts.octaves ?? 4\n const lacunarity = opts.lacunarity ?? 2\n const gain = opts.gain ?? 0.5\n\n let sum: ShaderNodeObject<Node> = noise(p)\n let amp = 1\n let freq = 1\n let total = amp\n\n for (let i = 1; i < octaves; i += 1) {\n freq *= lacunarity\n amp *= gain\n total += amp\n // Per-octave decorrelation: translate the sample point by a growing\n // offset so this octave reads from a totally different region of noise\n // space than the previous one. Magnitude 100 is well past simplex\n // noise's ~1-unit feature size, so adjacent octaves are fully\n // decorrelated. The scalar broadcasts across all components of `p`\n // (works for vec2 and vec3 inputs alike).\n //\n // Build the chain functionally from `p`: gotcha #12 doesn't apply\n // because `p` is uv-rooted, but the TSLNode union still requires\n // functional form on this hop.\n const pAtFreq = add(mul(p, freq), i * 100)\n const layer = noise(pAtFreq).mul(amp)\n\n sum = sum.add(layer)\n }\n\n // Normalize to approximate [-1..1] regardless of octave count / gain.\n return sum.div(total)\n}\n","// packages/matter/src/primitives/voronoi/voronoi.ts\nimport { mx_worley_noise_float } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\n\n/**\n * 2D voronoi (Worley) noise — distance to the nearest jittered cell point,\n * normalized roughly to [0, 1]. Higher values = farther from any cell point\n * (cell interiors); lower values = near a cell boundary.\n *\n * Built on three's `mx_worley_noise_float`. Combine with `colorRamp` for\n * a multi-color cellular pattern; threshold via `step`/`smoothstep` for\n * hard cell shapes.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) for cast-free call sites.\n *\n * @param p — Vec2 TSL node, typically `uv() * scale`.\n */\nexport function voronoi(p: TSLNode): ShaderNodeObject<Node> {\n return mx_worley_noise_float(p)\n}\n","// packages/matter/src/primitives/quantize/quantize.ts\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Quantize a scalar TSL node to `steps` discrete levels.\n *\n * quantize(t, 4) → values in {0, 0.25, 0.5, 0.75, 1.0}\n *\n * `steps` is a JS-side number (loop-equivalent at TSL build time, baked in).\n * If you need an animatable step count, rebuild the TSL fragment.\n */\nexport function quantize(t: ShaderNodeObject<Node>, steps: number): ShaderNodeObject<Node> {\n if (steps <= 1) {\n // Edge case: single step → constant 0. Return as-is wrapped in mul(0).\n return t.mul(0)\n }\n const denom = steps - 1\n\n // floor(t * (steps-1) + 0.5) / (steps-1)\n // Using floor(x + 0.5) instead of round() for TSL portability.\n return t.mul(denom).add(0.5).floor().div(denom)\n}\n","import { length } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\n\n/**\n * Signed distance field for a circle centered at the origin.\n *\n * sdfCircle(p, r) = length(p) - r\n *\n * Negative inside the circle, zero on the boundary, positive outside.\n * Combine with `smoothstep(-edge, +edge, sdf)` to render a soft-edged disk.\n *\n * @param p — Vec2 TSL node (typically a UV-space offset from the center).\n * @param radius — JS-side scalar OR a scalar TSL node.\n */\nexport function sdfCircle(p: TSLNode, radius: TSLNode | number): ShaderNodeObject<Node> {\n return length(p).sub(radius)\n}\n","import { add } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\n\n/**\n * Naive vector addition: returns `p + by`.\n *\n * displace(p, by) = p + by\n *\n * Thin wrapper that names the spatial intent of shifting a sample point.\n *\n * **SDF caveat:** when using this to translate an SDF render, pass the\n * NEGATED translation — `sdfCircle(displace(p, v.mul(-1)), r)` renders the\n * disk at position `+v` because SDF translation evaluates as\n * `length(p - center) - r`. Adding `+v` to the sample point shifts the\n * rendered shape in the OPPOSITE direction.\n *\n * @param p — Vec2 TSL node (the position being displaced).\n * @param by — Vec2 TSL node (the displacement vector).\n */\nexport function displace(p: TSLNode, by: TSLNode): ShaderNodeObject<Node> {\n return add(p, by)\n}\n","import { length, sin, smoothstep, sub } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\nimport { time } from '../time/time.js'\n\nexport interface CursorRippleOptions {\n /** Decay radius (UV space). Beyond this, the ripple is ~0. Default: 0.4. */\n reach?: number\n /** Wavelength controls the ripple spacing. Default: 30. Larger = wider rings. */\n frequency?: number\n /** Time multiplier on the wave phase. Default: 6. Larger = faster oscillation. */\n speed?: number\n /** Output amplitude. Default: 0.5. Final result is in roughly [-amplitude, +amplitude]. */\n amplitude?: number\n}\n\n/**\n * A radial ripple emanating from `center`. Returns a scalar TSL node in\n * roughly [-amplitude, +amplitude] that decays to ~0 outside `reach`.\n *\n * ripple = sin(d*frequency - time*speed) * amplitude * smoothstep(reach, 0, d)\n *\n * Compose into a wave field by adding it to the underlying base wave.\n *\n * Note: `frequency` / `speed` / `reach` / `amplitude` are JS-side numbers\n * (baked into the TSL fragment at material-build time). The animatable\n * cursor position is the only live uniform consumed.\n *\n * @param p — Vec2 TSL node (typically `uv()`).\n * @param center — Vec2 TSL node (cursor uniform, in UV space).\n */\nexport function cursorRipple(\n p: TSLNode,\n center: TSLNode,\n opts: CursorRippleOptions = {},\n): ShaderNodeObject<Node> {\n const reach = opts.reach ?? 0.4\n const frequency = opts.frequency ?? 30\n const speed = opts.speed ?? 6\n const amplitude = opts.amplitude ?? 0.5\n\n // d = length(p - center). Use functional `sub(p, center)` because both\n // are typed as the broad TSLNode union (no chain receiver). Per gotcha #12,\n // building from a raw `uniform()` receiver silently produces wrong GPU\n // values, so the functional form is also safer for `center` being a uniform.\n const d = length(sub(p, center))\n // `time` is the engine-gated TSL node (from primitives/time/time.ts);\n // chains rooted in `time` automatically respect `prefers-reduced-motion` and\n // the runtime override set via `setReducedMotionPolicy`.\n const wave = sin(d.mul(frequency).sub(time.mul(speed)))\n const decay = smoothstep(reach, 0, d)\n\n return wave.mul(amplitude).mul(decay)\n}\n","// Engine-gated `time` — equals the TSL built-in `time` multiplied by the\n// reduced-motion scale uniform. Components consuming `time` from `@lovo/matter`\n// automatically respect `prefers-reduced-motion` and the policy override set\n// via `setReducedMotionPolicy`.\n//\n// If you want raw uncapped time (e.g. for a debug overlay), import `time`\n// from `three/tsl` directly.\n\nimport { time as _builtinTime } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport { getReducedMotionTimeScale } from '../../runtime/reduced-motion/reduced-motion.js'\n\nexport const time: ShaderNodeObject<Node> = _builtinTime.mul(getReducedMotionTimeScale())\n","import { uniform } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nexport type ReducedMotionPolicy = 'auto' | 'off' | 'slow' | 'paused'\n\n/**\n * Public surface exposed to package consumers. `recompute` is intentionally\n * absent — it is engine-internal and should not be callable from outside.\n */\nexport interface ReducedMotionWatcher {\n /** Current time scale: 0, 0.3, or 1. */\n scale(): number\n /** Subscribe to scale changes. Returns unsubscribe. */\n subscribe(cb: (scale: number) => void): () => void\n /** Tear down media-query listener. */\n dispose(): void\n}\n\n/**\n * Engine-internal extension of the public watcher. Only `setReducedMotionPolicy`\n * calls `recompute`; it is never part of the consumer-visible type.\n */\ninterface InternalWatcher extends ReducedMotionWatcher {\n recompute(): void\n}\n\ninterface PolicyState {\n policy: ReducedMotionPolicy\n watchers: Set<InternalWatcher>\n}\n\nconst state: PolicyState = {\n policy: 'auto',\n watchers: new Set(),\n}\n\n/**\n * Override Matter's default behavior of honoring `prefers-reduced-motion`.\n * - 'auto' — follow the OS media query (default)\n * - 'off' — full speed regardless of OS setting\n * - 'slow' — 30% speed regardless of OS setting\n * - 'paused' — 0 (animation effectively frozen) regardless of OS setting\n */\nexport function setReducedMotionPolicy(policy: ReducedMotionPolicy): void {\n if (state.policy === policy) return\n state.policy = policy\n for (const w of state.watchers) w.recompute()\n}\n\nexport function getReducedMotionPolicy(): ReducedMotionPolicy {\n return state.policy\n}\n\nconst computeScale = (mqlMatches: boolean): number => {\n switch (state.policy) {\n case 'off':\n return 1\n case 'slow':\n return 0.3\n case 'paused':\n return 0\n case 'auto':\n return mqlMatches ? 0.3 : 1\n }\n}\n\n/**\n * Create a watcher that tracks `prefers-reduced-motion: reduce` and the\n * global Matter policy override. Strict-mode-safe — callers create+dispose\n * one per mount cycle.\n */\nexport function createReducedMotionWatcher(): ReducedMotionWatcher {\n // SSR safety: bail to the no-op watcher if matchMedia is missing.\n // SSR watcher: scale() respects policy override but does not emit\n // subscription events (the engine has no way to notify SSR-created\n // watchers because they are not added to state.watchers — but in\n // practice CLAUDE.md gotcha #10 requires `ssr: false` for any component\n // that touches the matter engine).\n if (typeof matchMedia !== 'function') {\n return {\n scale: () => computeScale(false),\n subscribe: (cb) => {\n // No-op: SSR watchers are not in state.watchers and will never\n // receive policy-change notifications.\n void cb\n\n return () => {\n // SSR no-op unsubscribe\n }\n },\n /** SSR watcher does not emit policy-change notifications. */\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const mql = matchMedia('(prefers-reduced-motion: reduce)')\n const subs = new Set<(s: number) => void>()\n let last = computeScale(mql.matches)\n\n const onChange = () => {\n const next = computeScale(mql.matches)\n\n if (next !== last) {\n last = next\n for (const cb of subs) cb(next)\n }\n }\n\n mql.addEventListener('change', onChange)\n\n const watcher: InternalWatcher = {\n scale: () => last,\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n recompute() {\n const next = computeScale(mql.matches)\n\n if (next !== last) {\n last = next\n for (const cb of subs) cb(next)\n }\n },\n dispose() {\n mql.removeEventListener('change', onChange)\n subs.clear()\n state.watchers.delete(watcher)\n },\n }\n\n state.watchers.add(watcher)\n\n return watcher\n}\n\nlet globalScaleUniform: ReturnType<typeof uniform<number>> | null = null\nlet globalWatcher: ReducedMotionWatcher | null = null\n\n/**\n * Returns the engine-shared TSL uniform that `time` is multiplied by. Lazily\n * initialized on first read; reused across all materials. Mutating `.value`\n * imperatively when policy changes is safe — TSL re-reads the uniform every\n * frame.\n */\nexport function getReducedMotionTimeScale(): ShaderNodeObject<Node> {\n if (globalScaleUniform === null) {\n globalWatcher = createReducedMotionWatcher()\n globalScaleUniform = uniform(globalWatcher.scale())\n globalWatcher.subscribe((s) => {\n if (globalScaleUniform === null) return\n globalScaleUniform.value = s\n })\n }\n\n // ShaderNodeObject<UniformNode<number>> isn't structurally assignable to\n // ShaderNodeObject<Node> (invariant generic methods); chains work the same\n // at runtime, so widen at the return boundary.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n return globalScaleUniform as unknown as ShaderNodeObject<Node>\n}\n\n// Keep a typed reference for tests that may want to re-init between tests.\nexport const __resetReducedMotionForTests = () => {\n globalWatcher?.dispose()\n globalWatcher = null\n globalScaleUniform = null\n}\n","import { fract, length, sin, vec2 } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Hash-based film grain — chaotic, uncorrelated per-pixel noise sampled\n * from `uvNode`. The output is *centered* around zero so it acts as a\n * brightness-preserving texture overlay (half the pixels brighten by up\n * to `intensity`, half darken, mean unchanged). ADD the result to a color.\n *\n * filmGrain(uv, k) → static grain\n * filmGrain(uv, k, time) → twinkling grain. Pass a quantized time node\n * (e.g. `time.mul(speed).mul(60).floor()`) so\n * the grain re-randomizes at a controllable\n * \"shutter rate\" instead of every frame.\n *\n * Recipe:\n *\n * base = vec2(uv·c1, uv·c2) + timeOffset\n * hash = fract(sin(base) * 43758.5453)\n * out = (length(hash) - 0.765) * intensity\n *\n * `c1 = (2127.1, 81.17)` and `c2 = (1269.5, 283.37)` are arbitrary\n * near-prime constants that produce visually-uncorrelated noise. `0.765`\n * is the empirical mean of `length(vec2(u, v))` for uniform u, v ∈ [0, 1),\n * computed once so we don't have to subtract it at runtime per pixel.\n *\n * For a film-stock look (darkens as grain rises — silver-emulsion\n * physics) subtract the result from the color instead of adding.\n *\n * @param uvNode vec2 TSL node, typically `uv()`.\n * @param intensity number or TSL node in [0, 1]; scales the grain.\n * @param timeOffset optional number or TSL node added to each sample\n * before hashing. `0` (default) → static grain.\n */\nexport function filmGrain(\n uvNode: ShaderNodeObject<Node>,\n intensity: ShaderNodeObject<Node> | number,\n timeOffset: ShaderNodeObject<Node> | number = 0,\n): ShaderNodeObject<Node> {\n const HASH_C1 = vec2(2127.1, 81.17)\n const HASH_C2 = vec2(1269.5, 283.37)\n const base = vec2(uvNode.dot(HASH_C1).add(timeOffset), uvNode.dot(HASH_C2).add(timeOffset))\n\n const hash = fract(sin(base).mul(43758.5453))\n\n return length(hash).sub(0.765).mul(intensity)\n}\n","export interface VisibilityWatcher {\n isVisible(): boolean\n /** Subscribe to changes. Receives the new visibility state. Returns unsubscribe. */\n subscribe(cb: (visible: boolean) => void): () => void\n dispose(): void\n}\n\n/**\n * Watch `document.visibilityState`. Strict-mode-safe — callers create+dispose\n * one per mount cycle.\n *\n * SSR: if `document` is unavailable, returns a no-op watcher whose\n * `isVisible()` always returns `true` and whose `subscribe` does nothing.\n */\nexport function createVisibilityWatcher(): VisibilityWatcher {\n if (typeof document === 'undefined') {\n return {\n isVisible: () => true,\n subscribe: () => () => {\n // SSR no-op unsubscribe\n },\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const subs = new Set<(v: boolean) => void>()\n const onChange = () => {\n const v = document.visibilityState === 'visible'\n\n for (const cb of subs) cb(v)\n }\n\n document.addEventListener('visibilitychange', onChange)\n\n return {\n isVisible: () => document.visibilityState === 'visible',\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n dispose() {\n document.removeEventListener('visibilitychange', onChange)\n subs.clear()\n },\n }\n}\n","export interface IntersectionWatcher {\n isInView(): boolean\n /** Subscribe to changes. Receives the new in-view state. Returns unsubscribe. */\n subscribe(cb: (inView: boolean) => void): () => void\n dispose(): void\n}\n\n/**\n * Watch a canvas's viewport intersection. Pauses tied to this watcher should\n * be resumed when the canvas is *any* fraction visible. Strict-mode-safe.\n *\n * SSR: if `IntersectionObserver` is unavailable, returns a no-op watcher whose\n * `isInView()` always returns `true` and whose `subscribe` does nothing.\n */\nexport function createIntersectionWatcher(canvas: HTMLCanvasElement): IntersectionWatcher {\n if (typeof IntersectionObserver === 'undefined') {\n return {\n isInView: () => true,\n subscribe: () => () => {\n // SSR no-op unsubscribe\n },\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const subs = new Set<(v: boolean) => void>()\n let inView = true\n const obs = new IntersectionObserver(\n (entries) => {\n const next = entries.some((e) => e.isIntersecting)\n\n if (next === inView) return\n inView = next\n for (const cb of subs) cb(inView)\n },\n { threshold: 0 },\n )\n\n obs.observe(canvas)\n\n return {\n isInView: () => inView,\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n dispose() {\n obs.disconnect()\n subs.clear()\n },\n }\n}\n","export interface SchedulerTick {\n /** Seconds since the previous tick. 0 on the first call. */\n delta: number\n /** Total seconds since the scheduler started its current run. */\n elapsed: number\n /** The raw `performance.now()` timestamp the rAF callback received. */\n now: number\n}\n\nexport type SchedulerClient = (tick: SchedulerTick) => void\n\n/**\n * Batches `requestAnimationFrame` calls across all clients registered with\n * a single scheduler. One scheduler is created per <ShaderScene>; clients\n * are typically a Three.js renderer's render call.\n */\nexport class FrameScheduler {\n private readonly clients = new Set<SchedulerClient>()\n private rafId: number | null = null\n private running = false\n private paused = false\n private idle = false\n private flushPending = false\n private startedAt = 0\n private lastTickAt = 0\n\n /** Activate the scheduler. The rAF loop starts on the first client added. */\n start(): void {\n this.running = true\n this.paused = false\n this.maybeQueue()\n }\n\n /** Halt the rAF loop entirely. Use dispose() for permanent teardown. */\n stop(): void {\n this.running = false\n this.cancel()\n }\n\n /** Temporarily skip ticks without losing client registrations. */\n pause(): void {\n this.paused = true\n }\n\n /** Resume after pause(). */\n resume(): void {\n this.paused = false\n if (this.running) this.maybeQueue()\n }\n\n /** Register a client to be called every frame. */\n add(client: SchedulerClient): void {\n this.clients.add(client)\n if (this.running) this.maybeQueue()\n }\n\n /** Unregister a client. */\n remove(client: SchedulerClient): void {\n this.clients.delete(client)\n }\n\n /** Permanent teardown: stop the loop and drop all clients. */\n dispose(): void {\n this.stop()\n this.clients.clear()\n }\n\n /**\n * Mark the scheduler idle. The next tick still fires (a final flush so\n * uniform changes that triggered the idle state are rendered), then the\n * rAF loop halts. Use `requestRender()` or `setIdle(false)` to wake.\n */\n setIdle(idle: boolean): void {\n if (this.idle === idle) return\n this.idle = idle\n if (idle) {\n this.flushPending = true\n this.maybeQueue()\n } else {\n this.flushPending = false\n this.maybeQueue()\n }\n }\n\n /** Force a single tick while idle. Useful for prop-change invalidation. */\n requestRender(): void {\n if (!this.idle) return\n this.flushPending = true\n this.maybeQueue()\n }\n\n private maybeQueue(): void {\n if (this.rafId !== null) return\n if (!this.running) return\n if (this.clients.size === 0) return\n if (this.idle && !this.flushPending) return\n this.rafId = requestAnimationFrame(this.frame)\n }\n\n private cancel(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n }\n\n private readonly frame = (now: number): void => {\n this.rafId = null\n if (!this.running || this.paused) return\n\n if (this.startedAt === 0) {\n this.startedAt = now\n this.lastTickAt = now\n }\n const delta = (now - this.lastTickAt) / 1000\n const elapsed = (now - this.startedAt) / 1000\n\n this.lastTickAt = now\n\n const tick: SchedulerTick = { delta, elapsed, now }\n\n for (const client of this.clients) {\n client(tick)\n }\n\n this.flushPending = false\n this.maybeQueue()\n }\n}\n"],"mappings":"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","names":["import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","import_tsl","_builtinTime","import_tsl"]}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/runtime/
|
|
1
|
+
// src/runtime/create-renderer/create-renderer.ts
|
|
2
2
|
import { Color } from "three";
|
|
3
3
|
import { WebGPURenderer } from "three/webgpu";
|
|
4
4
|
async function createRenderer(canvas, opts = {}) {
|
|
@@ -36,7 +36,7 @@ async function createRenderer(canvas, opts = {}) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// src/inputs/
|
|
39
|
+
// src/inputs/cursor-input/cursor-input.ts
|
|
40
40
|
var CursorInput = class {
|
|
41
41
|
value;
|
|
42
42
|
target;
|
|
@@ -110,7 +110,7 @@ var CursorInput = class {
|
|
|
110
110
|
var clamp01 = (n) => Math.max(0, Math.min(1, n));
|
|
111
111
|
var lerp = (a, b, t) => a + (b - a) * t;
|
|
112
112
|
|
|
113
|
-
// src/primitives/
|
|
113
|
+
// src/primitives/color-ramp/color-ramp.ts
|
|
114
114
|
import { mix, vec3 } from "three/tsl";
|
|
115
115
|
import { clamp, div, sub } from "three/tsl";
|
|
116
116
|
function colorRamp(t, stops) {
|
|
@@ -130,13 +130,13 @@ function colorRamp(t, stops) {
|
|
|
130
130
|
return result;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
// src/primitives/noise.ts
|
|
133
|
+
// src/primitives/noise/noise.ts
|
|
134
134
|
import { mx_noise_float } from "three/tsl";
|
|
135
135
|
function noise(p) {
|
|
136
136
|
return mx_noise_float(p);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// src/primitives/fbm.ts
|
|
139
|
+
// src/primitives/fbm/fbm.ts
|
|
140
140
|
import { add, mul } from "three/tsl";
|
|
141
141
|
function fbm(p, opts = {}) {
|
|
142
142
|
const octaves = opts.octaves ?? 4;
|
|
@@ -157,13 +157,13 @@ function fbm(p, opts = {}) {
|
|
|
157
157
|
return sum.div(total);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
// src/primitives/voronoi.ts
|
|
160
|
+
// src/primitives/voronoi/voronoi.ts
|
|
161
161
|
import { mx_worley_noise_float } from "three/tsl";
|
|
162
162
|
function voronoi(p) {
|
|
163
163
|
return mx_worley_noise_float(p);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
// src/primitives/quantize.ts
|
|
166
|
+
// src/primitives/quantize/quantize.ts
|
|
167
167
|
function quantize(t, steps) {
|
|
168
168
|
if (steps <= 1) {
|
|
169
169
|
return t.mul(0);
|
|
@@ -172,25 +172,25 @@ function quantize(t, steps) {
|
|
|
172
172
|
return t.mul(denom).add(0.5).floor().div(denom);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
// src/primitives/
|
|
175
|
+
// src/primitives/sdf-circle/sdf-circle.ts
|
|
176
176
|
import { length } from "three/tsl";
|
|
177
177
|
function sdfCircle(p, radius) {
|
|
178
178
|
return length(p).sub(radius);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
// src/primitives/displace.ts
|
|
181
|
+
// src/primitives/displace/displace.ts
|
|
182
182
|
import { add as add2 } from "three/tsl";
|
|
183
183
|
function displace(p, by) {
|
|
184
184
|
return add2(p, by);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
// src/primitives/
|
|
187
|
+
// src/primitives/cursor-ripple/cursor-ripple.ts
|
|
188
188
|
import { length as length2, sin, smoothstep, sub as sub2 } from "three/tsl";
|
|
189
189
|
|
|
190
|
-
// src/primitives/time.ts
|
|
190
|
+
// src/primitives/time/time.ts
|
|
191
191
|
import { time as _builtinTime } from "three/tsl";
|
|
192
192
|
|
|
193
|
-
// src/runtime/
|
|
193
|
+
// src/runtime/reduced-motion/reduced-motion.ts
|
|
194
194
|
import { uniform } from "three/tsl";
|
|
195
195
|
var state = {
|
|
196
196
|
policy: "auto",
|
|
@@ -277,10 +277,10 @@ function getReducedMotionTimeScale() {
|
|
|
277
277
|
return globalScaleUniform;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
// src/primitives/time.ts
|
|
280
|
+
// src/primitives/time/time.ts
|
|
281
281
|
var time = _builtinTime.mul(getReducedMotionTimeScale());
|
|
282
282
|
|
|
283
|
-
// src/primitives/
|
|
283
|
+
// src/primitives/cursor-ripple/cursor-ripple.ts
|
|
284
284
|
function cursorRipple(p, center, opts = {}) {
|
|
285
285
|
const reach = opts.reach ?? 0.4;
|
|
286
286
|
const frequency = opts.frequency ?? 30;
|
|
@@ -292,7 +292,7 @@ function cursorRipple(p, center, opts = {}) {
|
|
|
292
292
|
return wave.mul(amplitude).mul(decay);
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
// src/primitives/
|
|
295
|
+
// src/primitives/film-grain/film-grain.ts
|
|
296
296
|
import { fract, length as length3, sin as sin2, vec2 } from "three/tsl";
|
|
297
297
|
function filmGrain(uvNode, intensity, timeOffset = 0) {
|
|
298
298
|
const HASH_C1 = vec2(2127.1, 81.17);
|
|
@@ -302,7 +302,7 @@ function filmGrain(uvNode, intensity, timeOffset = 0) {
|
|
|
302
302
|
return length3(hash).sub(0.765).mul(intensity);
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
// src/runtime/visibility.ts
|
|
305
|
+
// src/runtime/visibility/visibility.ts
|
|
306
306
|
function createVisibilityWatcher() {
|
|
307
307
|
if (typeof document === "undefined") {
|
|
308
308
|
return {
|
|
@@ -332,7 +332,7 @@ function createVisibilityWatcher() {
|
|
|
332
332
|
};
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
// src/runtime/intersection.ts
|
|
335
|
+
// src/runtime/intersection/intersection.ts
|
|
336
336
|
function createIntersectionWatcher(canvas) {
|
|
337
337
|
if (typeof IntersectionObserver === "undefined") {
|
|
338
338
|
return {
|
|
@@ -368,7 +368,7 @@ function createIntersectionWatcher(canvas) {
|
|
|
368
368
|
};
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
// src/runtime/frame-scheduler.ts
|
|
371
|
+
// src/runtime/frame-scheduler/frame-scheduler.ts
|
|
372
372
|
var FrameScheduler = class {
|
|
373
373
|
clients = /* @__PURE__ */ new Set();
|
|
374
374
|
rafId = null;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/createRenderer.ts","../src/inputs/CursorInput.ts","../src/primitives/colorRamp.ts","../src/primitives/noise.ts","../src/primitives/fbm.ts","../src/primitives/voronoi.ts","../src/primitives/quantize.ts","../src/primitives/sdfCircle.ts","../src/primitives/displace.ts","../src/primitives/cursorRipple.ts","../src/primitives/time.ts","../src/runtime/reducedMotion.ts","../src/primitives/filmGrain.ts","../src/runtime/visibility.ts","../src/runtime/intersection.ts","../src/runtime/frame-scheduler.ts"],"sourcesContent":["import { Color } from 'three'\nimport { WebGPURenderer } from 'three/webgpu'\n\nexport type GpuBackend = 'webgpu' | 'webgl2'\n\nexport interface CreateRendererOptions {\n /** Anti-alias the framebuffer. Default: true. */\n antialias?: boolean\n /** Force WebGL2 even if WebGPU is available (useful for testing fallback). Default: false. */\n forceWebGL?: boolean\n /** Clear color (hex, CSS string, or THREE.Color). Default: transparent. */\n clearColor?: number | string | Color\n /** Clear alpha (0–1). Default: 0 (transparent). */\n clearAlpha?: number\n /** Cap on devicePixelRatio. Default: 2. Pass Infinity to disable. */\n maxDPR?: number\n}\n\nexport interface GpuRenderer {\n /** The underlying Three.js WebGPURenderer (which may be running on a WebGL2 backend). */\n three: WebGPURenderer\n /** Which backend the renderer initialized with. */\n backend: GpuBackend\n /** Tear down the renderer and release GPU resources. */\n dispose: () => void\n /** Resize the renderer to the canvas's current client dimensions. */\n resize: () => void\n}\n\n/**\n * Create a Matter renderer wrapping THREE.WebGPURenderer.\n *\n * Tries WebGPU first; falls back to WebGL2 automatically if WebGPU is\n * unavailable on the host. The returned object exposes the underlying\n * three renderer plus a small wrapper for resize and disposal.\n */\nexport async function createRenderer(\n canvas: HTMLCanvasElement,\n opts: CreateRendererOptions = {},\n): Promise<GpuRenderer> {\n const {\n antialias = true,\n forceWebGL = false,\n clearColor = 0x000000,\n clearAlpha = 0,\n maxDPR = 2,\n } = opts\n\n const three = new WebGPURenderer({\n canvas,\n antialias,\n forceWebGL,\n })\n\n await three.init()\n\n three.setPixelRatio(Math.min(window.devicePixelRatio, maxDPR))\n const resolvedClearColor = clearColor instanceof Color ? clearColor : new Color(clearColor)\n\n three.setClearColor(resolvedClearColor, clearAlpha)\n\n const resize = () => {\n const w = canvas.clientWidth\n const h = canvas.clientHeight\n\n if (canvas.width !== w * three.getPixelRatio() || canvas.height !== h * three.getPixelRatio()) {\n three.setSize(w, h, false)\n }\n }\n\n resize()\n\n // Detect backend after init. `isWebGLBackend` is an internal duck-type flag\n // not declared in three's public Backend type — probe via `in` rather than\n // a property access that would trip strict typing.\n const isWebGL = 'isWebGLBackend' in three.backend && three.backend.isWebGLBackend === true\n const backend: GpuBackend = forceWebGL || isWebGL ? 'webgl2' : 'webgpu'\n\n return {\n three,\n backend,\n dispose: () => three.dispose(),\n resize,\n }\n}\n","export type Vec2 = readonly [number, number]\n\nexport interface CursorInputOptions {\n /**\n * Smoothing factor: 0 = no smoothing (snap to target instantly).\n * 1 = max smoothing (essentially never reaches target).\n * Sensible default: 0.1.\n *\n * Implementation: per-frame, value moves toward target by `(1 - smoothing) * delta * 60`,\n * roughly meaning \"at smoothing=0.1, ~90% of the gap is closed in 1 second at 60fps.\"\n */\n smoothing?: number\n /** Starting position. Default: [0.5, 0.5] (center). */\n initial?: Vec2\n /** Listen on this target. Default: window. */\n target?: EventTarget\n /**\n * Element to normalize cursor coordinates against. Default: window viewport.\n *\n * When set, cursor x/y are in [0,1] across the element's bounding rect, with\n * extrapolation outside (negative when left/above, >1 when right/below). This\n * matches what shader UV space expects: a cursor at the canvas's top-left\n * corner reads as (0, 0); at bottom-right as (1, 1); regardless of where the\n * canvas sits in the viewport. Without this, components inside a partial-\n * viewport scene (e.g. a 70vh hero section) see a cursor offset that scales\n * with the canvas's vertical position on the page.\n */\n element?: {\n getBoundingClientRect(): { left: number; top: number; width: number; height: number }\n }\n}\n\ntype ChangeListener = (value: Vec2) => void\n\n/**\n * Smoothed pointer tracker emitting a normalized (0..1) Vec2 position.\n * Implements the AnimatableSignal protocol (`get()` + `on('change', cb)`)\n * so it composes with Motion's `useTransform` and similar tools.\n */\nexport class CursorInput {\n private value: [number, number]\n private target: [number, number]\n private targetDirty = false\n private readonly smoothing: number\n private readonly listeners = new Set<ChangeListener>()\n private readonly eventTarget: EventTarget\n private readonly element: CursorInputOptions['element']\n private readonly handleMouseMove: (e: Event) => void\n private disposed = false\n\n constructor(opts: CursorInputOptions = {}) {\n const { smoothing = 0.1, initial = [0.5, 0.5], target, element } = opts\n\n this.smoothing = clamp01(smoothing)\n this.value = [initial[0], initial[1]]\n this.target = [initial[0], initial[1]]\n this.eventTarget = target ?? (typeof window !== 'undefined' ? window : new EventTarget())\n this.element = element\n\n this.handleMouseMove = (e: Event) => {\n if (!(e instanceof MouseEvent)) return\n const me = e\n\n if (this.element) {\n // Normalize to 0..1 across the element's bounding rect. Reading the\n // rect on every move is fine — `getBoundingClientRect` is cheap and\n // mousemove is already throttled to ~60Hz by the browser. The benefit\n // is tracking the element's position even if it moved/scrolled since\n // the last frame.\n const r = this.element.getBoundingClientRect()\n const w = r.width || 1\n const h = r.height || 1\n\n this.target = [(me.clientX - r.left) / w, (me.clientY - r.top) / h]\n } else {\n // Fallback: viewport-normalized. Used when no element is supplied —\n // mostly the standalone-API case for users not consuming through\n // <ShaderScene>'s context.\n const w = (typeof window !== 'undefined' && window.innerWidth) || 1\n const h = (typeof window !== 'undefined' && window.innerHeight) || 1\n\n this.target = [me.clientX / w, me.clientY / h]\n }\n this.targetDirty = true\n }\n\n this.eventTarget.addEventListener('mousemove', this.handleMouseMove)\n }\n\n /** Current smoothed position. Implements AnimatableSignal protocol. */\n get(): Vec2 {\n return this.value\n }\n\n /** Subscribe to change events. Returns an unsubscribe function. */\n on(_event: 'change', cb: ChangeListener): () => void {\n this.listeners.add(cb)\n\n return () => this.listeners.delete(cb)\n }\n\n /**\n * Advance the smoothing one tick. Called by the host scheduler; not\n * typically called directly except in tests.\n */\n tick(delta: number): void {\n if (this.disposed) return\n const factor = this.smoothing === 0 ? 1 : 1 - Math.pow(this.smoothing, delta * 60)\n const prev0 = this.value[0]\n const prev1 = this.value[1]\n const next0 = lerp(prev0, this.target[0], factor)\n const next1 = lerp(prev1, this.target[1], factor)\n const moved = next0 !== prev0 || next1 !== prev1\n\n if (moved || this.targetDirty) {\n this.value = [next0, next1]\n this.targetDirty = false\n const snapshot: Vec2 = [next0, next1]\n\n for (const listener of this.listeners) listener(snapshot)\n }\n }\n\n /** Tear down listeners. */\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.eventTarget.removeEventListener('mousemove', this.handleMouseMove)\n this.listeners.clear()\n }\n}\n\nconst clamp01 = (n: number) => Math.max(0, Math.min(1, n))\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t\n","import type { ShaderNodeObject } from 'three/tsl'\nimport { mix, vec3 } from 'three/tsl'\nimport { clamp, div, sub } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Canonical TSL-node *input* shape used throughout `@lovo/matter`.\n *\n * Stays as the broad `Node | ShaderNodeObject<Node>` union so callers can\n * pass uniform-typed nodes (e.g. `ShaderNodeObject<UniformNode<Vector2>>`)\n * without casting at the call site — those are subtypes of `Node` but NOT\n * subtypes of `ShaderNodeObject<Node>` due to invariant generic parameters.\n *\n * Wrappers should return the narrower `ShaderNodeObject<Node>` so the\n * **output** is always chainable without casts.\n */\nexport type TSLNode = Node | ShaderNodeObject<Node>\n\nexport interface ColorRampStop {\n /** Color expressed as a TSL node (typically `vec3(r,g,b)`). */\n color: TSLNode\n /** Position 0..1 along the ramp. */\n position: number\n}\n\n/**\n * Multi-stop color interpolation. Given a t in [0..1] and N color stops at\n * fixed positions, returns the smoothly-interpolated color.\n *\n * Falls back to the first/last stop's color outside the bracketing positions.\n */\nexport function colorRamp(t: TSLNode, stops: ColorRampStop[]): ShaderNodeObject<Node> {\n // TSLNode is wider than ShaderNodeObject<Node> in TSL's published types\n // (see CLAUDE.md gotcha #5). Wrapping with mix(node, node, 0) yields a\n // chainable ShaderNodeObject<Node> without a cast — the GPU shader compiler\n // folds the no-op interpolation away.\n const first = stops[0]\n\n if (first === undefined) return vec3(0, 0, 0)\n if (stops.length === 1) return mix(first.color, first.color, 0)\n\n // Build a chain of nested mixes, one per adjacent pair of stops.\n // For three stops at positions 0, 0.5, 1:\n // inner = mix(stop0, stop1, smoothstep(0, 0.5, t))\n // outer = mix(inner, stop2, smoothstep(0.5, 1, t))\n let result = mix(first.color, first.color, 0)\n\n for (let i = 1; i < stops.length; i += 1) {\n const prev = stops[i - 1]\n const next = stops[i]\n\n if (prev === undefined || next === undefined) continue\n const span = next.position - prev.position\n\n if (span <= 0) continue\n // Localize t into the [prev..next] range. `t` is TSLNode (the union),\n // so we use functional-form ops to avoid needing a chain-method receiver.\n const localT = clamp(div(sub(t, prev.position), span), 0, 1)\n\n result = mix(result, next.color, localT)\n }\n\n return result\n}\n","// packages/matter/src/primitives/noise.ts\nimport { mx_noise_float } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\n\n/**\n * 2D simplex noise sampled at a point. Returns a scalar TSL node in\n * approximately [-1, 1] (MaterialX's mx_noise_float is roughly that range).\n *\n * @param p — Vec2 TSL node (typically `uv()` or a scaled/offset uv).\n *\n * Built on top of three's `mx_noise_float`; we wrap it so consumers have a\n * stable import path through `@lovo/matter` and we can swap the\n * implementation if a different noise primitive proves better in practice.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) rather than the broader\n * `TSLNode` union, so callers can `.add(...)`/`.mul(...)` without casting.\n */\nexport function noise(p: TSLNode): ShaderNodeObject<Node> {\n return mx_noise_float(p)\n}\n","// packages/matter/src/primitives/fbm.ts\nimport { add, mul } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\nimport { noise } from './noise.js'\n\nexport interface FBMOptions {\n /** Number of octaves to sum. JS-side number — fixed at TSL build time, not a uniform. Default: 4. */\n octaves?: number\n /** Per-octave frequency multiplier. JS-side number. Default: 2. */\n lacunarity?: number\n /** Per-octave amplitude multiplier. JS-side number. Default: 0.5. */\n gain?: number\n}\n\n/**\n * Fractal Brownian Motion — sum of N octaves of 2D simplex noise.\n *\n * Each octave samples noise at a higher frequency (× `lacunarity`) and lower\n * amplitude (× `gain`) than the previous one, AND at a translated coordinate\n * so the octaves sample uncorrelated regions of noise space. Without the\n * per-octave translation, octaves at related frequencies tend to pile up\n * peaks and troughs at the same input coordinates, producing visibly muddy\n * \"spotty\" output. With it, the octaves look like independent noise patterns\n * layered together — Inigo Quilez's classic FBM technique.\n *\n * `octaves`, `lacunarity`, and `gain` are JavaScript numbers (NOT TSL\n * uniforms) because the loop must be unrolled at TSL-build time — TSL has\n * no dynamic-length loop primitive that maps cleanly to all backends.\n * Animatable parameters that *do* survive on the GPU are the input UV\n * (which the caller can scale/translate per frame) and `time`.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) for cast-free call sites.\n *\n * @param p — Vec2 or Vec3 TSL node (UV-space position).\n * @returns scalar TSL node, normalized to roughly [-1..1] regardless of\n * octave count thanks to the amplitude-sum division at the end.\n */\nexport function fbm(p: TSLNode, opts: FBMOptions = {}): ShaderNodeObject<Node> {\n const octaves = opts.octaves ?? 4\n const lacunarity = opts.lacunarity ?? 2\n const gain = opts.gain ?? 0.5\n\n let sum: ShaderNodeObject<Node> = noise(p)\n let amp = 1\n let freq = 1\n let total = amp\n\n for (let i = 1; i < octaves; i += 1) {\n freq *= lacunarity\n amp *= gain\n total += amp\n // Per-octave decorrelation: translate the sample point by a growing\n // offset so this octave reads from a totally different region of noise\n // space than the previous one. Magnitude 100 is well past simplex\n // noise's ~1-unit feature size, so adjacent octaves are fully\n // decorrelated. The scalar broadcasts across all components of `p`\n // (works for vec2 and vec3 inputs alike).\n //\n // Build the chain functionally from `p`: gotcha #12 doesn't apply\n // because `p` is uv-rooted, but the TSLNode union still requires\n // functional form on this hop.\n const pAtFreq = add(mul(p, freq), i * 100)\n const layer = noise(pAtFreq).mul(amp)\n\n sum = sum.add(layer)\n }\n\n // Normalize to approximate [-1..1] regardless of octave count / gain.\n return sum.div(total)\n}\n","// packages/matter/src/primitives/voronoi.ts\nimport { mx_worley_noise_float } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\n\n/**\n * 2D voronoi (Worley) noise — distance to the nearest jittered cell point,\n * normalized roughly to [0, 1]. Higher values = farther from any cell point\n * (cell interiors); lower values = near a cell boundary.\n *\n * Built on three's `mx_worley_noise_float`. Combine with `colorRamp` for\n * a multi-color cellular pattern; threshold via `step`/`smoothstep` for\n * hard cell shapes.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) for cast-free call sites.\n *\n * @param p — Vec2 TSL node, typically `uv() * scale`.\n */\nexport function voronoi(p: TSLNode): ShaderNodeObject<Node> {\n return mx_worley_noise_float(p)\n}\n","// packages/matter/src/primitives/quantize.ts\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Quantize a scalar TSL node to `steps` discrete levels.\n *\n * quantize(t, 4) → values in {0, 0.25, 0.5, 0.75, 1.0}\n *\n * `steps` is a JS-side number (loop-equivalent at TSL build time, baked in).\n * If you need an animatable step count, rebuild the TSL fragment.\n */\nexport function quantize(t: ShaderNodeObject<Node>, steps: number): ShaderNodeObject<Node> {\n if (steps <= 1) {\n // Edge case: single step → constant 0. Return as-is wrapped in mul(0).\n return t.mul(0)\n }\n const denom = steps - 1\n\n // floor(t * (steps-1) + 0.5) / (steps-1)\n // Using floor(x + 0.5) instead of round() for TSL portability.\n return t.mul(denom).add(0.5).floor().div(denom)\n}\n","import { length } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\n\n/**\n * Signed distance field for a circle centered at the origin.\n *\n * sdfCircle(p, r) = length(p) - r\n *\n * Negative inside the circle, zero on the boundary, positive outside.\n * Combine with `smoothstep(-edge, +edge, sdf)` to render a soft-edged disk.\n *\n * @param p — Vec2 TSL node (typically a UV-space offset from the center).\n * @param radius — JS-side scalar OR a scalar TSL node.\n */\nexport function sdfCircle(p: TSLNode, radius: TSLNode | number): ShaderNodeObject<Node> {\n return length(p).sub(radius)\n}\n","import { add } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\n\n/**\n * Naive vector addition: returns `p + by`.\n *\n * displace(p, by) = p + by\n *\n * Thin wrapper that names the spatial intent of shifting a sample point.\n *\n * **SDF caveat:** when using this to translate an SDF render, pass the\n * NEGATED translation — `sdfCircle(displace(p, v.mul(-1)), r)` renders the\n * disk at position `+v` because SDF translation evaluates as\n * `length(p - center) - r`. Adding `+v` to the sample point shifts the\n * rendered shape in the OPPOSITE direction.\n *\n * @param p — Vec2 TSL node (the position being displaced).\n * @param by — Vec2 TSL node (the displacement vector).\n */\nexport function displace(p: TSLNode, by: TSLNode): ShaderNodeObject<Node> {\n return add(p, by)\n}\n","import { length, sin, smoothstep, sub } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from './colorRamp.js'\nimport { time } from './time.js'\n\nexport interface CursorRippleOptions {\n /** Decay radius (UV space). Beyond this, the ripple is ~0. Default: 0.4. */\n reach?: number\n /** Wavelength controls the ripple spacing. Default: 30. Larger = wider rings. */\n frequency?: number\n /** Time multiplier on the wave phase. Default: 6. Larger = faster oscillation. */\n speed?: number\n /** Output amplitude. Default: 0.5. Final result is in roughly [-amplitude, +amplitude]. */\n amplitude?: number\n}\n\n/**\n * A radial ripple emanating from `center`. Returns a scalar TSL node in\n * roughly [-amplitude, +amplitude] that decays to ~0 outside `reach`.\n *\n * ripple = sin(d*frequency - time*speed) * amplitude * smoothstep(reach, 0, d)\n *\n * Compose into a wave field by adding it to the underlying base wave.\n *\n * Note: `frequency` / `speed` / `reach` / `amplitude` are JS-side numbers\n * (baked into the TSL fragment at material-build time). The animatable\n * cursor position is the only live uniform consumed.\n *\n * @param p — Vec2 TSL node (typically `uv()`).\n * @param center — Vec2 TSL node (cursor uniform, in UV space).\n */\nexport function cursorRipple(\n p: TSLNode,\n center: TSLNode,\n opts: CursorRippleOptions = {},\n): ShaderNodeObject<Node> {\n const reach = opts.reach ?? 0.4\n const frequency = opts.frequency ?? 30\n const speed = opts.speed ?? 6\n const amplitude = opts.amplitude ?? 0.5\n\n // d = length(p - center). Use functional `sub(p, center)` because both\n // are typed as the broad TSLNode union (no chain receiver). Per gotcha #12,\n // building from a raw `uniform()` receiver silently produces wrong GPU\n // values, so the functional form is also safer for `center` being a uniform.\n const d = length(sub(p, center))\n // `time` is the engine-gated TSL node (from primitives/time.ts);\n // chains rooted in `time` automatically respect `prefers-reduced-motion` and\n // the runtime override set via `setReducedMotionPolicy`.\n const wave = sin(d.mul(frequency).sub(time.mul(speed)))\n const decay = smoothstep(reach, 0, d)\n\n return wave.mul(amplitude).mul(decay)\n}\n","// Engine-gated `time` — equals the TSL built-in `time` multiplied by the\n// reduced-motion scale uniform. Components consuming `time` from `@lovo/matter`\n// automatically respect `prefers-reduced-motion` and the policy override set\n// via `setReducedMotionPolicy`.\n//\n// If you want raw uncapped time (e.g. for a debug overlay), import `time`\n// from `three/tsl` directly.\n\nimport { time as _builtinTime } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport { getReducedMotionTimeScale } from '../runtime/reducedMotion.js'\n\nexport const time: ShaderNodeObject<Node> = _builtinTime.mul(getReducedMotionTimeScale())\n","import { uniform } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nexport type ReducedMotionPolicy = 'auto' | 'off' | 'slow' | 'paused'\n\n/**\n * Public surface exposed to package consumers. `recompute` is intentionally\n * absent — it is engine-internal and should not be callable from outside.\n */\nexport interface ReducedMotionWatcher {\n /** Current time scale: 0, 0.3, or 1. */\n scale(): number\n /** Subscribe to scale changes. Returns unsubscribe. */\n subscribe(cb: (scale: number) => void): () => void\n /** Tear down media-query listener. */\n dispose(): void\n}\n\n/**\n * Engine-internal extension of the public watcher. Only `setReducedMotionPolicy`\n * calls `recompute`; it is never part of the consumer-visible type.\n */\ninterface InternalWatcher extends ReducedMotionWatcher {\n recompute(): void\n}\n\ninterface PolicyState {\n policy: ReducedMotionPolicy\n watchers: Set<InternalWatcher>\n}\n\nconst state: PolicyState = {\n policy: 'auto',\n watchers: new Set(),\n}\n\n/**\n * Override Matter's default behavior of honoring `prefers-reduced-motion`.\n * - 'auto' — follow the OS media query (default)\n * - 'off' — full speed regardless of OS setting\n * - 'slow' — 30% speed regardless of OS setting\n * - 'paused' — 0 (animation effectively frozen) regardless of OS setting\n */\nexport function setReducedMotionPolicy(policy: ReducedMotionPolicy): void {\n if (state.policy === policy) return\n state.policy = policy\n for (const w of state.watchers) w.recompute()\n}\n\nexport function getReducedMotionPolicy(): ReducedMotionPolicy {\n return state.policy\n}\n\nconst computeScale = (mqlMatches: boolean): number => {\n switch (state.policy) {\n case 'off':\n return 1\n case 'slow':\n return 0.3\n case 'paused':\n return 0\n case 'auto':\n return mqlMatches ? 0.3 : 1\n }\n}\n\n/**\n * Create a watcher that tracks `prefers-reduced-motion: reduce` and the\n * global Matter policy override. Strict-mode-safe — callers create+dispose\n * one per mount cycle.\n */\nexport function createReducedMotionWatcher(): ReducedMotionWatcher {\n // SSR safety: bail to the no-op watcher if matchMedia is missing.\n // SSR watcher: scale() respects policy override but does not emit\n // subscription events (the engine has no way to notify SSR-created\n // watchers because they are not added to state.watchers — but in\n // practice CLAUDE.md gotcha #10 requires `ssr: false` for any component\n // that touches the matter engine).\n if (typeof matchMedia !== 'function') {\n return {\n scale: () => computeScale(false),\n subscribe: (cb) => {\n // No-op: SSR watchers are not in state.watchers and will never\n // receive policy-change notifications.\n void cb\n\n return () => {\n // SSR no-op unsubscribe\n }\n },\n /** SSR watcher does not emit policy-change notifications. */\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const mql = matchMedia('(prefers-reduced-motion: reduce)')\n const subs = new Set<(s: number) => void>()\n let last = computeScale(mql.matches)\n\n const onChange = () => {\n const next = computeScale(mql.matches)\n\n if (next !== last) {\n last = next\n for (const cb of subs) cb(next)\n }\n }\n\n mql.addEventListener('change', onChange)\n\n const watcher: InternalWatcher = {\n scale: () => last,\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n recompute() {\n const next = computeScale(mql.matches)\n\n if (next !== last) {\n last = next\n for (const cb of subs) cb(next)\n }\n },\n dispose() {\n mql.removeEventListener('change', onChange)\n subs.clear()\n state.watchers.delete(watcher)\n },\n }\n\n state.watchers.add(watcher)\n\n return watcher\n}\n\nlet globalScaleUniform: ReturnType<typeof uniform<number>> | null = null\nlet globalWatcher: ReducedMotionWatcher | null = null\n\n/**\n * Returns the engine-shared TSL uniform that `time` is multiplied by. Lazily\n * initialized on first read; reused across all materials. Mutating `.value`\n * imperatively when policy changes is safe — TSL re-reads the uniform every\n * frame.\n */\nexport function getReducedMotionTimeScale(): ShaderNodeObject<Node> {\n if (globalScaleUniform === null) {\n globalWatcher = createReducedMotionWatcher()\n globalScaleUniform = uniform(globalWatcher.scale())\n globalWatcher.subscribe((s) => {\n if (globalScaleUniform === null) return\n globalScaleUniform.value = s\n })\n }\n\n // ShaderNodeObject<UniformNode<number>> isn't structurally assignable to\n // ShaderNodeObject<Node> (invariant generic methods); chains work the same\n // at runtime, so widen at the return boundary.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n return globalScaleUniform as unknown as ShaderNodeObject<Node>\n}\n\n// Keep a typed reference for tests that may want to re-init between tests.\nexport const __resetReducedMotionForTests = () => {\n globalWatcher?.dispose()\n globalWatcher = null\n globalScaleUniform = null\n}\n","import { fract, length, sin, vec2 } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Hash-based film grain — chaotic, uncorrelated per-pixel noise sampled\n * from `uvNode`. The output is *centered* around zero so it acts as a\n * brightness-preserving texture overlay (half the pixels brighten by up\n * to `intensity`, half darken, mean unchanged). ADD the result to a color.\n *\n * filmGrain(uv, k) → static grain\n * filmGrain(uv, k, time) → twinkling grain. Pass a quantized time node\n * (e.g. `time.mul(speed).mul(60).floor()`) so\n * the grain re-randomizes at a controllable\n * \"shutter rate\" instead of every frame.\n *\n * Recipe:\n *\n * base = vec2(uv·c1, uv·c2) + timeOffset\n * hash = fract(sin(base) * 43758.5453)\n * out = (length(hash) - 0.765) * intensity\n *\n * `c1 = (2127.1, 81.17)` and `c2 = (1269.5, 283.37)` are arbitrary\n * near-prime constants that produce visually-uncorrelated noise. `0.765`\n * is the empirical mean of `length(vec2(u, v))` for uniform u, v ∈ [0, 1),\n * computed once so we don't have to subtract it at runtime per pixel.\n *\n * For a film-stock look (darkens as grain rises — silver-emulsion\n * physics) subtract the result from the color instead of adding.\n *\n * @param uvNode vec2 TSL node, typically `uv()`.\n * @param intensity number or TSL node in [0, 1]; scales the grain.\n * @param timeOffset optional number or TSL node added to each sample\n * before hashing. `0` (default) → static grain.\n */\nexport function filmGrain(\n uvNode: ShaderNodeObject<Node>,\n intensity: ShaderNodeObject<Node> | number,\n timeOffset: ShaderNodeObject<Node> | number = 0,\n): ShaderNodeObject<Node> {\n const HASH_C1 = vec2(2127.1, 81.17)\n const HASH_C2 = vec2(1269.5, 283.37)\n const base = vec2(uvNode.dot(HASH_C1).add(timeOffset), uvNode.dot(HASH_C2).add(timeOffset))\n\n const hash = fract(sin(base).mul(43758.5453))\n\n return length(hash).sub(0.765).mul(intensity)\n}\n","export interface VisibilityWatcher {\n isVisible(): boolean\n /** Subscribe to changes. Receives the new visibility state. Returns unsubscribe. */\n subscribe(cb: (visible: boolean) => void): () => void\n dispose(): void\n}\n\n/**\n * Watch `document.visibilityState`. Strict-mode-safe — callers create+dispose\n * one per mount cycle.\n *\n * SSR: if `document` is unavailable, returns a no-op watcher whose\n * `isVisible()` always returns `true` and whose `subscribe` does nothing.\n */\nexport function createVisibilityWatcher(): VisibilityWatcher {\n if (typeof document === 'undefined') {\n return {\n isVisible: () => true,\n subscribe: () => () => {\n // SSR no-op unsubscribe\n },\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const subs = new Set<(v: boolean) => void>()\n const onChange = () => {\n const v = document.visibilityState === 'visible'\n\n for (const cb of subs) cb(v)\n }\n\n document.addEventListener('visibilitychange', onChange)\n\n return {\n isVisible: () => document.visibilityState === 'visible',\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n dispose() {\n document.removeEventListener('visibilitychange', onChange)\n subs.clear()\n },\n }\n}\n","export interface IntersectionWatcher {\n isInView(): boolean\n /** Subscribe to changes. Receives the new in-view state. Returns unsubscribe. */\n subscribe(cb: (inView: boolean) => void): () => void\n dispose(): void\n}\n\n/**\n * Watch a canvas's viewport intersection. Pauses tied to this watcher should\n * be resumed when the canvas is *any* fraction visible. Strict-mode-safe.\n *\n * SSR: if `IntersectionObserver` is unavailable, returns a no-op watcher whose\n * `isInView()` always returns `true` and whose `subscribe` does nothing.\n */\nexport function createIntersectionWatcher(canvas: HTMLCanvasElement): IntersectionWatcher {\n if (typeof IntersectionObserver === 'undefined') {\n return {\n isInView: () => true,\n subscribe: () => () => {\n // SSR no-op unsubscribe\n },\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const subs = new Set<(v: boolean) => void>()\n let inView = true\n const obs = new IntersectionObserver(\n (entries) => {\n const next = entries.some((e) => e.isIntersecting)\n\n if (next === inView) return\n inView = next\n for (const cb of subs) cb(inView)\n },\n { threshold: 0 },\n )\n\n obs.observe(canvas)\n\n return {\n isInView: () => inView,\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n dispose() {\n obs.disconnect()\n subs.clear()\n },\n }\n}\n","export interface SchedulerTick {\n /** Seconds since the previous tick. 0 on the first call. */\n delta: number\n /** Total seconds since the scheduler started its current run. */\n elapsed: number\n /** The raw `performance.now()` timestamp the rAF callback received. */\n now: number\n}\n\nexport type SchedulerClient = (tick: SchedulerTick) => void\n\n/**\n * Batches `requestAnimationFrame` calls across all clients registered with\n * a single scheduler. One scheduler is created per <ShaderScene>; clients\n * are typically a Three.js renderer's render call.\n */\nexport class FrameScheduler {\n private readonly clients = new Set<SchedulerClient>()\n private rafId: number | null = null\n private running = false\n private paused = false\n private idle = false\n private flushPending = false\n private startedAt = 0\n private lastTickAt = 0\n\n /** Activate the scheduler. The rAF loop starts on the first client added. */\n start(): void {\n this.running = true\n this.paused = false\n this.maybeQueue()\n }\n\n /** Halt the rAF loop entirely. Use dispose() for permanent teardown. */\n stop(): void {\n this.running = false\n this.cancel()\n }\n\n /** Temporarily skip ticks without losing client registrations. */\n pause(): void {\n this.paused = true\n }\n\n /** Resume after pause(). */\n resume(): void {\n this.paused = false\n if (this.running) this.maybeQueue()\n }\n\n /** Register a client to be called every frame. */\n add(client: SchedulerClient): void {\n this.clients.add(client)\n if (this.running) this.maybeQueue()\n }\n\n /** Unregister a client. */\n remove(client: SchedulerClient): void {\n this.clients.delete(client)\n }\n\n /** Permanent teardown: stop the loop and drop all clients. */\n dispose(): void {\n this.stop()\n this.clients.clear()\n }\n\n /**\n * Mark the scheduler idle. The next tick still fires (a final flush so\n * uniform changes that triggered the idle state are rendered), then the\n * rAF loop halts. Use `requestRender()` or `setIdle(false)` to wake.\n */\n setIdle(idle: boolean): void {\n if (this.idle === idle) return\n this.idle = idle\n if (idle) {\n this.flushPending = true\n this.maybeQueue()\n } else {\n this.flushPending = false\n this.maybeQueue()\n }\n }\n\n /** Force a single tick while idle. Useful for prop-change invalidation. */\n requestRender(): void {\n if (!this.idle) return\n this.flushPending = true\n this.maybeQueue()\n }\n\n private maybeQueue(): void {\n if (this.rafId !== null) return\n if (!this.running) return\n if (this.clients.size === 0) return\n if (this.idle && !this.flushPending) return\n this.rafId = requestAnimationFrame(this.frame)\n }\n\n private cancel(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n }\n\n private readonly frame = (now: number): void => {\n this.rafId = null\n if (!this.running || this.paused) return\n\n if (this.startedAt === 0) {\n this.startedAt = now\n this.lastTickAt = now\n }\n const delta = (now - this.lastTickAt) / 1000\n const elapsed = (now - this.startedAt) / 1000\n\n this.lastTickAt = now\n\n const tick: SchedulerTick = { delta, elapsed, now }\n\n for (const client of this.clients) {\n client(tick)\n }\n\n this.flushPending = false\n this.maybeQueue()\n }\n}\n"],"mappings":"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","names":["add","length","sub","length","sub","length","sin"]}
|
|
1
|
+
{"version":3,"sources":["../src/runtime/create-renderer/create-renderer.ts","../src/inputs/cursor-input/cursor-input.ts","../src/primitives/color-ramp/color-ramp.ts","../src/primitives/noise/noise.ts","../src/primitives/fbm/fbm.ts","../src/primitives/voronoi/voronoi.ts","../src/primitives/quantize/quantize.ts","../src/primitives/sdf-circle/sdf-circle.ts","../src/primitives/displace/displace.ts","../src/primitives/cursor-ripple/cursor-ripple.ts","../src/primitives/time/time.ts","../src/runtime/reduced-motion/reduced-motion.ts","../src/primitives/film-grain/film-grain.ts","../src/runtime/visibility/visibility.ts","../src/runtime/intersection/intersection.ts","../src/runtime/frame-scheduler/frame-scheduler.ts"],"sourcesContent":["import { Color } from 'three'\nimport { WebGPURenderer } from 'three/webgpu'\n\nexport type GpuBackend = 'webgpu' | 'webgl2'\n\nexport interface CreateRendererOptions {\n /** Anti-alias the framebuffer. Default: true. */\n antialias?: boolean\n /** Force WebGL2 even if WebGPU is available (useful for testing fallback). Default: false. */\n forceWebGL?: boolean\n /** Clear color (hex, CSS string, or THREE.Color). Default: transparent. */\n clearColor?: number | string | Color\n /** Clear alpha (0–1). Default: 0 (transparent). */\n clearAlpha?: number\n /** Cap on devicePixelRatio. Default: 2. Pass Infinity to disable. */\n maxDPR?: number\n}\n\nexport interface GpuRenderer {\n /** The underlying Three.js WebGPURenderer (which may be running on a WebGL2 backend). */\n three: WebGPURenderer\n /** Which backend the renderer initialized with. */\n backend: GpuBackend\n /** Tear down the renderer and release GPU resources. */\n dispose: () => void\n /** Resize the renderer to the canvas's current client dimensions. */\n resize: () => void\n}\n\n/**\n * Create a Matter renderer wrapping THREE.WebGPURenderer.\n *\n * Tries WebGPU first; falls back to WebGL2 automatically if WebGPU is\n * unavailable on the host. The returned object exposes the underlying\n * three renderer plus a small wrapper for resize and disposal.\n */\nexport async function createRenderer(\n canvas: HTMLCanvasElement,\n opts: CreateRendererOptions = {},\n): Promise<GpuRenderer> {\n const {\n antialias = true,\n forceWebGL = false,\n clearColor = 0x000000,\n clearAlpha = 0,\n maxDPR = 2,\n } = opts\n\n const three = new WebGPURenderer({\n canvas,\n antialias,\n forceWebGL,\n })\n\n await three.init()\n\n three.setPixelRatio(Math.min(window.devicePixelRatio, maxDPR))\n const resolvedClearColor = clearColor instanceof Color ? clearColor : new Color(clearColor)\n\n three.setClearColor(resolvedClearColor, clearAlpha)\n\n const resize = () => {\n const w = canvas.clientWidth\n const h = canvas.clientHeight\n\n if (canvas.width !== w * three.getPixelRatio() || canvas.height !== h * three.getPixelRatio()) {\n three.setSize(w, h, false)\n }\n }\n\n resize()\n\n // Detect backend after init. `isWebGLBackend` is an internal duck-type flag\n // not declared in three's public Backend type — probe via `in` rather than\n // a property access that would trip strict typing.\n const isWebGL = 'isWebGLBackend' in three.backend && three.backend.isWebGLBackend === true\n const backend: GpuBackend = forceWebGL || isWebGL ? 'webgl2' : 'webgpu'\n\n return {\n three,\n backend,\n dispose: () => three.dispose(),\n resize,\n }\n}\n","export type Vec2 = readonly [number, number]\n\nexport interface CursorInputOptions {\n /**\n * Smoothing factor: 0 = no smoothing (snap to target instantly).\n * 1 = max smoothing (essentially never reaches target).\n * Sensible default: 0.1.\n *\n * Implementation: per-frame, value moves toward target by `(1 - smoothing) * delta * 60`,\n * roughly meaning \"at smoothing=0.1, ~90% of the gap is closed in 1 second at 60fps.\"\n */\n smoothing?: number\n /** Starting position. Default: [0.5, 0.5] (center). */\n initial?: Vec2\n /** Listen on this target. Default: window. */\n target?: EventTarget\n /**\n * Element to normalize cursor coordinates against. Default: window viewport.\n *\n * When set, cursor x/y are in [0,1] across the element's bounding rect, with\n * extrapolation outside (negative when left/above, >1 when right/below). This\n * matches what shader UV space expects: a cursor at the canvas's top-left\n * corner reads as (0, 0); at bottom-right as (1, 1); regardless of where the\n * canvas sits in the viewport. Without this, components inside a partial-\n * viewport scene (e.g. a 70vh hero section) see a cursor offset that scales\n * with the canvas's vertical position on the page.\n */\n element?: {\n getBoundingClientRect(): { left: number; top: number; width: number; height: number }\n }\n}\n\ntype ChangeListener = (value: Vec2) => void\n\n/**\n * Smoothed pointer tracker emitting a normalized (0..1) Vec2 position.\n * Implements the AnimatableSignal protocol (`get()` + `on('change', cb)`)\n * so it composes with Motion's `useTransform` and similar tools.\n */\nexport class CursorInput {\n private value: [number, number]\n private target: [number, number]\n private targetDirty = false\n private readonly smoothing: number\n private readonly listeners = new Set<ChangeListener>()\n private readonly eventTarget: EventTarget\n private readonly element: CursorInputOptions['element']\n private readonly handleMouseMove: (e: Event) => void\n private disposed = false\n\n constructor(opts: CursorInputOptions = {}) {\n const { smoothing = 0.1, initial = [0.5, 0.5], target, element } = opts\n\n this.smoothing = clamp01(smoothing)\n this.value = [initial[0], initial[1]]\n this.target = [initial[0], initial[1]]\n this.eventTarget = target ?? (typeof window !== 'undefined' ? window : new EventTarget())\n this.element = element\n\n this.handleMouseMove = (e: Event) => {\n if (!(e instanceof MouseEvent)) return\n const me = e\n\n if (this.element) {\n // Normalize to 0..1 across the element's bounding rect. Reading the\n // rect on every move is fine — `getBoundingClientRect` is cheap and\n // mousemove is already throttled to ~60Hz by the browser. The benefit\n // is tracking the element's position even if it moved/scrolled since\n // the last frame.\n const r = this.element.getBoundingClientRect()\n const w = r.width || 1\n const h = r.height || 1\n\n this.target = [(me.clientX - r.left) / w, (me.clientY - r.top) / h]\n } else {\n // Fallback: viewport-normalized. Used when no element is supplied —\n // mostly the standalone-API case for users not consuming through\n // <ShaderScene>'s context.\n const w = (typeof window !== 'undefined' && window.innerWidth) || 1\n const h = (typeof window !== 'undefined' && window.innerHeight) || 1\n\n this.target = [me.clientX / w, me.clientY / h]\n }\n this.targetDirty = true\n }\n\n this.eventTarget.addEventListener('mousemove', this.handleMouseMove)\n }\n\n /** Current smoothed position. Implements AnimatableSignal protocol. */\n get(): Vec2 {\n return this.value\n }\n\n /** Subscribe to change events. Returns an unsubscribe function. */\n on(_event: 'change', cb: ChangeListener): () => void {\n this.listeners.add(cb)\n\n return () => this.listeners.delete(cb)\n }\n\n /**\n * Advance the smoothing one tick. Called by the host scheduler; not\n * typically called directly except in tests.\n */\n tick(delta: number): void {\n if (this.disposed) return\n const factor = this.smoothing === 0 ? 1 : 1 - Math.pow(this.smoothing, delta * 60)\n const prev0 = this.value[0]\n const prev1 = this.value[1]\n const next0 = lerp(prev0, this.target[0], factor)\n const next1 = lerp(prev1, this.target[1], factor)\n const moved = next0 !== prev0 || next1 !== prev1\n\n if (moved || this.targetDirty) {\n this.value = [next0, next1]\n this.targetDirty = false\n const snapshot: Vec2 = [next0, next1]\n\n for (const listener of this.listeners) listener(snapshot)\n }\n }\n\n /** Tear down listeners. */\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.eventTarget.removeEventListener('mousemove', this.handleMouseMove)\n this.listeners.clear()\n }\n}\n\nconst clamp01 = (n: number) => Math.max(0, Math.min(1, n))\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t\n","import type { ShaderNodeObject } from 'three/tsl'\nimport { mix, vec3 } from 'three/tsl'\nimport { clamp, div, sub } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Canonical TSL-node *input* shape used throughout `@lovo/matter`.\n *\n * Stays as the broad `Node | ShaderNodeObject<Node>` union so callers can\n * pass uniform-typed nodes (e.g. `ShaderNodeObject<UniformNode<Vector2>>`)\n * without casting at the call site — those are subtypes of `Node` but NOT\n * subtypes of `ShaderNodeObject<Node>` due to invariant generic parameters.\n *\n * Wrappers should return the narrower `ShaderNodeObject<Node>` so the\n * **output** is always chainable without casts.\n */\nexport type TSLNode = Node | ShaderNodeObject<Node>\n\nexport interface ColorRampStop {\n /** Color expressed as a TSL node (typically `vec3(r,g,b)`). */\n color: TSLNode\n /** Position 0..1 along the ramp. */\n position: number\n}\n\n/**\n * Multi-stop color interpolation. Given a t in [0..1] and N color stops at\n * fixed positions, returns the smoothly-interpolated color.\n *\n * Falls back to the first/last stop's color outside the bracketing positions.\n */\nexport function colorRamp(t: TSLNode, stops: ColorRampStop[]): ShaderNodeObject<Node> {\n // TSLNode is wider than ShaderNodeObject<Node> in TSL's published types\n // (see CLAUDE.md gotcha #5). Wrapping with mix(node, node, 0) yields a\n // chainable ShaderNodeObject<Node> without a cast — the GPU shader compiler\n // folds the no-op interpolation away.\n const first = stops[0]\n\n if (first === undefined) return vec3(0, 0, 0)\n if (stops.length === 1) return mix(first.color, first.color, 0)\n\n // Build a chain of nested mixes, one per adjacent pair of stops.\n // For three stops at positions 0, 0.5, 1:\n // inner = mix(stop0, stop1, smoothstep(0, 0.5, t))\n // outer = mix(inner, stop2, smoothstep(0.5, 1, t))\n let result = mix(first.color, first.color, 0)\n\n for (let i = 1; i < stops.length; i += 1) {\n const prev = stops[i - 1]\n const next = stops[i]\n\n if (prev === undefined || next === undefined) continue\n const span = next.position - prev.position\n\n if (span <= 0) continue\n // Localize t into the [prev..next] range. `t` is TSLNode (the union),\n // so we use functional-form ops to avoid needing a chain-method receiver.\n const localT = clamp(div(sub(t, prev.position), span), 0, 1)\n\n result = mix(result, next.color, localT)\n }\n\n return result\n}\n","// packages/matter/src/primitives/noise/noise.ts\nimport { mx_noise_float } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\n\n/**\n * 2D simplex noise sampled at a point. Returns a scalar TSL node in\n * approximately [-1, 1] (MaterialX's mx_noise_float is roughly that range).\n *\n * @param p — Vec2 TSL node (typically `uv()` or a scaled/offset uv).\n *\n * Built on top of three's `mx_noise_float`; we wrap it so consumers have a\n * stable import path through `@lovo/matter` and we can swap the\n * implementation if a different noise primitive proves better in practice.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) rather than the broader\n * `TSLNode` union, so callers can `.add(...)`/`.mul(...)` without casting.\n */\nexport function noise(p: TSLNode): ShaderNodeObject<Node> {\n return mx_noise_float(p)\n}\n","// packages/matter/src/primitives/fbm/fbm.ts\nimport { add, mul } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\nimport { noise } from '../noise/noise.js'\n\nexport interface FBMOptions {\n /** Number of octaves to sum. JS-side number — fixed at TSL build time, not a uniform. Default: 4. */\n octaves?: number\n /** Per-octave frequency multiplier. JS-side number. Default: 2. */\n lacunarity?: number\n /** Per-octave amplitude multiplier. JS-side number. Default: 0.5. */\n gain?: number\n}\n\n/**\n * Fractal Brownian Motion — sum of N octaves of 2D simplex noise.\n *\n * Each octave samples noise at a higher frequency (× `lacunarity`) and lower\n * amplitude (× `gain`) than the previous one, AND at a translated coordinate\n * so the octaves sample uncorrelated regions of noise space. Without the\n * per-octave translation, octaves at related frequencies tend to pile up\n * peaks and troughs at the same input coordinates, producing visibly muddy\n * \"spotty\" output. With it, the octaves look like independent noise patterns\n * layered together — Inigo Quilez's classic FBM technique.\n *\n * `octaves`, `lacunarity`, and `gain` are JavaScript numbers (NOT TSL\n * uniforms) because the loop must be unrolled at TSL-build time — TSL has\n * no dynamic-length loop primitive that maps cleanly to all backends.\n * Animatable parameters that *do* survive on the GPU are the input UV\n * (which the caller can scale/translate per frame) and `time`.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) for cast-free call sites.\n *\n * @param p — Vec2 or Vec3 TSL node (UV-space position).\n * @returns scalar TSL node, normalized to roughly [-1..1] regardless of\n * octave count thanks to the amplitude-sum division at the end.\n */\nexport function fbm(p: TSLNode, opts: FBMOptions = {}): ShaderNodeObject<Node> {\n const octaves = opts.octaves ?? 4\n const lacunarity = opts.lacunarity ?? 2\n const gain = opts.gain ?? 0.5\n\n let sum: ShaderNodeObject<Node> = noise(p)\n let amp = 1\n let freq = 1\n let total = amp\n\n for (let i = 1; i < octaves; i += 1) {\n freq *= lacunarity\n amp *= gain\n total += amp\n // Per-octave decorrelation: translate the sample point by a growing\n // offset so this octave reads from a totally different region of noise\n // space than the previous one. Magnitude 100 is well past simplex\n // noise's ~1-unit feature size, so adjacent octaves are fully\n // decorrelated. The scalar broadcasts across all components of `p`\n // (works for vec2 and vec3 inputs alike).\n //\n // Build the chain functionally from `p`: gotcha #12 doesn't apply\n // because `p` is uv-rooted, but the TSLNode union still requires\n // functional form on this hop.\n const pAtFreq = add(mul(p, freq), i * 100)\n const layer = noise(pAtFreq).mul(amp)\n\n sum = sum.add(layer)\n }\n\n // Normalize to approximate [-1..1] regardless of octave count / gain.\n return sum.div(total)\n}\n","// packages/matter/src/primitives/voronoi/voronoi.ts\nimport { mx_worley_noise_float } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\n\n/**\n * 2D voronoi (Worley) noise — distance to the nearest jittered cell point,\n * normalized roughly to [0, 1]. Higher values = farther from any cell point\n * (cell interiors); lower values = near a cell boundary.\n *\n * Built on three's `mx_worley_noise_float`. Combine with `colorRamp` for\n * a multi-color cellular pattern; threshold via `step`/`smoothstep` for\n * hard cell shapes.\n *\n * Returns `ShaderNodeObject<Node>` (chainable) for cast-free call sites.\n *\n * @param p — Vec2 TSL node, typically `uv() * scale`.\n */\nexport function voronoi(p: TSLNode): ShaderNodeObject<Node> {\n return mx_worley_noise_float(p)\n}\n","// packages/matter/src/primitives/quantize/quantize.ts\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Quantize a scalar TSL node to `steps` discrete levels.\n *\n * quantize(t, 4) → values in {0, 0.25, 0.5, 0.75, 1.0}\n *\n * `steps` is a JS-side number (loop-equivalent at TSL build time, baked in).\n * If you need an animatable step count, rebuild the TSL fragment.\n */\nexport function quantize(t: ShaderNodeObject<Node>, steps: number): ShaderNodeObject<Node> {\n if (steps <= 1) {\n // Edge case: single step → constant 0. Return as-is wrapped in mul(0).\n return t.mul(0)\n }\n const denom = steps - 1\n\n // floor(t * (steps-1) + 0.5) / (steps-1)\n // Using floor(x + 0.5) instead of round() for TSL portability.\n return t.mul(denom).add(0.5).floor().div(denom)\n}\n","import { length } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\n\n/**\n * Signed distance field for a circle centered at the origin.\n *\n * sdfCircle(p, r) = length(p) - r\n *\n * Negative inside the circle, zero on the boundary, positive outside.\n * Combine with `smoothstep(-edge, +edge, sdf)` to render a soft-edged disk.\n *\n * @param p — Vec2 TSL node (typically a UV-space offset from the center).\n * @param radius — JS-side scalar OR a scalar TSL node.\n */\nexport function sdfCircle(p: TSLNode, radius: TSLNode | number): ShaderNodeObject<Node> {\n return length(p).sub(radius)\n}\n","import { add } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\n\n/**\n * Naive vector addition: returns `p + by`.\n *\n * displace(p, by) = p + by\n *\n * Thin wrapper that names the spatial intent of shifting a sample point.\n *\n * **SDF caveat:** when using this to translate an SDF render, pass the\n * NEGATED translation — `sdfCircle(displace(p, v.mul(-1)), r)` renders the\n * disk at position `+v` because SDF translation evaluates as\n * `length(p - center) - r`. Adding `+v` to the sample point shifts the\n * rendered shape in the OPPOSITE direction.\n *\n * @param p — Vec2 TSL node (the position being displaced).\n * @param by — Vec2 TSL node (the displacement vector).\n */\nexport function displace(p: TSLNode, by: TSLNode): ShaderNodeObject<Node> {\n return add(p, by)\n}\n","import { length, sin, smoothstep, sub } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport type { TSLNode } from '../color-ramp/color-ramp.js'\nimport { time } from '../time/time.js'\n\nexport interface CursorRippleOptions {\n /** Decay radius (UV space). Beyond this, the ripple is ~0. Default: 0.4. */\n reach?: number\n /** Wavelength controls the ripple spacing. Default: 30. Larger = wider rings. */\n frequency?: number\n /** Time multiplier on the wave phase. Default: 6. Larger = faster oscillation. */\n speed?: number\n /** Output amplitude. Default: 0.5. Final result is in roughly [-amplitude, +amplitude]. */\n amplitude?: number\n}\n\n/**\n * A radial ripple emanating from `center`. Returns a scalar TSL node in\n * roughly [-amplitude, +amplitude] that decays to ~0 outside `reach`.\n *\n * ripple = sin(d*frequency - time*speed) * amplitude * smoothstep(reach, 0, d)\n *\n * Compose into a wave field by adding it to the underlying base wave.\n *\n * Note: `frequency` / `speed` / `reach` / `amplitude` are JS-side numbers\n * (baked into the TSL fragment at material-build time). The animatable\n * cursor position is the only live uniform consumed.\n *\n * @param p — Vec2 TSL node (typically `uv()`).\n * @param center — Vec2 TSL node (cursor uniform, in UV space).\n */\nexport function cursorRipple(\n p: TSLNode,\n center: TSLNode,\n opts: CursorRippleOptions = {},\n): ShaderNodeObject<Node> {\n const reach = opts.reach ?? 0.4\n const frequency = opts.frequency ?? 30\n const speed = opts.speed ?? 6\n const amplitude = opts.amplitude ?? 0.5\n\n // d = length(p - center). Use functional `sub(p, center)` because both\n // are typed as the broad TSLNode union (no chain receiver). Per gotcha #12,\n // building from a raw `uniform()` receiver silently produces wrong GPU\n // values, so the functional form is also safer for `center` being a uniform.\n const d = length(sub(p, center))\n // `time` is the engine-gated TSL node (from primitives/time/time.ts);\n // chains rooted in `time` automatically respect `prefers-reduced-motion` and\n // the runtime override set via `setReducedMotionPolicy`.\n const wave = sin(d.mul(frequency).sub(time.mul(speed)))\n const decay = smoothstep(reach, 0, d)\n\n return wave.mul(amplitude).mul(decay)\n}\n","// Engine-gated `time` — equals the TSL built-in `time` multiplied by the\n// reduced-motion scale uniform. Components consuming `time` from `@lovo/matter`\n// automatically respect `prefers-reduced-motion` and the policy override set\n// via `setReducedMotionPolicy`.\n//\n// If you want raw uncapped time (e.g. for a debug overlay), import `time`\n// from `three/tsl` directly.\n\nimport { time as _builtinTime } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nimport { getReducedMotionTimeScale } from '../../runtime/reduced-motion/reduced-motion.js'\n\nexport const time: ShaderNodeObject<Node> = _builtinTime.mul(getReducedMotionTimeScale())\n","import { uniform } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nexport type ReducedMotionPolicy = 'auto' | 'off' | 'slow' | 'paused'\n\n/**\n * Public surface exposed to package consumers. `recompute` is intentionally\n * absent — it is engine-internal and should not be callable from outside.\n */\nexport interface ReducedMotionWatcher {\n /** Current time scale: 0, 0.3, or 1. */\n scale(): number\n /** Subscribe to scale changes. Returns unsubscribe. */\n subscribe(cb: (scale: number) => void): () => void\n /** Tear down media-query listener. */\n dispose(): void\n}\n\n/**\n * Engine-internal extension of the public watcher. Only `setReducedMotionPolicy`\n * calls `recompute`; it is never part of the consumer-visible type.\n */\ninterface InternalWatcher extends ReducedMotionWatcher {\n recompute(): void\n}\n\ninterface PolicyState {\n policy: ReducedMotionPolicy\n watchers: Set<InternalWatcher>\n}\n\nconst state: PolicyState = {\n policy: 'auto',\n watchers: new Set(),\n}\n\n/**\n * Override Matter's default behavior of honoring `prefers-reduced-motion`.\n * - 'auto' — follow the OS media query (default)\n * - 'off' — full speed regardless of OS setting\n * - 'slow' — 30% speed regardless of OS setting\n * - 'paused' — 0 (animation effectively frozen) regardless of OS setting\n */\nexport function setReducedMotionPolicy(policy: ReducedMotionPolicy): void {\n if (state.policy === policy) return\n state.policy = policy\n for (const w of state.watchers) w.recompute()\n}\n\nexport function getReducedMotionPolicy(): ReducedMotionPolicy {\n return state.policy\n}\n\nconst computeScale = (mqlMatches: boolean): number => {\n switch (state.policy) {\n case 'off':\n return 1\n case 'slow':\n return 0.3\n case 'paused':\n return 0\n case 'auto':\n return mqlMatches ? 0.3 : 1\n }\n}\n\n/**\n * Create a watcher that tracks `prefers-reduced-motion: reduce` and the\n * global Matter policy override. Strict-mode-safe — callers create+dispose\n * one per mount cycle.\n */\nexport function createReducedMotionWatcher(): ReducedMotionWatcher {\n // SSR safety: bail to the no-op watcher if matchMedia is missing.\n // SSR watcher: scale() respects policy override but does not emit\n // subscription events (the engine has no way to notify SSR-created\n // watchers because they are not added to state.watchers — but in\n // practice CLAUDE.md gotcha #10 requires `ssr: false` for any component\n // that touches the matter engine).\n if (typeof matchMedia !== 'function') {\n return {\n scale: () => computeScale(false),\n subscribe: (cb) => {\n // No-op: SSR watchers are not in state.watchers and will never\n // receive policy-change notifications.\n void cb\n\n return () => {\n // SSR no-op unsubscribe\n }\n },\n /** SSR watcher does not emit policy-change notifications. */\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const mql = matchMedia('(prefers-reduced-motion: reduce)')\n const subs = new Set<(s: number) => void>()\n let last = computeScale(mql.matches)\n\n const onChange = () => {\n const next = computeScale(mql.matches)\n\n if (next !== last) {\n last = next\n for (const cb of subs) cb(next)\n }\n }\n\n mql.addEventListener('change', onChange)\n\n const watcher: InternalWatcher = {\n scale: () => last,\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n recompute() {\n const next = computeScale(mql.matches)\n\n if (next !== last) {\n last = next\n for (const cb of subs) cb(next)\n }\n },\n dispose() {\n mql.removeEventListener('change', onChange)\n subs.clear()\n state.watchers.delete(watcher)\n },\n }\n\n state.watchers.add(watcher)\n\n return watcher\n}\n\nlet globalScaleUniform: ReturnType<typeof uniform<number>> | null = null\nlet globalWatcher: ReducedMotionWatcher | null = null\n\n/**\n * Returns the engine-shared TSL uniform that `time` is multiplied by. Lazily\n * initialized on first read; reused across all materials. Mutating `.value`\n * imperatively when policy changes is safe — TSL re-reads the uniform every\n * frame.\n */\nexport function getReducedMotionTimeScale(): ShaderNodeObject<Node> {\n if (globalScaleUniform === null) {\n globalWatcher = createReducedMotionWatcher()\n globalScaleUniform = uniform(globalWatcher.scale())\n globalWatcher.subscribe((s) => {\n if (globalScaleUniform === null) return\n globalScaleUniform.value = s\n })\n }\n\n // ShaderNodeObject<UniformNode<number>> isn't structurally assignable to\n // ShaderNodeObject<Node> (invariant generic methods); chains work the same\n // at runtime, so widen at the return boundary.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n return globalScaleUniform as unknown as ShaderNodeObject<Node>\n}\n\n// Keep a typed reference for tests that may want to re-init between tests.\nexport const __resetReducedMotionForTests = () => {\n globalWatcher?.dispose()\n globalWatcher = null\n globalScaleUniform = null\n}\n","import { fract, length, sin, vec2 } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\n/**\n * Hash-based film grain — chaotic, uncorrelated per-pixel noise sampled\n * from `uvNode`. The output is *centered* around zero so it acts as a\n * brightness-preserving texture overlay (half the pixels brighten by up\n * to `intensity`, half darken, mean unchanged). ADD the result to a color.\n *\n * filmGrain(uv, k) → static grain\n * filmGrain(uv, k, time) → twinkling grain. Pass a quantized time node\n * (e.g. `time.mul(speed).mul(60).floor()`) so\n * the grain re-randomizes at a controllable\n * \"shutter rate\" instead of every frame.\n *\n * Recipe:\n *\n * base = vec2(uv·c1, uv·c2) + timeOffset\n * hash = fract(sin(base) * 43758.5453)\n * out = (length(hash) - 0.765) * intensity\n *\n * `c1 = (2127.1, 81.17)` and `c2 = (1269.5, 283.37)` are arbitrary\n * near-prime constants that produce visually-uncorrelated noise. `0.765`\n * is the empirical mean of `length(vec2(u, v))` for uniform u, v ∈ [0, 1),\n * computed once so we don't have to subtract it at runtime per pixel.\n *\n * For a film-stock look (darkens as grain rises — silver-emulsion\n * physics) subtract the result from the color instead of adding.\n *\n * @param uvNode vec2 TSL node, typically `uv()`.\n * @param intensity number or TSL node in [0, 1]; scales the grain.\n * @param timeOffset optional number or TSL node added to each sample\n * before hashing. `0` (default) → static grain.\n */\nexport function filmGrain(\n uvNode: ShaderNodeObject<Node>,\n intensity: ShaderNodeObject<Node> | number,\n timeOffset: ShaderNodeObject<Node> | number = 0,\n): ShaderNodeObject<Node> {\n const HASH_C1 = vec2(2127.1, 81.17)\n const HASH_C2 = vec2(1269.5, 283.37)\n const base = vec2(uvNode.dot(HASH_C1).add(timeOffset), uvNode.dot(HASH_C2).add(timeOffset))\n\n const hash = fract(sin(base).mul(43758.5453))\n\n return length(hash).sub(0.765).mul(intensity)\n}\n","export interface VisibilityWatcher {\n isVisible(): boolean\n /** Subscribe to changes. Receives the new visibility state. Returns unsubscribe. */\n subscribe(cb: (visible: boolean) => void): () => void\n dispose(): void\n}\n\n/**\n * Watch `document.visibilityState`. Strict-mode-safe — callers create+dispose\n * one per mount cycle.\n *\n * SSR: if `document` is unavailable, returns a no-op watcher whose\n * `isVisible()` always returns `true` and whose `subscribe` does nothing.\n */\nexport function createVisibilityWatcher(): VisibilityWatcher {\n if (typeof document === 'undefined') {\n return {\n isVisible: () => true,\n subscribe: () => () => {\n // SSR no-op unsubscribe\n },\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const subs = new Set<(v: boolean) => void>()\n const onChange = () => {\n const v = document.visibilityState === 'visible'\n\n for (const cb of subs) cb(v)\n }\n\n document.addEventListener('visibilitychange', onChange)\n\n return {\n isVisible: () => document.visibilityState === 'visible',\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n dispose() {\n document.removeEventListener('visibilitychange', onChange)\n subs.clear()\n },\n }\n}\n","export interface IntersectionWatcher {\n isInView(): boolean\n /** Subscribe to changes. Receives the new in-view state. Returns unsubscribe. */\n subscribe(cb: (inView: boolean) => void): () => void\n dispose(): void\n}\n\n/**\n * Watch a canvas's viewport intersection. Pauses tied to this watcher should\n * be resumed when the canvas is *any* fraction visible. Strict-mode-safe.\n *\n * SSR: if `IntersectionObserver` is unavailable, returns a no-op watcher whose\n * `isInView()` always returns `true` and whose `subscribe` does nothing.\n */\nexport function createIntersectionWatcher(canvas: HTMLCanvasElement): IntersectionWatcher {\n if (typeof IntersectionObserver === 'undefined') {\n return {\n isInView: () => true,\n subscribe: () => () => {\n // SSR no-op unsubscribe\n },\n dispose: () => {\n // SSR no-op dispose\n },\n }\n }\n\n const subs = new Set<(v: boolean) => void>()\n let inView = true\n const obs = new IntersectionObserver(\n (entries) => {\n const next = entries.some((e) => e.isIntersecting)\n\n if (next === inView) return\n inView = next\n for (const cb of subs) cb(inView)\n },\n { threshold: 0 },\n )\n\n obs.observe(canvas)\n\n return {\n isInView: () => inView,\n subscribe(cb) {\n subs.add(cb)\n\n return () => subs.delete(cb)\n },\n dispose() {\n obs.disconnect()\n subs.clear()\n },\n }\n}\n","export interface SchedulerTick {\n /** Seconds since the previous tick. 0 on the first call. */\n delta: number\n /** Total seconds since the scheduler started its current run. */\n elapsed: number\n /** The raw `performance.now()` timestamp the rAF callback received. */\n now: number\n}\n\nexport type SchedulerClient = (tick: SchedulerTick) => void\n\n/**\n * Batches `requestAnimationFrame` calls across all clients registered with\n * a single scheduler. One scheduler is created per <ShaderScene>; clients\n * are typically a Three.js renderer's render call.\n */\nexport class FrameScheduler {\n private readonly clients = new Set<SchedulerClient>()\n private rafId: number | null = null\n private running = false\n private paused = false\n private idle = false\n private flushPending = false\n private startedAt = 0\n private lastTickAt = 0\n\n /** Activate the scheduler. The rAF loop starts on the first client added. */\n start(): void {\n this.running = true\n this.paused = false\n this.maybeQueue()\n }\n\n /** Halt the rAF loop entirely. Use dispose() for permanent teardown. */\n stop(): void {\n this.running = false\n this.cancel()\n }\n\n /** Temporarily skip ticks without losing client registrations. */\n pause(): void {\n this.paused = true\n }\n\n /** Resume after pause(). */\n resume(): void {\n this.paused = false\n if (this.running) this.maybeQueue()\n }\n\n /** Register a client to be called every frame. */\n add(client: SchedulerClient): void {\n this.clients.add(client)\n if (this.running) this.maybeQueue()\n }\n\n /** Unregister a client. */\n remove(client: SchedulerClient): void {\n this.clients.delete(client)\n }\n\n /** Permanent teardown: stop the loop and drop all clients. */\n dispose(): void {\n this.stop()\n this.clients.clear()\n }\n\n /**\n * Mark the scheduler idle. The next tick still fires (a final flush so\n * uniform changes that triggered the idle state are rendered), then the\n * rAF loop halts. Use `requestRender()` or `setIdle(false)` to wake.\n */\n setIdle(idle: boolean): void {\n if (this.idle === idle) return\n this.idle = idle\n if (idle) {\n this.flushPending = true\n this.maybeQueue()\n } else {\n this.flushPending = false\n this.maybeQueue()\n }\n }\n\n /** Force a single tick while idle. Useful for prop-change invalidation. */\n requestRender(): void {\n if (!this.idle) return\n this.flushPending = true\n this.maybeQueue()\n }\n\n private maybeQueue(): void {\n if (this.rafId !== null) return\n if (!this.running) return\n if (this.clients.size === 0) return\n if (this.idle && !this.flushPending) return\n this.rafId = requestAnimationFrame(this.frame)\n }\n\n private cancel(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n }\n\n private readonly frame = (now: number): void => {\n this.rafId = null\n if (!this.running || this.paused) return\n\n if (this.startedAt === 0) {\n this.startedAt = now\n this.lastTickAt = now\n }\n const delta = (now - this.lastTickAt) / 1000\n const elapsed = (now - this.startedAt) / 1000\n\n this.lastTickAt = now\n\n const tick: SchedulerTick = { delta, elapsed, now }\n\n for (const client of this.clients) {\n client(tick)\n }\n\n this.flushPending = false\n this.maybeQueue()\n }\n}\n"],"mappings":";AAAA,SAAS,aAAa;AACtB,SAAS,sBAAsB;AAmC/B,eAAsB,eACpB,QACA,OAA8B,CAAC,GACT;AACtB,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,aAAa;AAAA,IACb,SAAS;AAAA,EACX,IAAI;AAEJ,QAAM,QAAQ,IAAI,eAAe;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,MAAM,KAAK;AAEjB,QAAM,cAAc,KAAK,IAAI,OAAO,kBAAkB,MAAM,CAAC;AAC7D,QAAM,qBAAqB,sBAAsB,QAAQ,aAAa,IAAI,MAAM,UAAU;AAE1F,QAAM,cAAc,oBAAoB,UAAU;AAElD,QAAM,SAAS,MAAM;AACnB,UAAM,IAAI,OAAO;AACjB,UAAM,IAAI,OAAO;AAEjB,QAAI,OAAO,UAAU,IAAI,MAAM,cAAc,KAAK,OAAO,WAAW,IAAI,MAAM,cAAc,GAAG;AAC7F,YAAM,QAAQ,GAAG,GAAG,KAAK;AAAA,IAC3B;AAAA,EACF;AAEA,SAAO;AAKP,QAAM,UAAU,oBAAoB,MAAM,WAAW,MAAM,QAAQ,mBAAmB;AACtF,QAAM,UAAsB,cAAc,UAAU,WAAW;AAE/D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,MAAM,MAAM,QAAQ;AAAA,IAC7B;AAAA,EACF;AACF;;;AC7CO,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACL;AAAA,EACA,YAAY,oBAAI,IAAoB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACT,WAAW;AAAA,EAEnB,YAAY,OAA2B,CAAC,GAAG;AACzC,UAAM,EAAE,YAAY,KAAK,UAAU,CAAC,KAAK,GAAG,GAAG,QAAQ,QAAQ,IAAI;AAEnE,SAAK,YAAY,QAAQ,SAAS;AAClC,SAAK,QAAQ,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC;AACpC,SAAK,SAAS,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC;AACrC,SAAK,cAAc,WAAW,OAAO,WAAW,cAAc,SAAS,IAAI,YAAY;AACvF,SAAK,UAAU;AAEf,SAAK,kBAAkB,CAAC,MAAa;AACnC,UAAI,EAAE,aAAa,YAAa;AAChC,YAAM,KAAK;AAEX,UAAI,KAAK,SAAS;AAMhB,cAAM,IAAI,KAAK,QAAQ,sBAAsB;AAC7C,cAAM,IAAI,EAAE,SAAS;AACrB,cAAM,IAAI,EAAE,UAAU;AAEtB,aAAK,SAAS,EAAE,GAAG,UAAU,EAAE,QAAQ,IAAI,GAAG,UAAU,EAAE,OAAO,CAAC;AAAA,MACpE,OAAO;AAIL,cAAM,IAAK,OAAO,WAAW,eAAe,OAAO,cAAe;AAClE,cAAM,IAAK,OAAO,WAAW,eAAe,OAAO,eAAgB;AAEnE,aAAK,SAAS,CAAC,GAAG,UAAU,GAAG,GAAG,UAAU,CAAC;AAAA,MAC/C;AACA,WAAK,cAAc;AAAA,IACrB;AAEA,SAAK,YAAY,iBAAiB,aAAa,KAAK,eAAe;AAAA,EACrE;AAAA;AAAA,EAGA,MAAY;AACV,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,GAAG,QAAkB,IAAgC;AACnD,SAAK,UAAU,IAAI,EAAE;AAErB,WAAO,MAAM,KAAK,UAAU,OAAO,EAAE;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,KAAK,OAAqB;AACxB,QAAI,KAAK,SAAU;AACnB,UAAM,SAAS,KAAK,cAAc,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,WAAW,QAAQ,EAAE;AACjF,UAAM,QAAQ,KAAK,MAAM,CAAC;AAC1B,UAAM,QAAQ,KAAK,MAAM,CAAC;AAC1B,UAAM,QAAQ,KAAK,OAAO,KAAK,OAAO,CAAC,GAAG,MAAM;AAChD,UAAM,QAAQ,KAAK,OAAO,KAAK,OAAO,CAAC,GAAG,MAAM;AAChD,UAAM,QAAQ,UAAU,SAAS,UAAU;AAE3C,QAAI,SAAS,KAAK,aAAa;AAC7B,WAAK,QAAQ,CAAC,OAAO,KAAK;AAC1B,WAAK,cAAc;AACnB,YAAM,WAAiB,CAAC,OAAO,KAAK;AAEpC,iBAAW,YAAY,KAAK,UAAW,UAAS,QAAQ;AAAA,IAC1D;AAAA,EACF;AAAA;AAAA,EAGA,UAAgB;AACd,QAAI,KAAK,SAAU;AACnB,SAAK,WAAW;AAChB,SAAK,YAAY,oBAAoB,aAAa,KAAK,eAAe;AACtE,SAAK,UAAU,MAAM;AAAA,EACvB;AACF;AAEA,IAAM,UAAU,CAAC,MAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AACzD,IAAM,OAAO,CAAC,GAAW,GAAW,MAAc,KAAK,IAAI,KAAK;;;ACpIhE,SAAS,KAAK,YAAY;AAC1B,SAAS,OAAO,KAAK,WAAW;AA6BzB,SAAS,UAAU,GAAY,OAAgD;AAKpF,QAAM,QAAQ,MAAM,CAAC;AAErB,MAAI,UAAU,OAAW,QAAO,KAAK,GAAG,GAAG,CAAC;AAC5C,MAAI,MAAM,WAAW,EAAG,QAAO,IAAI,MAAM,OAAO,MAAM,OAAO,CAAC;AAM9D,MAAI,SAAS,IAAI,MAAM,OAAO,MAAM,OAAO,CAAC;AAE5C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,UAAM,OAAO,MAAM,IAAI,CAAC;AACxB,UAAM,OAAO,MAAM,CAAC;AAEpB,QAAI,SAAS,UAAa,SAAS,OAAW;AAC9C,UAAM,OAAO,KAAK,WAAW,KAAK;AAElC,QAAI,QAAQ,EAAG;AAGf,UAAM,SAAS,MAAM,IAAI,IAAI,GAAG,KAAK,QAAQ,GAAG,IAAI,GAAG,GAAG,CAAC;AAE3D,aAAS,IAAI,QAAQ,KAAK,OAAO,MAAM;AAAA,EACzC;AAEA,SAAO;AACT;;;AC9DA,SAAS,sBAAsB;AAmBxB,SAAS,MAAM,GAAoC;AACxD,SAAO,eAAe,CAAC;AACzB;;;ACrBA,SAAS,KAAK,WAAW;AAuClB,SAAS,IAAI,GAAY,OAAmB,CAAC,GAA2B;AAC7E,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,OAAO,KAAK,QAAQ;AAE1B,MAAI,MAA8B,MAAM,CAAC;AACzC,MAAI,MAAM;AACV,MAAI,OAAO;AACX,MAAI,QAAQ;AAEZ,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK,GAAG;AACnC,YAAQ;AACR,WAAO;AACP,aAAS;AAWT,UAAM,UAAU,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG;AACzC,UAAM,QAAQ,MAAM,OAAO,EAAE,IAAI,GAAG;AAEpC,UAAM,IAAI,IAAI,KAAK;AAAA,EACrB;AAGA,SAAO,IAAI,IAAI,KAAK;AACtB;;;ACvEA,SAAS,6BAA6B;AAmB/B,SAAS,QAAQ,GAAoC;AAC1D,SAAO,sBAAsB,CAAC;AAChC;;;ACVO,SAAS,SAAS,GAA2B,OAAuC;AACzF,MAAI,SAAS,GAAG;AAEd,WAAO,EAAE,IAAI,CAAC;AAAA,EAChB;AACA,QAAM,QAAQ,QAAQ;AAItB,SAAO,EAAE,IAAI,KAAK,EAAE,IAAI,GAAG,EAAE,MAAM,EAAE,IAAI,KAAK;AAChD;;;ACtBA,SAAS,cAAc;AAiBhB,SAAS,UAAU,GAAY,QAAkD;AACtF,SAAO,OAAO,CAAC,EAAE,IAAI,MAAM;AAC7B;;;ACnBA,SAAS,OAAAA,YAAW;AAsBb,SAAS,SAAS,GAAY,IAAqC;AACxE,SAAOA,KAAI,GAAG,EAAE;AAClB;;;ACxBA,SAAS,UAAAC,SAAQ,KAAK,YAAY,OAAAC,YAAW;;;ACQ7C,SAAS,QAAQ,oBAAoB;;;ACRrC,SAAS,eAAe;AAgCxB,IAAM,QAAqB;AAAA,EACzB,QAAQ;AAAA,EACR,UAAU,oBAAI,IAAI;AACpB;AASO,SAAS,uBAAuB,QAAmC;AACxE,MAAI,MAAM,WAAW,OAAQ;AAC7B,QAAM,SAAS;AACf,aAAW,KAAK,MAAM,SAAU,GAAE,UAAU;AAC9C;AAEO,SAAS,yBAA8C;AAC5D,SAAO,MAAM;AACf;AAEA,IAAM,eAAe,CAAC,eAAgC;AACpD,UAAQ,MAAM,QAAQ;AAAA,IACpB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,aAAa,MAAM;AAAA,EAC9B;AACF;AAOO,SAAS,6BAAmD;AAOjE,MAAI,OAAO,eAAe,YAAY;AACpC,WAAO;AAAA,MACL,OAAO,MAAM,aAAa,KAAK;AAAA,MAC/B,WAAW,CAAC,OAAO;AAGjB,aAAK;AAEL,eAAO,MAAM;AAAA,QAEb;AAAA,MACF;AAAA;AAAA,MAEA,SAAS,MAAM;AAAA,MAEf;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,WAAW,kCAAkC;AACzD,QAAM,OAAO,oBAAI,IAAyB;AAC1C,MAAI,OAAO,aAAa,IAAI,OAAO;AAEnC,QAAM,WAAW,MAAM;AACrB,UAAM,OAAO,aAAa,IAAI,OAAO;AAErC,QAAI,SAAS,MAAM;AACjB,aAAO;AACP,iBAAW,MAAM,KAAM,IAAG,IAAI;AAAA,IAChC;AAAA,EACF;AAEA,MAAI,iBAAiB,UAAU,QAAQ;AAEvC,QAAM,UAA2B;AAAA,IAC/B,OAAO,MAAM;AAAA,IACb,UAAU,IAAI;AACZ,WAAK,IAAI,EAAE;AAEX,aAAO,MAAM,KAAK,OAAO,EAAE;AAAA,IAC7B;AAAA,IACA,YAAY;AACV,YAAM,OAAO,aAAa,IAAI,OAAO;AAErC,UAAI,SAAS,MAAM;AACjB,eAAO;AACP,mBAAW,MAAM,KAAM,IAAG,IAAI;AAAA,MAChC;AAAA,IACF;AAAA,IACA,UAAU;AACR,UAAI,oBAAoB,UAAU,QAAQ;AAC1C,WAAK,MAAM;AACX,YAAM,SAAS,OAAO,OAAO;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,OAAO;AAE1B,SAAO;AACT;AAEA,IAAI,qBAAgE;AACpE,IAAI,gBAA6C;AAQ1C,SAAS,4BAAoD;AAClE,MAAI,uBAAuB,MAAM;AAC/B,oBAAgB,2BAA2B;AAC3C,yBAAqB,QAAQ,cAAc,MAAM,CAAC;AAClD,kBAAc,UAAU,CAAC,MAAM;AAC7B,UAAI,uBAAuB,KAAM;AACjC,yBAAmB,QAAQ;AAAA,IAC7B,CAAC;AAAA,EACH;AAMA,SAAO;AACT;;;ADtJO,IAAM,OAA+B,aAAa,IAAI,0BAA0B,CAAC;;;ADmBjF,SAAS,aACd,GACA,QACA,OAA4B,CAAC,GACL;AACxB,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,YAAY,KAAK,aAAa;AAMpC,QAAM,IAAIC,QAAOC,KAAI,GAAG,MAAM,CAAC;AAI/B,QAAM,OAAO,IAAI,EAAE,IAAI,SAAS,EAAE,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC;AACtD,QAAM,QAAQ,WAAW,OAAO,GAAG,CAAC;AAEpC,SAAO,KAAK,IAAI,SAAS,EAAE,IAAI,KAAK;AACtC;;;AGvDA,SAAS,OAAO,UAAAC,SAAQ,OAAAC,MAAK,YAAY;AAmClC,SAAS,UACd,QACA,WACA,aAA8C,GACtB;AACxB,QAAM,UAAU,KAAK,QAAQ,KAAK;AAClC,QAAM,UAAU,KAAK,QAAQ,MAAM;AACnC,QAAM,OAAO,KAAK,OAAO,IAAI,OAAO,EAAE,IAAI,UAAU,GAAG,OAAO,IAAI,OAAO,EAAE,IAAI,UAAU,CAAC;AAE1F,QAAM,OAAO,MAAMA,KAAI,IAAI,EAAE,IAAI,UAAU,CAAC;AAE5C,SAAOD,QAAO,IAAI,EAAE,IAAI,KAAK,EAAE,IAAI,SAAS;AAC9C;;;ACjCO,SAAS,0BAA6C;AAC3D,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,MACL,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM,MAAM;AAAA,MAEvB;AAAA,MACA,SAAS,MAAM;AAAA,MAEf;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,oBAAI,IAA0B;AAC3C,QAAM,WAAW,MAAM;AACrB,UAAM,IAAI,SAAS,oBAAoB;AAEvC,eAAW,MAAM,KAAM,IAAG,CAAC;AAAA,EAC7B;AAEA,WAAS,iBAAiB,oBAAoB,QAAQ;AAEtD,SAAO;AAAA,IACL,WAAW,MAAM,SAAS,oBAAoB;AAAA,IAC9C,UAAU,IAAI;AACZ,WAAK,IAAI,EAAE;AAEX,aAAO,MAAM,KAAK,OAAO,EAAE;AAAA,IAC7B;AAAA,IACA,UAAU;AACR,eAAS,oBAAoB,oBAAoB,QAAQ;AACzD,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AACF;;;AClCO,SAAS,0BAA0B,QAAgD;AACxF,MAAI,OAAO,yBAAyB,aAAa;AAC/C,WAAO;AAAA,MACL,UAAU,MAAM;AAAA,MAChB,WAAW,MAAM,MAAM;AAAA,MAEvB;AAAA,MACA,SAAS,MAAM;AAAA,MAEf;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,oBAAI,IAA0B;AAC3C,MAAI,SAAS;AACb,QAAM,MAAM,IAAI;AAAA,IACd,CAAC,YAAY;AACX,YAAM,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,cAAc;AAEjD,UAAI,SAAS,OAAQ;AACrB,eAAS;AACT,iBAAW,MAAM,KAAM,IAAG,MAAM;AAAA,IAClC;AAAA,IACA,EAAE,WAAW,EAAE;AAAA,EACjB;AAEA,MAAI,QAAQ,MAAM;AAElB,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,UAAU,IAAI;AACZ,WAAK,IAAI,EAAE;AAEX,aAAO,MAAM,KAAK,OAAO,EAAE;AAAA,IAC7B;AAAA,IACA,UAAU;AACR,UAAI,WAAW;AACf,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AACF;;;ACtCO,IAAM,iBAAN,MAAqB;AAAA,EACT,UAAU,oBAAI,IAAqB;AAAA,EAC5C,QAAuB;AAAA,EACvB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,OAAO;AAAA,EACP,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,aAAa;AAAA;AAAA,EAGrB,QAAc;AACZ,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,OAAa;AACX,SAAK,UAAU;AACf,SAAK,OAAO;AAAA,EACd;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,SAAe;AACb,SAAK,SAAS;AACd,QAAI,KAAK,QAAS,MAAK,WAAW;AAAA,EACpC;AAAA;AAAA,EAGA,IAAI,QAA+B;AACjC,SAAK,QAAQ,IAAI,MAAM;AACvB,QAAI,KAAK,QAAS,MAAK,WAAW;AAAA,EACpC;AAAA;AAAA,EAGA,OAAO,QAA+B;AACpC,SAAK,QAAQ,OAAO,MAAM;AAAA,EAC5B;AAAA;AAAA,EAGA,UAAgB;AACd,SAAK,KAAK;AACV,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,MAAqB;AAC3B,QAAI,KAAK,SAAS,KAAM;AACxB,SAAK,OAAO;AACZ,QAAI,MAAM;AACR,WAAK,eAAe;AACpB,WAAK,WAAW;AAAA,IAClB,OAAO;AACL,WAAK,eAAe;AACpB,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA;AAAA,EAGA,gBAAsB;AACpB,QAAI,CAAC,KAAK,KAAM;AAChB,SAAK,eAAe;AACpB,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,aAAmB;AACzB,QAAI,KAAK,UAAU,KAAM;AACzB,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI,KAAK,QAAQ,SAAS,EAAG;AAC7B,QAAI,KAAK,QAAQ,CAAC,KAAK,aAAc;AACrC,SAAK,QAAQ,sBAAsB,KAAK,KAAK;AAAA,EAC/C;AAAA,EAEQ,SAAe;AACrB,QAAI,KAAK,UAAU,MAAM;AACvB,2BAAqB,KAAK,KAAK;AAC/B,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA,EAEiB,QAAQ,CAAC,QAAsB;AAC9C,SAAK,QAAQ;AACb,QAAI,CAAC,KAAK,WAAW,KAAK,OAAQ;AAElC,QAAI,KAAK,cAAc,GAAG;AACxB,WAAK,YAAY;AACjB,WAAK,aAAa;AAAA,IACpB;AACA,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,WAAW,MAAM,KAAK,aAAa;AAEzC,SAAK,aAAa;AAElB,UAAM,OAAsB,EAAE,OAAO,SAAS,IAAI;AAElD,eAAW,UAAU,KAAK,SAAS;AACjC,aAAO,IAAI;AAAA,IACb;AAEA,SAAK,eAAe;AACpB,SAAK,WAAW;AAAA,EAClB;AACF;","names":["add","length","sub","length","sub","length","sin"]}
|