@lovo/matter-react 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,21 +1,92 @@
1
- // src/MatterScene.tsx
2
- import { useEffect, useRef, useState } from "react";
3
- import { Scene, OrthographicCamera } from "three";
4
- import { PostProcessing } from "three/webgpu";
5
- import { pass } from "three/tsl";
1
+ // src/components/fallback-boundary/fallback-boundary.tsx
2
+ import { useEffect, useState } from "react";
3
+ import { Fragment, jsx } from "react/jsx-runtime";
4
+ function FallbackBoundary({ fallback, children }) {
5
+ const [mounted, setMounted] = useState(false);
6
+ useEffect(() => {
7
+ setMounted(true);
8
+ }, []);
9
+ return /* @__PURE__ */ jsx(Fragment, { children: mounted ? children : fallback ?? null });
10
+ }
11
+
12
+ // src/components/shader-monitor/shader-monitor.tsx
13
+ import { useContext, useEffect as useEffect2, useRef, useState as useState2 } from "react";
14
+
15
+ // src/context/shader-context.ts
16
+ import { createContext } from "react";
17
+ var ShaderContext = createContext(null);
18
+
19
+ // src/components/shader-monitor/shader-monitor.tsx
20
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
21
+ var anchorStyle = {
22
+ "top-left": { top: 8, left: 8 },
23
+ "top-right": { top: 8, right: 8 },
24
+ "bottom-left": { bottom: 8, left: 8 },
25
+ "bottom-right": { bottom: 8, right: 8 }
26
+ };
27
+ var baseStyle = {
28
+ position: "absolute",
29
+ zIndex: 10,
30
+ padding: "6px 8px",
31
+ borderRadius: 6,
32
+ background: "rgba(0, 0, 0, 0.6)",
33
+ color: "#fff",
34
+ font: "11px ui-monospace, monospace",
35
+ lineHeight: 1.4,
36
+ pointerEvents: "none",
37
+ whiteSpace: "pre"
38
+ };
39
+ function ShaderMonitor({ anchor = "top-right" }) {
40
+ const ctx = useContext(ShaderContext);
41
+ const [stats, setStats] = useState2({ fps: 0, ticks: 0, frames: 0 });
42
+ const ticksRef = useRef(0);
43
+ const fpsAccumRef = useRef({ frames: 0, lastSampleAt: 0, fps: 0 });
44
+ useEffect2(() => {
45
+ if (!ctx) return;
46
+ const client = (tick) => {
47
+ ticksRef.current += 1;
48
+ const acc = fpsAccumRef.current;
49
+ acc.frames += 1;
50
+ if (acc.lastSampleAt === 0) acc.lastSampleAt = tick.now;
51
+ const dt = tick.now - acc.lastSampleAt;
52
+ if (dt >= 500) {
53
+ acc.fps = Math.round(acc.frames * 1e3 / dt);
54
+ acc.frames = 0;
55
+ acc.lastSampleAt = tick.now;
56
+ }
57
+ setStats({ fps: acc.fps, ticks: ticksRef.current, frames: acc.frames });
58
+ };
59
+ ctx.scheduler.add(client);
60
+ return () => ctx.scheduler.remove(client);
61
+ }, [ctx]);
62
+ if (!ctx) {
63
+ return /* @__PURE__ */ jsx2("div", { "data-testid": "matter-monitor", style: { ...baseStyle, ...anchorStyle[anchor] }, children: "no scene" });
64
+ }
65
+ return /* @__PURE__ */ jsxs("div", { "data-testid": "matter-monitor", style: { ...baseStyle, ...anchorStyle[anchor] }, children: [
66
+ /* @__PURE__ */ jsxs("span", { "data-testid": "matter-monitor-fps", children: [
67
+ "fps: ",
68
+ stats.fps || "\u2014"
69
+ ] }),
70
+ "\n",
71
+ /* @__PURE__ */ jsxs("span", { "data-testid": "matter-monitor-ticks", children: [
72
+ "ticks: ",
73
+ stats.ticks
74
+ ] })
75
+ ] });
76
+ }
77
+
78
+ // src/components/shader-scene/shader-scene.tsx
6
79
  import {
80
+ createIntersectionWatcher,
7
81
  createRenderer,
8
- MatterScheduler,
9
82
  createVisibilityWatcher,
10
- createIntersectionWatcher
83
+ FrameScheduler
11
84
  } from "@lovo/matter";
12
-
13
- // src/matter-context.ts
14
- import { createContext } from "react";
15
- var MatterContext = createContext(null);
16
-
17
- // src/MatterScene.tsx
18
- import { jsx, jsxs } from "react/jsx-runtime";
85
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
86
+ import { OrthographicCamera, Scene } from "three";
87
+ import { pass } from "three/tsl";
88
+ import { PostProcessing } from "three/webgpu";
89
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
19
90
  var defaultStyle = {
20
91
  position: "absolute",
21
92
  inset: 0,
@@ -23,12 +94,12 @@ var defaultStyle = {
23
94
  width: "100%",
24
95
  height: "100%"
25
96
  };
26
- function MatterScene(props) {
97
+ function ShaderScene(props) {
27
98
  const { children, fallback, className, style, maxDPR } = props;
28
- const canvasRef = useRef(null);
29
- const [ctx, setCtx] = useState(null);
30
- const [error, setError] = useState(null);
31
- useEffect(() => {
99
+ const canvasRef = useRef2(null);
100
+ const [ctx, setCtx] = useState3(null);
101
+ const [error, setError] = useState3(null);
102
+ useEffect3(() => {
32
103
  const canvas = canvasRef.current;
33
104
  if (!canvas) return;
34
105
  let cancelled = false;
@@ -44,14 +115,14 @@ function MatterScene(props) {
44
115
  const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
45
116
  camera.position.z = 1;
46
117
  const postProcessing = new PostProcessing(renderer.three);
47
- const scheduler = new MatterScheduler();
118
+ const scheduler = new FrameScheduler();
48
119
  const overlays = /* @__PURE__ */ new Map();
49
120
  const basePass = pass(scene, camera);
50
121
  const rebuildOutputNode = () => {
51
- const transforms = Array.from(overlays.values());
52
- postProcessing.outputNode = transforms.reduce(
122
+ const seed = basePass;
123
+ postProcessing.outputNode = Array.from(overlays.values()).reduce(
53
124
  (node, transform) => transform(node),
54
- basePass
125
+ seed
55
126
  );
56
127
  postProcessing.needsUpdate = true;
57
128
  };
@@ -92,7 +163,7 @@ function MatterScene(props) {
92
163
  } catch (err) {
93
164
  if (cancelled) return;
94
165
  const e = err instanceof Error ? err : new Error(String(err));
95
- console.error("[MatterScene] renderer init failed:", e);
166
+ console.error("[ShaderScene] renderer init failed:", e);
96
167
  setError(e);
97
168
  }
98
169
  };
@@ -104,9 +175,9 @@ function MatterScene(props) {
104
175
  setCtx(null);
105
176
  };
106
177
  }, [maxDPR]);
107
- return /* @__PURE__ */ jsxs("div", { className, style: { ...defaultStyle, ...style }, children: [
108
- /* @__PURE__ */ jsx("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block" } }),
109
- error ? /* @__PURE__ */ jsxs(
178
+ let content;
179
+ if (error) {
180
+ content = /* @__PURE__ */ jsxs2(
110
181
  "div",
111
182
  {
112
183
  style: {
@@ -123,47 +194,67 @@ function MatterScene(props) {
123
194
  textAlign: "center"
124
195
  },
125
196
  children: [
126
- "MatterScene init failed:",
197
+ "ShaderScene init failed:",
127
198
  "\n",
128
199
  error.message
129
200
  ]
130
201
  }
131
- ) : ctx ? /* @__PURE__ */ jsx(MatterContext.Provider, { value: ctx, children }) : fallback ?? null
202
+ );
203
+ } else if (ctx) {
204
+ content = /* @__PURE__ */ jsx3(ShaderContext.Provider, { value: ctx, children });
205
+ } else {
206
+ content = fallback ?? null;
207
+ }
208
+ return /* @__PURE__ */ jsxs2("div", { className, style: { ...defaultStyle, ...style }, children: [
209
+ /* @__PURE__ */ jsx3("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block" } }),
210
+ content
132
211
  ] });
133
212
  }
134
213
 
135
- // src/useMatterContext.ts
136
- import { useContext } from "react";
137
- function useMatterContext() {
138
- return useContext(MatterContext);
214
+ // src/hooks/use-animatable-uniform/use-animatable-uniform.ts
215
+ import { useEffect as useEffect4, useMemo } from "react";
216
+ import { uniform } from "three/tsl";
217
+ var isSignal = (value) => {
218
+ if (typeof value !== "object" || value === null) return false;
219
+ return "get" in value && typeof value.get === "function" && "on" in value && typeof value.on === "function";
220
+ };
221
+ function useAnimatableUniform(value) {
222
+ const uniformNode = useMemo(() => {
223
+ const initial = isSignal(value) ? value.get() : value;
224
+ return uniform(initial);
225
+ }, []);
226
+ useEffect4(() => {
227
+ if (isSignal(value)) {
228
+ const unsub = value.on("change", (next) => {
229
+ uniformNode.value = next;
230
+ });
231
+ return unsub;
232
+ }
233
+ uniformNode.value = value;
234
+ return void 0;
235
+ }, [value, uniformNode]);
236
+ return uniformNode;
139
237
  }
140
238
 
141
- // src/useShaderMaterial.ts
142
- import { useEffect as useEffect2, useMemo } from "react";
143
- import { MeshBasicNodeMaterial } from "three/webgpu";
144
- function useShaderMaterial(build) {
145
- const material = useMemo(() => {
146
- const m = new MeshBasicNodeMaterial();
147
- m.colorNode = build();
148
- return m;
149
- }, [build]);
150
- useEffect2(() => {
151
- return () => material.dispose();
152
- }, [material]);
153
- return material;
239
+ // src/hooks/use-cursor/use-cursor.ts
240
+ import { CursorInput } from "@lovo/matter";
241
+ import { useEffect as useEffect5, useState as useState4 } from "react";
242
+
243
+ // src/hooks/use-shader-context/use-shader-context.ts
244
+ import { useContext as useContext2 } from "react";
245
+ function useShaderContext() {
246
+ return useContext2(ShaderContext);
154
247
  }
155
248
 
156
- // src/useCursor.ts
157
- import { useEffect as useEffect3, useState as useState2 } from "react";
158
- import { CursorInput } from "@lovo/matter";
249
+ // src/hooks/use-cursor/use-cursor.ts
159
250
  var STUB_SIGNAL = {
160
251
  get: () => [0.5, 0.5],
161
252
  on: () => () => void 0
162
253
  };
163
254
  function useCursor(opts = {}) {
164
- const ctx = useMatterContext();
165
- const [input, setInput] = useState2(null);
166
- useEffect3(() => {
255
+ const ctx = useShaderContext();
256
+ const [input, setInput] = useState4(null);
257
+ useEffect5(() => {
167
258
  const canvas = ctx?.renderer.three.domElement;
168
259
  const elementOpt = opts.element ?? (canvas instanceof HTMLElement ? canvas : void 0);
169
260
  const fresh = new CursorInput({ ...opts, element: elementOpt });
@@ -188,7 +279,7 @@ function useCursor(opts = {}) {
188
279
  };
189
280
  }
190
281
  return () => {
191
- detach?.();
282
+ detach();
192
283
  fresh.dispose();
193
284
  setInput(null);
194
285
  };
@@ -196,16 +287,27 @@ function useCursor(opts = {}) {
196
287
  return input ?? STUB_SIGNAL;
197
288
  }
198
289
 
199
- // src/useResize.ts
200
- import { useEffect as useEffect4, useState as useState3 } from "react";
290
+ // src/hooks/use-overlay-pass/use-overlay-pass.ts
291
+ import { useEffect as useEffect6 } from "react";
292
+ function useOverlayPass(transform, deps) {
293
+ const ctx = useShaderContext();
294
+ useEffect6(() => {
295
+ if (!ctx) return;
296
+ const unregister = ctx.registerOverlay(transform);
297
+ return unregister;
298
+ }, [ctx, ...deps]);
299
+ }
300
+
301
+ // src/hooks/use-resize/use-resize.ts
302
+ import { useEffect as useEffect7, useState as useState5 } from "react";
201
303
  var STUB_SIGNAL2 = {
202
304
  get: () => [0, 0, 1],
203
305
  on: () => () => void 0
204
306
  };
205
307
  function useResize() {
206
- const ctx = useMatterContext();
207
- const [signal, setSignal] = useState3(null);
208
- useEffect4(() => {
308
+ const ctx = useShaderContext();
309
+ const [signal, setSignal] = useState5(null);
310
+ useEffect7(() => {
209
311
  if (!ctx) return void 0;
210
312
  const canvas = ctx.renderer.three.domElement;
211
313
  if (!(canvas instanceof HTMLCanvasElement)) return void 0;
@@ -265,15 +367,15 @@ function useResize() {
265
367
  return signal ?? STUB_SIGNAL2;
266
368
  }
267
369
 
268
- // src/useScroll.ts
269
- import { useEffect as useEffect5, useState as useState4 } from "react";
370
+ // src/hooks/use-scroll/use-scroll.ts
371
+ import { useEffect as useEffect8, useState as useState6 } from "react";
270
372
  var STUB_SIGNAL3 = {
271
373
  get: () => [0, 0],
272
374
  on: () => () => void 0
273
375
  };
274
376
  function useScroll() {
275
- const [signal, setSignal] = useState4(null);
276
- useEffect5(() => {
377
+ const [signal, setSignal] = useState6(null);
378
+ useEffect8(() => {
277
379
  if (typeof window === "undefined") return void 0;
278
380
  const compute = () => {
279
381
  const y = window.scrollY;
@@ -315,134 +417,41 @@ function useScroll() {
315
417
  return signal ?? STUB_SIGNAL3;
316
418
  }
317
419
 
318
- // src/useAnimatableUniform.ts
319
- import { useEffect as useEffect6, useMemo as useMemo2 } from "react";
320
- import { uniform } from "three/tsl";
321
- var isSignal = (value) => {
322
- return typeof value === "object" && value !== null && typeof value.get === "function" && typeof value.on === "function";
323
- };
324
- function useAnimatableUniform(value) {
325
- const uniformNode = useMemo2(() => {
326
- const initial = isSignal(value) ? value.get() : value;
327
- return uniform(initial);
328
- }, []);
329
- useEffect6(() => {
330
- if (isSignal(value)) {
331
- const unsub = value.on("change", (next) => {
332
- ;
333
- uniformNode.value = next;
334
- });
335
- return unsub;
336
- }
337
- ;
338
- uniformNode.value = value;
339
- return void 0;
340
- }, [value, uniformNode]);
341
- return uniformNode;
342
- }
343
-
344
- // src/useOverlayPass.ts
345
- import { useEffect as useEffect7 } from "react";
346
- function useOverlayPass(transform, deps) {
347
- const ctx = useMatterContext();
348
- useEffect7(() => {
349
- if (!ctx) return;
350
- const unregister = ctx.registerOverlay(transform);
351
- return unregister;
352
- }, [ctx, ...deps]);
353
- }
354
-
355
- // src/FallbackBoundary.tsx
356
- import { useEffect as useEffect8, useState as useState5 } from "react";
357
- import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
358
- function FallbackBoundary({ fallback, children }) {
359
- const [mounted, setMounted] = useState5(false);
360
- useEffect8(() => {
361
- setMounted(true);
362
- }, []);
363
- return /* @__PURE__ */ jsx2(Fragment, { children: mounted ? children : fallback ?? null });
420
+ // src/hooks/use-shader-material/use-shader-material.ts
421
+ import { useEffect as useEffect9, useMemo as useMemo2 } from "react";
422
+ import { MeshBasicNodeMaterial } from "three/webgpu";
423
+ function useShaderMaterial(build) {
424
+ const material = useMemo2(() => {
425
+ const m = new MeshBasicNodeMaterial();
426
+ m.colorNode = build();
427
+ return m;
428
+ }, [build]);
429
+ useEffect9(() => {
430
+ return () => material.dispose();
431
+ }, [material]);
432
+ return material;
364
433
  }
365
434
 
366
- // src/useStaticHint.ts
367
- import { useEffect as useEffect9 } from "react";
435
+ // src/hooks/use-static-hint/use-static-hint.ts
436
+ import { useEffect as useEffect10 } from "react";
368
437
  function useStaticHint(hint) {
369
- const ctx = useMatterContext();
370
- useEffect9(() => {
438
+ const ctx = useShaderContext();
439
+ useEffect10(() => {
371
440
  if (!ctx) return;
372
441
  ctx.scheduler.setIdle(hint);
373
442
  return () => ctx.scheduler.setIdle(false);
374
443
  }, [ctx, hint]);
375
444
  }
376
-
377
- // src/MatterMonitor.tsx
378
- import { useEffect as useEffect10, useRef as useRef2, useState as useState6, useContext as useContext2 } from "react";
379
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
380
- var anchorStyle = {
381
- "top-left": { top: 8, left: 8 },
382
- "top-right": { top: 8, right: 8 },
383
- "bottom-left": { bottom: 8, left: 8 },
384
- "bottom-right": { bottom: 8, right: 8 }
385
- };
386
- var baseStyle = {
387
- position: "absolute",
388
- zIndex: 10,
389
- padding: "6px 8px",
390
- borderRadius: 6,
391
- background: "rgba(0, 0, 0, 0.6)",
392
- color: "#fff",
393
- font: "11px ui-monospace, monospace",
394
- lineHeight: 1.4,
395
- pointerEvents: "none",
396
- whiteSpace: "pre"
397
- };
398
- function MatterMonitor({ anchor = "top-right" }) {
399
- const ctx = useContext2(MatterContext);
400
- const [stats, setStats] = useState6({ fps: 0, ticks: 0, frames: 0 });
401
- const ticksRef = useRef2(0);
402
- const fpsAccumRef = useRef2({ frames: 0, lastSampleAt: 0, fps: 0 });
403
- useEffect10(() => {
404
- if (!ctx) return;
405
- const client = (tick) => {
406
- ticksRef.current += 1;
407
- const acc = fpsAccumRef.current;
408
- acc.frames += 1;
409
- if (acc.lastSampleAt === 0) acc.lastSampleAt = tick.now;
410
- const dt = tick.now - acc.lastSampleAt;
411
- if (dt >= 500) {
412
- acc.fps = Math.round(acc.frames * 1e3 / dt);
413
- acc.frames = 0;
414
- acc.lastSampleAt = tick.now;
415
- }
416
- setStats({ fps: acc.fps, ticks: ticksRef.current, frames: acc.frames });
417
- };
418
- ctx.scheduler.add(client);
419
- return () => ctx.scheduler.remove(client);
420
- }, [ctx]);
421
- if (!ctx) {
422
- return /* @__PURE__ */ jsx3("div", { "data-testid": "matter-monitor", style: { ...baseStyle, ...anchorStyle[anchor] }, children: "no scene" });
423
- }
424
- return /* @__PURE__ */ jsxs2("div", { "data-testid": "matter-monitor", style: { ...baseStyle, ...anchorStyle[anchor] }, children: [
425
- /* @__PURE__ */ jsxs2("span", { "data-testid": "matter-monitor-fps", children: [
426
- "fps: ",
427
- stats.fps || "\u2014"
428
- ] }),
429
- "\n",
430
- /* @__PURE__ */ jsxs2("span", { "data-testid": "matter-monitor-ticks", children: [
431
- "ticks: ",
432
- stats.ticks
433
- ] })
434
- ] });
435
- }
436
445
  export {
437
446
  FallbackBoundary,
438
- MatterMonitor,
439
- MatterScene,
447
+ ShaderMonitor,
448
+ ShaderScene,
440
449
  useAnimatableUniform,
441
450
  useCursor,
442
- useMatterContext,
443
451
  useOverlayPass,
444
452
  useResize,
445
453
  useScroll,
454
+ useShaderContext,
446
455
  useShaderMaterial,
447
456
  useStaticHint
448
457
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/MatterScene.tsx","../src/matter-context.ts","../src/useMatterContext.ts","../src/useShaderMaterial.ts","../src/useCursor.ts","../src/useResize.ts","../src/useScroll.ts","../src/useAnimatableUniform.ts","../src/useOverlayPass.ts","../src/FallbackBoundary.tsx","../src/useStaticHint.ts","../src/MatterMonitor.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'\nimport { Scene, OrthographicCamera } from 'three'\nimport { PostProcessing } from 'three/webgpu'\nimport type { Node } from 'three/webgpu'\nimport { pass } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport {\n createRenderer,\n MatterScheduler,\n createVisibilityWatcher,\n createIntersectionWatcher,\n} from '@lovo/matter'\nimport { MatterContext, type MatterContextValue, type OverlayTransform } from './matter-context.js'\n\nexport interface MatterSceneProps {\n children?: ReactNode\n /** Rendered server-side and during WebGPU init. Default: empty. */\n fallback?: ReactNode\n className?: string\n style?: CSSProperties\n /** Cap on devicePixelRatio. Default: 2. */\n maxDPR?: number\n}\n\nconst defaultStyle: CSSProperties = {\n position: 'absolute',\n inset: 0,\n display: 'block',\n width: '100%',\n height: '100%',\n}\n\n/**\n * Owns a canvas, a Three.js renderer (WebGPU + WebGL2 fallback), an\n * orthographic camera covering the canvas, an empty Scene, and a\n * MatterScheduler. Children consume these via useMatterContext().\n */\nexport function MatterScene(props: MatterSceneProps) {\n const { children, fallback, className, style, maxDPR } = props\n const canvasRef = useRef<HTMLCanvasElement>(null)\n const [ctx, setCtx] = useState<MatterContextValue | null>(null)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n let cancelled = false\n let cleanup: (() => void) | null = null\n\n const setup = async () => {\n try {\n const renderer = await createRenderer(canvas, { maxDPR })\n if (cancelled) {\n renderer.dispose()\n return\n }\n const scene = new Scene()\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10)\n camera.position.z = 1\n const postProcessing = new PostProcessing(renderer.three)\n const scheduler = new MatterScheduler()\n\n const overlays = new Map<symbol, OverlayTransform>()\n\n // Allocate the base PassNode once per setup so rebuilds reuse the same\n // node identity instead of churning a fresh one (and a fresh render\n // target binding) on every register/unregister.\n const basePass = pass(scene, camera) as unknown as ShaderNodeObject<Node>\n\n const rebuildOutputNode = () => {\n const transforms = Array.from(overlays.values())\n postProcessing.outputNode = transforms.reduce(\n (node, transform) => transform(node),\n basePass,\n )\n postProcessing.needsUpdate = true\n }\n\n rebuildOutputNode() // initial: just basePass, no overlays\n\n const registerOverlay = (transform: OverlayTransform): (() => void) => {\n const key = Symbol('overlay')\n overlays.set(key, transform)\n rebuildOutputNode()\n return () => {\n overlays.delete(key)\n rebuildOutputNode()\n }\n }\n\n scheduler.add(() => postProcessing.render())\n scheduler.start()\n\n const visibility = createVisibilityWatcher()\n const intersection = createIntersectionWatcher(canvas)\n\n const updatePauseState = () => {\n const shouldRun = visibility.isVisible() && intersection.isInView()\n if (shouldRun) scheduler.resume()\n else scheduler.pause()\n }\n updatePauseState()\n\n const unsubVisibility = visibility.subscribe(updatePauseState)\n const unsubIntersection = intersection.subscribe(updatePauseState)\n\n const onResize = () => renderer.resize()\n window.addEventListener('resize', onResize)\n\n cleanup = () => {\n unsubVisibility()\n unsubIntersection()\n visibility.dispose()\n intersection.dispose()\n window.removeEventListener('resize', onResize)\n scheduler.dispose()\n renderer.dispose()\n }\n\n setCtx({ renderer, scene, camera, scheduler, registerOverlay })\n } catch (err) {\n if (cancelled) return\n const e = err instanceof Error ? err : new Error(String(err))\n console.error('[MatterScene] renderer init failed:', e)\n setError(e)\n }\n }\n\n void setup()\n return () => {\n cancelled = true\n cleanup?.()\n cleanup = null\n setCtx(null)\n }\n }, [maxDPR])\n\n return (\n <div className={className} style={{ ...defaultStyle, ...style }}>\n <canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />\n {error ? (\n <div\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '1rem',\n color: '#fff',\n background: 'rgba(120, 30, 30, 0.85)',\n font: '0.85rem ui-monospace, monospace',\n whiteSpace: 'pre-wrap',\n textAlign: 'center',\n }}\n >\n MatterScene init failed:\n {'\\n'}\n {error.message}\n </div>\n ) : ctx ? (\n <MatterContext.Provider value={ctx}>{children}</MatterContext.Provider>\n ) : (\n (fallback ?? null)\n )}\n </div>\n )\n}\n","import { createContext } from 'react'\nimport type { Scene, Camera } from 'three'\nimport type { Node } from 'three/webgpu'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { MatterRenderer, MatterScheduler } from '@lovo/matter'\n\nexport type OverlayTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>\n\nexport interface MatterContextValue {\n renderer: MatterRenderer\n scene: Scene\n camera: Camera\n scheduler: MatterScheduler\n registerOverlay: (transform: OverlayTransform) => () => void\n}\n\nexport const MatterContext = createContext<MatterContextValue | null>(null)\n","import { useContext } from 'react'\nimport { MatterContext, type MatterContextValue } from './matter-context.js'\n\n/**\n * Read the matter scene context. Returns null when called outside a\n * <MatterScene>; useShaderMaterial and similar hooks check this and\n * auto-provision a scene if missing (auto-wrap behavior).\n */\nexport function useMatterContext(): MatterContextValue | null {\n return useContext(MatterContext)\n}\n","'use client'\n\nimport { useEffect, useMemo } from 'react'\nimport { MeshBasicNodeMaterial } from 'three/webgpu'\nimport type { Node } from 'three/webgpu'\nimport type { ShaderNodeObject } from 'three/tsl'\n\n/** A TSL fragment that produces a color. Accept any Node or TSL-wrapped node. */\nexport type ColorTSL = Node | ShaderNodeObject<Node>\n\n/**\n * Bind a TSL color expression to a NodeMaterial. Returns the material;\n * caller is responsible for adding it to a mesh and disposing when done.\n *\n * The TSL fragment is computed once via `useMemo` and re-applied if the\n * factory function changes. For dynamic uniforms, mutate `.value` on the\n * uniform nodes — don't recreate the TSL fragment per render.\n */\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const m = new MeshBasicNodeMaterial()\n m.colorNode = build() as Node\n return m\n }, [build])\n\n useEffect(() => {\n return () => material.dispose()\n }, [material])\n\n return material\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\nimport { CursorInput, type CursorInputOptions, type Vec2 } from '@lovo/matter'\nimport { useMatterContext } from './useMatterContext.js'\n\nexport interface CursorSignal {\n /** Current smoothed cursor position (Vec2 in 0..1 viewport space). */\n get(): Vec2\n /** Subscribe to change events. Returns unsubscribe. */\n on(event: 'change', cb: (value: Vec2) => void): () => void\n}\n\n// Inert stub returned on the first render before the lifecycle effect\n// has created the real CursorInput. Calling .on returns an unsub no-op.\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n}\n\n/**\n * React wrapper for CursorInput. Auto-attaches to the parent <MatterScene>'s\n * scheduler if available; otherwise creates a free-running rAF tick.\n *\n * Lifecycle is in a single effect so React 19 Strict Mode's intentional\n * mount→unmount→mount cycle creates a *fresh* CursorInput per real mount\n * instead of disposing a long-lived one (which would silently break the\n * window mousemove listener and the smoothing tick).\n */\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const ctx = useMatterContext()\n const [input, setInput] = useState<CursorInput | null>(null)\n\n useEffect(() => {\n // Plumb the parent <MatterScene>'s canvas as the cursor's normalization\n // element. Without this, cursor coords are viewport-normalized — fine for\n // a full-page scene but visibly offset when the canvas sits inside a\n // smaller wrapper (e.g., 70vh hero). DotField's cell tiling makes the\n // mismatch obvious; LinearGradient mostly gets away with it. Caller can\n // override by passing `opts.element` explicitly.\n const canvas = ctx?.renderer.three.domElement\n const elementOpt = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined)\n const fresh = new CursorInput({ ...opts, element: elementOpt })\n setInput(fresh)\n\n let detach: (() => void) | null = null\n if (ctx?.scheduler) {\n const client = ({ delta }: { delta: number }) => fresh.tick(delta)\n ctx.scheduler.add(client)\n detach = () => ctx.scheduler.remove(client)\n } else {\n let raf: number | null = null\n let lastNow = performance.now()\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000\n lastNow = now\n fresh.tick(delta)\n raf = requestAnimationFrame(loop)\n }\n raf = requestAnimationFrame(loop)\n detach = () => {\n if (raf !== null) cancelAnimationFrame(raf)\n }\n }\n\n return () => {\n detach?.()\n fresh.dispose()\n setInput(null)\n }\n // We intentionally only re-create on ctx change, not opts (which is a\n // fresh object literal each render). Smoothing tweaks during dev are\n // applied by remounting the parent component.\n // oxlint-disable-next-line react/exhaustive-deps\n }, [ctx])\n\n return input ?? STUB_SIGNAL\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\nimport { useMatterContext } from './useMatterContext.js'\n\nexport type ResizeValue = readonly [width: number, height: number, dpr: number]\n\nexport interface ResizeSignal {\n /** Current size in CSS pixels + devicePixelRatio. */\n get(): ResizeValue\n on(event: 'change', cb: (value: ResizeValue) => void): () => void\n}\n\n// Inert stub returned on the first render before the lifecycle effect has\n// observed the canvas. Subscribing to it returns a no-op unsubscribe.\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n}\n\n/**\n * Track the parent <MatterScene>'s canvas size + DPR. Exposes a MatterSignal\n * that components can pass into a TSL uniform to make pixel-aware effects\n * (e.g., DotField's pixel-spacing math).\n *\n * Strict-Mode-safe: lifecycle is in one effect, so React 19's intentional\n * mount→unmount→mount cycle creates a fresh ResizeObserver per real mount\n * (CLAUDE.md gotcha #14).\n *\n * Falls back to the stub signal until the parent context is ready.\n */\nexport function useResize(): ResizeSignal {\n const ctx = useMatterContext()\n const [signal, setSignal] = useState<ResizeSignal | null>(null)\n\n useEffect(() => {\n if (!ctx) return undefined\n\n const canvas = ctx.renderer.three.domElement\n if (!(canvas instanceof HTMLCanvasElement)) return undefined\n\n let value: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ]\n const listeners = new Set<(v: ResizeValue) => void>()\n const fresh: ResizeSignal = {\n get: () => value,\n on: (_event, cb) => {\n listeners.add(cb)\n return () => {\n listeners.delete(cb)\n }\n },\n }\n setSignal(fresh)\n\n const emit = () => {\n const next: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ]\n if (next[0] === value[0] && next[1] === value[1] && next[2] === value[2]) return\n value = next\n for (const cb of listeners) cb(next)\n }\n\n const observer = new ResizeObserver(emit)\n observer.observe(canvas)\n\n // Cross-browser DPR-change watch. matchMedia(`(resolution: <dpr>dppx)`)\n // matches at the *current* DPR; when the user zooms the page the query\n // stops matching, fires `change`, and we re-arm the watch at the new DPR.\n // We track the current MQL + handler so we can fully detach in cleanup\n // (the handler is captured by the listener — passing a fresh closure to\n // removeEventListener wouldn't actually unregister it).\n let mql: MediaQueryList | null = null\n let mqlHandler: (() => void) | null = null\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return\n const dpr = window.devicePixelRatio\n const next = window.matchMedia(`(resolution: ${dpr}dppx)`)\n const handler = () => {\n emit()\n if (mql && mqlHandler) mql.removeEventListener('change', mqlHandler)\n setupDprWatch()\n }\n next.addEventListener('change', handler)\n mql = next\n mqlHandler = handler\n }\n setupDprWatch()\n\n return () => {\n observer.disconnect()\n if (mql && mqlHandler) mql.removeEventListener('change', mqlHandler)\n mql = null\n mqlHandler = null\n listeners.clear()\n setSignal(null)\n }\n }, [ctx])\n\n return signal ?? STUB_SIGNAL\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\nexport type ScrollValue = readonly [scrollY: number, progress: number]\n\nexport interface ScrollSignal {\n /** Current scroll Y (px) and normalized progress in [0,1]. */\n get(): ScrollValue\n on(event: 'change', cb: (value: ScrollValue) => void): () => void\n}\n\n// Inert stub returned during SSR + on the first client render before the\n// lifecycle effect attaches. Subscribing to it returns a no-op unsubscribe.\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n}\n\n/**\n * Track window scroll position. Exposes a MatterSignal of `[scrollY, progress]`\n * where `progress` is `scrollY / max(documentHeight - innerHeight, 1)` clamped\n * to [0, 1]. Listener is rAF-throttled and `passive: true` so it never blocks\n * scrolling.\n *\n * No v1 Tier 1 component consumes this hook; it ships so users can pass\n * `inputs={{ scroll: useScroll() }}` to any Matter component.\n *\n * Strict-Mode-safe: lifecycle is in one effect, so React 19's intentional\n * mount→unmount→mount cycle in dev creates a fresh listener pair per real\n * mount and tears down cleanly on each pseudo-unmount (CLAUDE.md gotcha #14).\n *\n * **Known limitation (v1):** `progress` is computed against whichever\n * `documentHeight` was current when the last scroll fired. If the page grows\n * after mount (async content, font load reflow, expanding panels) without\n * the user scrolling, the denominator goes stale. A future ResizeObserver/\n * MutationObserver pass would close the gap; deferred until a v1 component\n * consumes scroll input.\n */\nexport function useScroll(): ScrollSignal {\n const [signal, setSignal] = useState<ScrollSignal | null>(null)\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined\n\n const compute = (): ScrollValue => {\n const y = window.scrollY\n // For pages shorter than the viewport, `documentHeight - innerHeight` is\n // <= 0; clamp to 1 to avoid div-by-zero. Progress stays at 0 in that\n // case because scrollY is also 0.\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1)\n const progress = Math.max(0, Math.min(1, y / max))\n return [y, progress]\n }\n\n let value: ScrollValue = compute()\n const listeners = new Set<(v: ScrollValue) => void>()\n const fresh: ScrollSignal = {\n get: () => value,\n on: (_event, cb) => {\n listeners.add(cb)\n return () => {\n listeners.delete(cb)\n }\n },\n }\n setSignal(fresh)\n\n let rafPending = false\n const onScroll = () => {\n if (rafPending) return\n rafPending = true\n requestAnimationFrame(() => {\n rafPending = false\n const next = compute()\n if (next[0] === value[0] && next[1] === value[1]) return\n value = next\n for (const cb of listeners) cb(next)\n })\n }\n window.addEventListener('scroll', onScroll, { passive: true })\n\n return () => {\n window.removeEventListener('scroll', onScroll)\n listeners.clear()\n setSignal(null)\n }\n }, [])\n\n return signal ?? STUB_SIGNAL\n}\n","'use client'\n\nimport { useEffect, useMemo } from 'react'\nimport { uniform } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nexport interface MatterSignal<T> {\n get(): T\n on(event: 'change', cb: (value: T) => void): () => void\n}\n\nexport type AnimatableProp<T> = T | MatterSignal<T>\n\nconst isSignal = <T>(value: AnimatableProp<T>): value is MatterSignal<T> => {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as MatterSignal<T>).get === 'function' &&\n typeof (value as MatterSignal<T>).on === 'function'\n )\n}\n\n/**\n * Bind an AnimatableProp<T> to a TSL uniform. Plain values create a\n * static uniform that updates only when the prop changes (React render\n * path). Signals subscribe via .on('change') and write into the uniform\n * imperatively without re-rendering.\n */\nexport function useAnimatableUniform<T>(value: AnimatableProp<T>): ShaderNodeObject<Node> {\n // Create the uniform once with the initial value; subsequent updates flow\n // through the effect below (either via signal subscription or direct write).\n const uniformNode = useMemo(() => {\n const initial = isSignal(value) ? value.get() : value\n return uniform(initial) as unknown as ShaderNodeObject<Node>\n // oxlint-disable-next-line react/exhaustive-deps\n }, [])\n\n useEffect(() => {\n if (isSignal(value)) {\n const unsub = value.on('change', (next) => {\n ;(uniformNode as unknown as { value: T }).value = next\n })\n return unsub\n }\n ;(uniformNode as unknown as { value: T }).value = value\n return undefined\n }, [value, uniformNode])\n\n return uniformNode\n}\n","'use client'\n\nimport { useEffect, type DependencyList } from 'react'\nimport type { OverlayTransform } from './matter-context.js'\nimport { useMatterContext } from './useMatterContext.js'\n\n/**\n * Register a TSL transform as an overlay pass on the parent <MatterScene>.\n *\n * The transform takes the \"color so far\" — base scene + any earlier\n * overlays as a TSL vec4 node — and returns a modified vec4. Registration\n * happens on mount; unregistration on unmount. The hook re-registers\n * whenever any value in `deps` changes (useEffect semantics): use this\n * for structural changes (e.g., a `mode: 'additive' | 'subtractive'`\n * toggle) that swap the transform function itself. Uniforms captured\n * inside the transform mutate in place, so uniform value changes do\n * NOT need to be in deps.\n *\n * When called outside a <MatterScene> provider, this hook is a no-op.\n * Matches the existing useMatterContext convention.\n */\nexport function useOverlayPass(transform: OverlayTransform, deps: DependencyList): void {\n const ctx = useMatterContext()\n\n useEffect(() => {\n if (!ctx) return\n const unregister = ctx.registerOverlay(transform)\n return unregister\n // The transform captures the latest values via the deps array; we re-register\n // when deps change. ctx is included so a remounted MatterScene re-attaches.\n // oxlint-disable-next-line react/exhaustive-deps\n }, [ctx, ...deps])\n}\n","'use client'\n\nimport { useEffect, useState, type ReactNode } from 'react'\n\nexport interface FallbackBoundaryProps {\n /** Rendered until WebGPU/WebGL is available on the client. */\n fallback?: ReactNode\n children: ReactNode\n}\n\n/**\n * Render `fallback` until the component mounts on the client. Gates the\n * children behind client-only mounting so SSR/no-WebGPU users see a\n * sensible static placeholder rather than a flash of nothing.\n */\nexport function FallbackBoundary({ fallback, children }: FallbackBoundaryProps) {\n const [mounted, setMounted] = useState(false)\n useEffect(() => {\n setMounted(true)\n }, [])\n return <>{mounted ? children : (fallback ?? null)}</>\n}\n","'use client'\n\nimport { useEffect } from 'react'\nimport { useMatterContext } from './useMatterContext.js'\n\n/**\n * Opt a component out of the rAF loop while it has no dynamic uniforms.\n *\n * When `hint` is true, the scheduler runs one final flush tick (so any\n * uniform changes since the last frame are rendered) and then halts the\n * rAF loop until either `hint` becomes false or another component in the\n * same scene calls `scheduler.requestRender()`.\n *\n * Use for components whose animation is fully derived from props that don't\n * include `time`, e.g. `<LinearGradient speed={0}>` with no `interactive`.\n */\nexport function useStaticHint(hint: boolean): void {\n const ctx = useMatterContext()\n useEffect(() => {\n if (!ctx) return\n ctx.scheduler.setIdle(hint)\n return () => ctx.scheduler.setIdle(false)\n }, [ctx, hint])\n}\n","'use client'\n\nimport { useEffect, useRef, useState, useContext, type CSSProperties } from 'react'\nimport { MatterContext } from './matter-context.js'\n\nexport type MonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\nconst anchorStyle: Record<MonitorAnchor, CSSProperties> = {\n 'top-left': { top: 8, left: 8 },\n 'top-right': { top: 8, right: 8 },\n 'bottom-left': { bottom: 8, left: 8 },\n 'bottom-right': { bottom: 8, right: 8 },\n}\n\nconst baseStyle: CSSProperties = {\n position: 'absolute',\n zIndex: 10,\n padding: '6px 8px',\n borderRadius: 6,\n background: 'rgba(0, 0, 0, 0.6)',\n color: '#fff',\n font: '11px ui-monospace, monospace',\n lineHeight: 1.4,\n pointerEvents: 'none',\n whiteSpace: 'pre',\n}\n\nexport interface MatterMonitorProps {\n anchor?: MonitorAnchor\n}\n\n/**\n * Dev-only overlay that displays the current scene's FPS, tick count, and\n * paused/idle state. Reads from the surrounding `<MatterScene>` via context\n * and subscribes to its scheduler. Renders nothing useful if mounted outside\n * a scene.\n */\nexport function MatterMonitor({ anchor = 'top-right' }: MatterMonitorProps) {\n const ctx = useContext(MatterContext)\n const [stats, setStats] = useState({ fps: 0, ticks: 0, frames: 0 })\n const ticksRef = useRef(0)\n const fpsAccumRef = useRef({ frames: 0, lastSampleAt: 0, fps: 0 })\n\n useEffect(() => {\n if (!ctx) return\n const client = (tick: { now: number }) => {\n ticksRef.current += 1\n const acc = fpsAccumRef.current\n acc.frames += 1\n if (acc.lastSampleAt === 0) acc.lastSampleAt = tick.now\n const dt = tick.now - acc.lastSampleAt\n if (dt >= 500) {\n acc.fps = Math.round((acc.frames * 1000) / dt)\n acc.frames = 0\n acc.lastSampleAt = tick.now\n }\n setStats({ fps: acc.fps, ticks: ticksRef.current, frames: acc.frames })\n }\n ctx.scheduler.add(client)\n return () => ctx.scheduler.remove(client)\n }, [ctx])\n\n if (!ctx) {\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n no scene\n </div>\n )\n }\n\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n <span data-testid=\"matter-monitor-fps\">fps: {stats.fps || '—'}</span>\n {'\\n'}\n <span data-testid=\"matter-monitor-ticks\">ticks: {stats.ticks}</span>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useEffect","useState","useState","useEffect","useEffect","useState","STUB_SIGNAL","useState","useEffect","useEffect","useState","STUB_SIGNAL","useEffect","useMemo","useEffect","useEffect","useEffect","useState","jsx","useEffect","useEffect","useEffect","useRef","useState","useContext","jsx","jsxs","useContext","useState","useRef","useEffect"]}
1
+ {"version":3,"sources":["../src/components/fallback-boundary/fallback-boundary.tsx","../src/components/shader-monitor/shader-monitor.tsx","../src/context/shader-context.ts","../src/components/shader-scene/shader-scene.tsx","../src/hooks/use-animatable-uniform/use-animatable-uniform.ts","../src/hooks/use-cursor/use-cursor.ts","../src/hooks/use-shader-context/use-shader-context.ts","../src/hooks/use-overlay-pass/use-overlay-pass.ts","../src/hooks/use-resize/use-resize.ts","../src/hooks/use-scroll/use-scroll.ts","../src/hooks/use-shader-material/use-shader-material.ts","../src/hooks/use-static-hint/use-static-hint.ts"],"sourcesContent":["'use client'\n\nimport { type ReactNode, useEffect, useState } from 'react'\n\nexport interface FallbackBoundaryProps {\n /** Rendered until WebGPU/WebGL is available on the client. */\n fallback?: ReactNode\n children: ReactNode\n}\n\n/**\n * Render `fallback` until the component mounts on the client. Gates the\n * children behind client-only mounting so SSR/no-WebGPU users see a\n * sensible static placeholder rather than a flash of nothing.\n */\nexport function FallbackBoundary({ fallback, children }: FallbackBoundaryProps) {\n const [mounted, setMounted] = useState(false)\n\n useEffect(() => {\n setMounted(true)\n }, [])\n\n return <>{mounted ? children : (fallback ?? null)}</>\n}\n","'use client'\n\nimport { type CSSProperties, useContext, useEffect, useRef, useState } from 'react'\n\nimport { ShaderContext } from '../../context/shader-context.js'\n\nexport type MonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\nconst anchorStyle: Record<MonitorAnchor, CSSProperties> = {\n 'top-left': { top: 8, left: 8 },\n 'top-right': { top: 8, right: 8 },\n 'bottom-left': { bottom: 8, left: 8 },\n 'bottom-right': { bottom: 8, right: 8 },\n}\n\nconst baseStyle: CSSProperties = {\n position: 'absolute',\n zIndex: 10,\n padding: '6px 8px',\n borderRadius: 6,\n background: 'rgba(0, 0, 0, 0.6)',\n color: '#fff',\n font: '11px ui-monospace, monospace',\n lineHeight: 1.4,\n pointerEvents: 'none',\n whiteSpace: 'pre',\n}\n\nexport interface ShaderMonitorProps {\n anchor?: MonitorAnchor\n}\n\n/**\n * Dev-only overlay that displays the current scene's FPS, tick count, and\n * paused/idle state. Reads from the surrounding `<ShaderScene>` via context\n * and subscribes to its scheduler. Renders nothing useful if mounted outside\n * a scene.\n */\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const ctx = useContext(ShaderContext)\n const [stats, setStats] = useState({ fps: 0, ticks: 0, frames: 0 })\n const ticksRef = useRef(0)\n const fpsAccumRef = useRef({ frames: 0, lastSampleAt: 0, fps: 0 })\n\n useEffect(() => {\n if (!ctx) return\n const client = (tick: { now: number }) => {\n ticksRef.current += 1\n const acc = fpsAccumRef.current\n\n acc.frames += 1\n if (acc.lastSampleAt === 0) acc.lastSampleAt = tick.now\n const dt = tick.now - acc.lastSampleAt\n\n if (dt >= 500) {\n acc.fps = Math.round((acc.frames * 1000) / dt)\n acc.frames = 0\n acc.lastSampleAt = tick.now\n }\n setStats({ fps: acc.fps, ticks: ticksRef.current, frames: acc.frames })\n }\n\n ctx.scheduler.add(client)\n\n return () => ctx.scheduler.remove(client)\n }, [ctx])\n\n if (!ctx) {\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n no scene\n </div>\n )\n }\n\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n <span data-testid=\"matter-monitor-fps\">fps: {stats.fps || '—'}</span>\n {'\\n'}\n <span data-testid=\"matter-monitor-ticks\">ticks: {stats.ticks}</span>\n </div>\n )\n}\n","import type { FrameScheduler, GpuRenderer } from '@lovo/matter'\nimport { createContext } from 'react'\nimport type { Camera, Scene } from 'three'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nexport type OverlayTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer\n scene: Scene\n camera: Camera\n scheduler: FrameScheduler\n registerOverlay: (transform: OverlayTransform) => () => void\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null)\n","'use client'\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n FrameScheduler,\n} from '@lovo/matter'\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react'\nimport { OrthographicCamera, Scene } from 'three'\nimport { pass } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport { PostProcessing } from 'three/webgpu'\nimport type { Node } from 'three/webgpu'\n\nimport {\n type OverlayTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js'\n\nexport interface ShaderSceneProps {\n children?: ReactNode\n /** Rendered server-side and during WebGPU init. Default: empty. */\n fallback?: ReactNode\n className?: string\n style?: CSSProperties\n /** Cap on devicePixelRatio. Default: 2. */\n maxDPR?: number\n}\n\nconst defaultStyle: CSSProperties = {\n position: 'absolute',\n inset: 0,\n display: 'block',\n width: '100%',\n height: '100%',\n}\n\n/**\n * Owns a canvas, a Three.js renderer (WebGPU + WebGL2 fallback), an\n * orthographic camera covering the canvas, an empty Scene, and a\n * FrameScheduler. Children consume these via useShaderContext().\n */\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR } = props\n const canvasRef = useRef<HTMLCanvasElement>(null)\n const [ctx, setCtx] = useState<ShaderContextValue | null>(null)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n const canvas = canvasRef.current\n\n if (!canvas) return\n\n let cancelled = false\n let cleanup: (() => void) | null = null\n\n const setup = async () => {\n try {\n const renderer = await createRenderer(canvas, { maxDPR })\n\n if (cancelled) {\n renderer.dispose()\n\n return\n }\n const scene = new Scene()\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10)\n\n camera.position.z = 1\n const postProcessing = new PostProcessing(renderer.three)\n const scheduler = new FrameScheduler()\n\n const overlays = new Map<symbol, OverlayTransform>()\n\n // Allocate the base PassNode once per setup so rebuilds reuse the same\n // node identity instead of churning a fresh one (and a fresh render\n // target binding) on every register/unregister.\n const basePass = pass(scene, camera)\n\n const rebuildOutputNode = () => {\n // ShaderNodeObject<PassNode> isn't structurally assignable to\n // ShaderNodeObject<Node> (invariant generic methods); a runtime\n // PassNode is still a Node, so the cast at the reduce seed boundary\n // is safe. See CLAUDE.md gotcha #5.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n const seed = basePass as unknown as ShaderNodeObject<Node>\n\n postProcessing.outputNode = Array.from(overlays.values()).reduce(\n (node, transform) => transform(node),\n seed,\n )\n postProcessing.needsUpdate = true\n }\n\n rebuildOutputNode() // initial: just basePass, no overlays\n\n const registerOverlay = (transform: OverlayTransform): (() => void) => {\n const key = Symbol('overlay')\n\n overlays.set(key, transform)\n rebuildOutputNode()\n\n return () => {\n overlays.delete(key)\n rebuildOutputNode()\n }\n }\n\n scheduler.add(() => postProcessing.render())\n scheduler.start()\n\n const visibility = createVisibilityWatcher()\n const intersection = createIntersectionWatcher(canvas)\n\n const updatePauseState = () => {\n const shouldRun = visibility.isVisible() && intersection.isInView()\n\n if (shouldRun) scheduler.resume()\n else scheduler.pause()\n }\n\n updatePauseState()\n\n const unsubVisibility = visibility.subscribe(updatePauseState)\n const unsubIntersection = intersection.subscribe(updatePauseState)\n\n const onResize = () => renderer.resize()\n\n window.addEventListener('resize', onResize)\n\n cleanup = () => {\n unsubVisibility()\n unsubIntersection()\n visibility.dispose()\n intersection.dispose()\n window.removeEventListener('resize', onResize)\n scheduler.dispose()\n renderer.dispose()\n }\n\n setCtx({ renderer, scene, camera, scheduler, registerOverlay })\n } catch (err) {\n if (cancelled) return\n const e = err instanceof Error ? err : new Error(String(err))\n\n console.error('[ShaderScene] renderer init failed:', e)\n setError(e)\n }\n }\n\n void setup()\n\n return () => {\n cancelled = true\n cleanup?.()\n cleanup = null\n setCtx(null)\n }\n }, [maxDPR])\n\n let content: ReactNode\n\n if (error) {\n content = (\n <div\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '1rem',\n color: '#fff',\n background: 'rgba(120, 30, 30, 0.85)',\n font: '0.85rem ui-monospace, monospace',\n whiteSpace: 'pre-wrap',\n textAlign: 'center',\n }}\n >\n ShaderScene init failed:\n {'\\n'}\n {error.message}\n </div>\n )\n } else if (ctx) {\n content = <ShaderContext.Provider value={ctx}>{children}</ShaderContext.Provider>\n } else {\n content = fallback ?? null\n }\n\n return (\n <div className={className} style={{ ...defaultStyle, ...style }}>\n <canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />\n {content}\n </div>\n )\n}\n","'use client'\n\nimport { useEffect, useMemo } from 'react'\nimport { uniform } from 'three/tsl'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport type { Node } from 'three/webgpu'\n\nexport interface AnimatableSignal<T> {\n get(): T\n on(event: 'change', cb: (value: T) => void): () => void\n}\n\nexport type AnimatableProp<T> = T | AnimatableSignal<T>\n\nconst isSignal = <T>(value: AnimatableProp<T>): value is AnimatableSignal<T> => {\n if (typeof value !== 'object' || value === null) return false\n\n return (\n 'get' in value &&\n typeof value.get === 'function' &&\n 'on' in value &&\n typeof value.on === 'function'\n )\n}\n\n/**\n * Bind an AnimatableProp<T> to a TSL uniform. Plain values create a\n * static uniform that updates only when the prop changes (React render\n * path). Signals subscribe via .on('change') and write into the uniform\n * imperatively without re-rendering.\n *\n * Returns a chainable TSL node with `.value: T` exposed for callers that\n * need to read the current uniform value imperatively (e.g., to compute\n * derived JS-side math). The runtime object is a UniformNode<T>; we\n * present it as `ShaderNodeObject<Node> & { value: T }` because TSL's\n * generic invariance blocks the more precise type from flowing through\n * downstream consumers like OverlayTransform.\n */\nexport function useAnimatableUniform<T>(\n value: AnimatableProp<T>,\n): ShaderNodeObject<Node> & { value: T } {\n // Create the uniform once with the initial value; subsequent updates flow\n // through the effect below (either via signal subscription or direct write).\n const uniformNode = useMemo(() => {\n const initial = isSignal(value) ? value.get() : value\n\n return uniform(initial)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n useEffect(() => {\n if (isSignal(value)) {\n const unsub = value.on('change', (next) => {\n uniformNode.value = next\n })\n\n return unsub\n }\n uniformNode.value = value\n\n return undefined\n }, [value, uniformNode])\n\n // TSL's ShaderNodeObject<UniformNode<T>> isn't structurally assignable to\n // ShaderNodeObject<Node> because the ShaderNodeObject proxy has invariant\n // generic methods (label/etc.). Consumers chain `.mul()`/`.add()` which\n // work on either at runtime; the narrower return type is fine to widen.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n return uniformNode as unknown as ShaderNodeObject<Node> & { value: T }\n}\n","'use client'\n\nimport { CursorInput, type CursorInputOptions, type Vec2 } from '@lovo/matter'\nimport { useEffect, useState } from 'react'\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js'\n\nexport interface CursorSignal {\n /** Current smoothed cursor position (Vec2 in 0..1 viewport space). */\n get(): Vec2\n /** Subscribe to change events. Returns unsubscribe. */\n on(event: 'change', cb: (value: Vec2) => void): () => void\n}\n\n// Inert stub returned on the first render before the lifecycle effect\n// has created the real CursorInput. Calling .on returns an unsub no-op.\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n}\n\n/**\n * React wrapper for CursorInput. Auto-attaches to the parent <ShaderScene>'s\n * scheduler if available; otherwise creates a free-running rAF tick.\n *\n * Lifecycle is in a single effect so React 19 Strict Mode's intentional\n * mount→unmount→mount cycle creates a *fresh* CursorInput per real mount\n * instead of disposing a long-lived one (which would silently break the\n * window mousemove listener and the smoothing tick).\n */\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const ctx = useShaderContext()\n const [input, setInput] = useState<CursorInput | null>(null)\n\n useEffect(() => {\n // Plumb the parent <ShaderScene>'s canvas as the cursor's normalization\n // element. Without this, cursor coords are viewport-normalized — fine for\n // a full-page scene but visibly offset when the canvas sits inside a\n // smaller wrapper (e.g., 70vh hero). DotField's cell tiling makes the\n // mismatch obvious; LinearGradient mostly gets away with it. Caller can\n // override by passing `opts.element` explicitly.\n const canvas = ctx?.renderer.three.domElement\n const elementOpt = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined)\n const fresh = new CursorInput({ ...opts, element: elementOpt })\n\n setInput(fresh)\n\n let detach: (() => void) | null = null\n\n if (ctx?.scheduler) {\n const client = ({ delta }: { delta: number }) => fresh.tick(delta)\n\n ctx.scheduler.add(client)\n detach = () => ctx.scheduler.remove(client)\n } else {\n let raf: number | null = null\n let lastNow = performance.now()\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000\n\n lastNow = now\n fresh.tick(delta)\n raf = requestAnimationFrame(loop)\n }\n\n raf = requestAnimationFrame(loop)\n detach = () => {\n if (raf !== null) cancelAnimationFrame(raf)\n }\n }\n\n return () => {\n detach()\n fresh.dispose()\n setInput(null)\n }\n // We intentionally only re-create on ctx change, not opts (which is a\n // fresh object literal each render). Smoothing tweaks during dev are\n // applied by remounting the parent component.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ctx])\n\n return input ?? STUB_SIGNAL\n}\n","import { useContext } from 'react'\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js'\n\n/**\n * Read the shader scene context. Returns null when called outside a\n * <ShaderScene>; useShaderMaterial and similar hooks check this.\n */\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext)\n}\n","'use client'\n\nimport { type DependencyList, useEffect } from 'react'\n\nimport type { OverlayTransform } from '../../context/shader-context.js'\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js'\n\n/**\n * Register a TSL transform as an overlay pass on the parent <ShaderScene>.\n *\n * The transform takes the \"color so far\" — base scene + any earlier\n * overlays as a TSL vec4 node — and returns a modified vec4. Registration\n * happens on mount; unregistration on unmount. The hook re-registers\n * whenever any value in `deps` changes (useEffect semantics): use this\n * for structural changes (e.g., a `mode: 'additive' | 'subtractive'`\n * toggle) that swap the transform function itself. Uniforms captured\n * inside the transform mutate in place, so uniform value changes do\n * NOT need to be in deps.\n *\n * When called outside a <ShaderScene> provider, this hook is a no-op.\n * Matches the existing useShaderContext convention.\n */\nexport function useOverlayPass(transform: OverlayTransform, deps: DependencyList): void {\n const ctx = useShaderContext()\n\n useEffect(() => {\n if (!ctx) return\n const unregister = ctx.registerOverlay(transform)\n\n return unregister\n // The transform captures the latest values via the deps array; we re-register\n // when deps change. ctx is included so a remounted ShaderScene re-attaches.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ctx, ...deps])\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js'\n\nexport type ResizeValue = readonly [width: number, height: number, dpr: number]\n\nexport interface ResizeSignal {\n /** Current size in CSS pixels + devicePixelRatio. */\n get(): ResizeValue\n on(event: 'change', cb: (value: ResizeValue) => void): () => void\n}\n\n// Inert stub returned on the first render before the lifecycle effect has\n// observed the canvas. Subscribing to it returns a no-op unsubscribe.\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n}\n\n/**\n * Track the parent <ShaderScene>'s canvas size + DPR. Exposes an AnimatableSignal\n * that components can pass into a TSL uniform to make pixel-aware effects\n * (e.g., DotField's pixel-spacing math).\n *\n * Strict-Mode-safe: lifecycle is in one effect, so React 19's intentional\n * mount→unmount→mount cycle creates a fresh ResizeObserver per real mount\n * (CLAUDE.md gotcha #14).\n *\n * Falls back to the stub signal until the parent context is ready.\n */\nexport function useResize(): ResizeSignal {\n const ctx = useShaderContext()\n const [signal, setSignal] = useState<ResizeSignal | null>(null)\n\n useEffect(() => {\n if (!ctx) return undefined\n\n const canvas = ctx.renderer.three.domElement\n\n if (!(canvas instanceof HTMLCanvasElement)) return undefined\n\n let value: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ]\n const listeners = new Set<(v: ResizeValue) => void>()\n const fresh: ResizeSignal = {\n get: () => value,\n on: (_event, cb) => {\n listeners.add(cb)\n\n return () => {\n listeners.delete(cb)\n }\n },\n }\n\n setSignal(fresh)\n\n const emit = () => {\n const next: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ]\n\n if (next[0] === value[0] && next[1] === value[1] && next[2] === value[2]) return\n value = next\n for (const cb of listeners) cb(next)\n }\n\n const observer = new ResizeObserver(emit)\n\n observer.observe(canvas)\n\n // Cross-browser DPR-change watch. matchMedia(`(resolution: <dpr>dppx)`)\n // matches at the *current* DPR; when the user zooms the page the query\n // stops matching, fires `change`, and we re-arm the watch at the new DPR.\n // We track the current MQL + handler so we can fully detach in cleanup\n // (the handler is captured by the listener — passing a fresh closure to\n // removeEventListener wouldn't actually unregister it).\n let mql: MediaQueryList | null = null\n let mqlHandler: (() => void) | null = null\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return\n const dpr = window.devicePixelRatio\n const next = window.matchMedia(`(resolution: ${dpr}dppx)`)\n const handler = () => {\n emit()\n if (mql && mqlHandler) mql.removeEventListener('change', mqlHandler)\n setupDprWatch()\n }\n\n next.addEventListener('change', handler)\n mql = next\n mqlHandler = handler\n }\n\n setupDprWatch()\n\n return () => {\n observer.disconnect()\n if (mql && mqlHandler) mql.removeEventListener('change', mqlHandler)\n mql = null\n mqlHandler = null\n listeners.clear()\n setSignal(null)\n }\n }, [ctx])\n\n return signal ?? STUB_SIGNAL\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\nexport type ScrollValue = readonly [scrollY: number, progress: number]\n\nexport interface ScrollSignal {\n /** Current scroll Y (px) and normalized progress in [0,1]. */\n get(): ScrollValue\n on(event: 'change', cb: (value: ScrollValue) => void): () => void\n}\n\n// Inert stub returned during SSR + on the first client render before the\n// lifecycle effect attaches. Subscribing to it returns a no-op unsubscribe.\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n}\n\n/**\n * Track window scroll position. Exposes an AnimatableSignal of `[scrollY, progress]`\n * where `progress` is `scrollY / max(documentHeight - innerHeight, 1)` clamped\n * to [0, 1]. Listener is rAF-throttled and `passive: true` so it never blocks\n * scrolling.\n *\n * No v1 Tier 1 component consumes this hook; it ships so users can pass\n * `inputs={{ scroll: useScroll() }}` to any Matter component.\n *\n * Strict-Mode-safe: lifecycle is in one effect, so React 19's intentional\n * mount→unmount→mount cycle in dev creates a fresh listener pair per real\n * mount and tears down cleanly on each pseudo-unmount (CLAUDE.md gotcha #14).\n *\n * **Known limitation (v1):** `progress` is computed against whichever\n * `documentHeight` was current when the last scroll fired. If the page grows\n * after mount (async content, font load reflow, expanding panels) without\n * the user scrolling, the denominator goes stale. A future ResizeObserver/\n * MutationObserver pass would close the gap; deferred until a v1 component\n * consumes scroll input.\n */\nexport function useScroll(): ScrollSignal {\n const [signal, setSignal] = useState<ScrollSignal | null>(null)\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined\n\n const compute = (): ScrollValue => {\n const y = window.scrollY\n // For pages shorter than the viewport, `documentHeight - innerHeight` is\n // <= 0; clamp to 1 to avoid div-by-zero. Progress stays at 0 in that\n // case because scrollY is also 0.\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1)\n const progress = Math.max(0, Math.min(1, y / max))\n\n return [y, progress]\n }\n\n let value: ScrollValue = compute()\n const listeners = new Set<(v: ScrollValue) => void>()\n const fresh: ScrollSignal = {\n get: () => value,\n on: (_event, cb) => {\n listeners.add(cb)\n\n return () => {\n listeners.delete(cb)\n }\n },\n }\n\n setSignal(fresh)\n\n let rafPending = false\n const onScroll = () => {\n if (rafPending) return\n rafPending = true\n requestAnimationFrame(() => {\n rafPending = false\n const next = compute()\n\n if (next[0] === value[0] && next[1] === value[1]) return\n value = next\n for (const cb of listeners) cb(next)\n })\n }\n\n window.addEventListener('scroll', onScroll, { passive: true })\n\n return () => {\n window.removeEventListener('scroll', onScroll)\n listeners.clear()\n setSignal(null)\n }\n }, [])\n\n return signal ?? STUB_SIGNAL\n}\n","'use client'\n\nimport { useEffect, useMemo } from 'react'\nimport type { ShaderNodeObject } from 'three/tsl'\nimport { MeshBasicNodeMaterial } from 'three/webgpu'\nimport type { Node } from 'three/webgpu'\n\n/** A TSL fragment that produces a color. Accept any Node or TSL-wrapped node. */\nexport type ColorTSL = Node | ShaderNodeObject<Node>\n\n/**\n * Bind a TSL color expression to a NodeMaterial. Returns the material;\n * caller is responsible for adding it to a mesh and disposing when done.\n *\n * The TSL fragment is computed once via `useMemo` and re-applied if the\n * factory function changes. For dynamic uniforms, mutate `.value` on the\n * uniform nodes — don't recreate the TSL fragment per render.\n */\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const m = new MeshBasicNodeMaterial()\n\n m.colorNode = build()\n\n return m\n }, [build])\n\n useEffect(() => {\n return () => material.dispose()\n }, [material])\n\n return material\n}\n","'use client'\n\nimport { useEffect } from 'react'\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js'\n\n/**\n * Opt a component out of the rAF loop while it has no dynamic uniforms.\n *\n * When `hint` is true, the scheduler runs one final flush tick (so any\n * uniform changes since the last frame are rendered) and then halts the\n * rAF loop until either `hint` becomes false or another component in the\n * same scene calls `scheduler.requestRender()`.\n *\n * Use for components whose animation is fully derived from props that don't\n * include `time`, e.g. `<LinearGradient speed={0}>` with no `interactive`.\n */\nexport function useStaticHint(hint: boolean): void {\n const ctx = useShaderContext()\n\n useEffect(() => {\n if (!ctx) return\n ctx.scheduler.setIdle(hint)\n\n return () => ctx.scheduler.setIdle(false)\n }, [ctx, hint])\n}\n"],"mappings":"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","names":["useEffect","useState","jsx","useState","useEffect","useEffect","useRef","useState","jsx","jsxs","useRef","useState","useEffect","useEffect","useEffect","useState","useContext","useContext","useState","useEffect","useEffect","useEffect","useEffect","useState","STUB_SIGNAL","useState","useEffect","useEffect","useState","STUB_SIGNAL","useEffect","useMemo","useEffect","useEffect"]}