@lovo/matter-react 0.5.0 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @lovo/matter-react
2
2
 
3
+ ## 1.0.0
4
+
5
+ ## 0.6.0
6
+
3
7
  ## 0.5.0
4
8
 
5
9
  ### Minor Changes
@@ -51,9 +55,9 @@
51
55
 
52
56
  **Registry-side ships (delivered via `@lovo/matter-cli` copy-paste):**
53
57
 
54
- - `<FilmGrain>` — additive or subtractive grain overlay.
58
+ - `<Grain>` — additive or subtractive grain overlay.
55
59
  - `<Vignette>` — radial edge darkening, aspect-corrected so the mask is a circle on widescreen.
56
- - **Breaking:** `<MeshGradient>` no longer accepts `grain` / `grainSpeed` props. Stack `<FilmGrain />` as a sibling inside `<MatterScene>` instead. Existing copies pulled before this release keep working; new pulls / CLI refreshes pick up the new shape. The MeshGradient docs page has the new pattern.
60
+ - **Breaking:** `<MeshGradient>` no longer accepts `grain` / `grainSpeed` props. Stack `<Grain />` as a sibling inside `<MatterScene>` instead. Existing copies pulled before this release keep working; new pulls / CLI refreshes pick up the new shape. The MeshGradient docs page has the new pattern.
57
61
 
58
62
  ### Patch Changes
59
63
 
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  ShaderScene: () => ShaderScene,
26
26
  useAnimatableUniform: () => useAnimatableUniform,
27
27
  useCursor: () => useCursor,
28
+ useDisplayGamut: () => useDisplayGamut,
28
29
  usePostProcessPass: () => usePostProcessPass,
29
30
  useResize: () => useResize,
30
31
  useScroll: () => useScroll,
@@ -112,11 +113,44 @@ function ShaderMonitor({ anchor = "top-right" }) {
112
113
  }
113
114
 
114
115
  // src/components/shader-scene/shader-scene.tsx
115
- var import_react4 = require("react");
116
+ var import_react5 = require("react");
116
117
  var import_matter = require("@lovo/matter");
117
118
  var import_three = require("three");
118
119
  var import_tsl = require("three/tsl");
119
120
  var import_webgpu = require("three/webgpu");
121
+
122
+ // src/hooks/use-display-gamut/use-display-gamut.ts
123
+ var import_react4 = require("react");
124
+ var P3_QUERY = "(color-gamut: p3)";
125
+ function detectGamut() {
126
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
127
+ return "srgb";
128
+ }
129
+ return window.matchMedia(P3_QUERY).matches ? "p3" : "srgb";
130
+ }
131
+ function useDisplayGamut(preference) {
132
+ const [resolved, setResolved] = (0, import_react4.useState)(
133
+ () => preference === "auto" ? detectGamut() : preference
134
+ );
135
+ (0, import_react4.useEffect)(() => {
136
+ if (preference !== "auto") {
137
+ setResolved(preference);
138
+ return;
139
+ }
140
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
141
+ setResolved("srgb");
142
+ return;
143
+ }
144
+ const mediaQuery = window.matchMedia(P3_QUERY);
145
+ const update = () => setResolved(mediaQuery.matches ? "p3" : "srgb");
146
+ update();
147
+ mediaQuery.addEventListener("change", update);
148
+ return () => mediaQuery.removeEventListener("change", update);
149
+ }, [preference]);
150
+ return resolved;
151
+ }
152
+
153
+ // src/components/shader-scene/shader-scene.tsx
120
154
  var import_jsx_runtime3 = require("react/jsx-runtime");
121
155
  var defaultStyle = {
122
156
  position: "absolute",
@@ -126,18 +160,21 @@ var defaultStyle = {
126
160
  height: "100%"
127
161
  };
128
162
  function ShaderScene(props) {
129
- const { children, fallback, className, style, maxDPR } = props;
130
- const canvasRef = (0, import_react4.useRef)(null);
131
- const [shaderContext, setShaderContext] = (0, import_react4.useState)(null);
132
- const [error, setError] = (0, import_react4.useState)(null);
133
- (0, import_react4.useEffect)(() => {
163
+ const { children, fallback, className, style, maxDPR, gamut = "auto" } = props;
164
+ const resolvedGamut = useDisplayGamut(gamut);
165
+ const canvasRef = (0, import_react5.useRef)(null);
166
+ const [shaderContext, setShaderContext] = (0, import_react5.useState)(null);
167
+ const [error, setError] = (0, import_react5.useState)(null);
168
+ const [firstFramePainted, setFirstFramePainted] = (0, import_react5.useState)(false);
169
+ (0, import_react5.useEffect)(() => {
134
170
  const canvas = canvasRef.current;
135
171
  if (!canvas) return;
136
172
  let cancelled = false;
137
173
  let cleanup = null;
174
+ let firstPaintRaf = null;
138
175
  const setup = async () => {
139
176
  try {
140
- const renderer = await (0, import_matter.createRenderer)(canvas, { maxDPR });
177
+ const renderer = await (0, import_matter.createRenderer)(canvas, { maxDPR, gamut: resolvedGamut });
141
178
  if (cancelled) {
142
179
  renderer.dispose();
143
180
  return;
@@ -146,15 +183,16 @@ function ShaderScene(props) {
146
183
  const camera = new import_three.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
147
184
  camera.position.z = 1;
148
185
  const postProcessing = new import_webgpu.PostProcessing(renderer.three);
186
+ postProcessing.outputColorTransform = false;
149
187
  const scheduler = new import_matter.FrameScheduler();
150
188
  const overlays = /* @__PURE__ */ new Map();
151
- const basePass = (0, import_tsl.pass)(scene, camera);
189
+ const basePassNode = (0, import_tsl.vec4)((0, import_tsl.pass)(scene, camera));
152
190
  const rebuildOutputNode = () => {
153
- const basePassNode = basePass;
154
- postProcessing.outputNode = Array.from(overlays.values()).reduce(
191
+ const composed = Array.from(overlays.values()).reduce(
155
192
  (currentPipeline, transform) => transform(currentPipeline),
156
193
  basePassNode
157
194
  );
195
+ postProcessing.outputNode = (0, import_matter.dither)((0, import_tsl.renderOutput)(composed));
158
196
  postProcessing.needsUpdate = true;
159
197
  };
160
198
  rebuildOutputNode();
@@ -167,7 +205,18 @@ function ShaderScene(props) {
167
205
  rebuildOutputNode();
168
206
  };
169
207
  };
170
- scheduler.add(() => postProcessing.render());
208
+ let firstPaintSignaled = false;
209
+ const renderFrame = () => {
210
+ postProcessing.render();
211
+ if (!firstPaintSignaled && (scene.children.length > 0 || overlays.size > 0)) {
212
+ firstPaintSignaled = true;
213
+ firstPaintRaf = requestAnimationFrame(() => {
214
+ firstPaintRaf = null;
215
+ if (!cancelled) setFirstFramePainted(true);
216
+ });
217
+ }
218
+ };
219
+ scheduler.add(renderFrame);
171
220
  scheduler.start();
172
221
  const visibility = (0, import_matter.createVisibilityWatcher)();
173
222
  const intersection = (0, import_matter.createIntersectionWatcher)(canvas);
@@ -179,14 +228,14 @@ function ShaderScene(props) {
179
228
  updatePauseState();
180
229
  const unsubVisibility = visibility.subscribe(updatePauseState);
181
230
  const unsubIntersection = intersection.subscribe(updatePauseState);
182
- const onResize = () => renderer.resize();
183
- window.addEventListener("resize", onResize);
231
+ const resizeObserver = new ResizeObserver(() => renderer.resize());
232
+ resizeObserver.observe(canvas);
184
233
  cleanup = () => {
185
234
  unsubVisibility();
186
235
  unsubIntersection();
187
236
  visibility.dispose();
188
237
  intersection.dispose();
189
- window.removeEventListener("resize", onResize);
238
+ resizeObserver.disconnect();
190
239
  scheduler.dispose();
191
240
  renderer.dispose();
192
241
  };
@@ -201,11 +250,16 @@ function ShaderScene(props) {
201
250
  void setup();
202
251
  return () => {
203
252
  cancelled = true;
253
+ if (firstPaintRaf !== null) {
254
+ cancelAnimationFrame(firstPaintRaf);
255
+ firstPaintRaf = null;
256
+ }
204
257
  cleanup?.();
205
258
  cleanup = null;
206
259
  setShaderContext(null);
260
+ setFirstFramePainted(false);
207
261
  };
208
- }, [maxDPR]);
262
+ }, [maxDPR, resolvedGamut]);
209
263
  let content;
210
264
  if (error) {
211
265
  content = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -231,10 +285,11 @@ function ShaderScene(props) {
231
285
  ]
232
286
  }
233
287
  );
234
- } else if (shaderContext) {
235
- content = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShaderContext.Provider, { value: shaderContext, children });
236
288
  } else {
237
- content = fallback ?? null;
289
+ content = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
290
+ shaderContext && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShaderContext.Provider, { value: shaderContext, children }),
291
+ !firstFramePainted && (fallback ?? null)
292
+ ] });
238
293
  }
239
294
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: { ...defaultStyle, ...style }, children: [
240
295
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block" } }),
@@ -243,18 +298,18 @@ function ShaderScene(props) {
243
298
  }
244
299
 
245
300
  // src/hooks/use-animatable-uniform/use-animatable-uniform.ts
246
- var import_react5 = require("react");
301
+ var import_react6 = require("react");
247
302
  var import_tsl2 = require("three/tsl");
248
303
  var isSignal = (value) => {
249
304
  if (typeof value !== "object" || value === null) return false;
250
305
  return "get" in value && typeof value.get === "function" && "on" in value && typeof value.on === "function";
251
306
  };
252
307
  function useAnimatableUniform(value) {
253
- const uniformNode = (0, import_react5.useMemo)(() => {
308
+ const uniformNode = (0, import_react6.useMemo)(() => {
254
309
  const initial = isSignal(value) ? value.get() : value;
255
310
  return (0, import_tsl2.uniform)(initial);
256
311
  }, []);
257
- (0, import_react5.useEffect)(() => {
312
+ (0, import_react6.useEffect)(() => {
258
313
  if (isSignal(value)) {
259
314
  const unsub = value.on("change", (next) => {
260
315
  uniformNode.value = next;
@@ -268,13 +323,13 @@ function useAnimatableUniform(value) {
268
323
  }
269
324
 
270
325
  // src/hooks/use-cursor/use-cursor.ts
271
- var import_react7 = require("react");
326
+ var import_react8 = require("react");
272
327
  var import_matter2 = require("@lovo/matter");
273
328
 
274
329
  // src/hooks/use-shader-context/use-shader-context.ts
275
- var import_react6 = require("react");
330
+ var import_react7 = require("react");
276
331
  function useShaderContext() {
277
- return (0, import_react6.useContext)(ShaderContext);
332
+ return (0, import_react7.useContext)(ShaderContext);
278
333
  }
279
334
 
280
335
  // src/hooks/use-cursor/use-cursor.ts
@@ -284,8 +339,8 @@ var STUB_SIGNAL = {
284
339
  };
285
340
  function useCursor(opts = {}) {
286
341
  const shaderContext = useShaderContext();
287
- const [input, setInput] = (0, import_react7.useState)(null);
288
- (0, import_react7.useEffect)(() => {
342
+ const [input, setInput] = (0, import_react8.useState)(null);
343
+ (0, import_react8.useEffect)(() => {
289
344
  const canvas = shaderContext?.renderer.three.domElement;
290
345
  const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : void 0);
291
346
  const newCursorInput = new import_matter2.CursorInput({ ...opts, element: resolvedElement });
@@ -319,10 +374,10 @@ function useCursor(opts = {}) {
319
374
  }
320
375
 
321
376
  // src/hooks/use-overlay-pass/use-overlay-pass.ts
322
- var import_react8 = require("react");
377
+ var import_react9 = require("react");
323
378
  function usePostProcessPass(transform, deps) {
324
379
  const shaderContext = useShaderContext();
325
- (0, import_react8.useEffect)(() => {
380
+ (0, import_react9.useEffect)(() => {
326
381
  if (!shaderContext) return;
327
382
  const unregister = shaderContext.registerOverlay(transform);
328
383
  return unregister;
@@ -330,7 +385,7 @@ function usePostProcessPass(transform, deps) {
330
385
  }
331
386
 
332
387
  // src/hooks/use-resize/use-resize.ts
333
- var import_react9 = require("react");
388
+ var import_react10 = require("react");
334
389
 
335
390
  // src/internal/create-signal.ts
336
391
  function createSignal(getValue) {
@@ -356,8 +411,8 @@ var STUB_SIGNAL2 = {
356
411
  };
357
412
  function useResize() {
358
413
  const shaderContext = useShaderContext();
359
- const [signal, setSignal] = (0, import_react9.useState)(null);
360
- (0, import_react9.useEffect)(() => {
414
+ const [signal, setSignal] = (0, import_react10.useState)(null);
415
+ (0, import_react10.useEffect)(() => {
361
416
  if (!shaderContext) return void 0;
362
417
  const canvas = shaderContext.renderer.three.domElement;
363
418
  if (!(canvas instanceof HTMLCanvasElement)) return void 0;
@@ -411,14 +466,14 @@ function useResize() {
411
466
  }
412
467
 
413
468
  // src/hooks/use-scroll/use-scroll.ts
414
- var import_react10 = require("react");
469
+ var import_react11 = require("react");
415
470
  var STUB_SIGNAL3 = {
416
471
  get: () => [0, 0],
417
472
  on: () => () => void 0
418
473
  };
419
474
  function useScroll() {
420
- const [signal, setSignal] = (0, import_react10.useState)(null);
421
- (0, import_react10.useEffect)(() => {
475
+ const [signal, setSignal] = (0, import_react11.useState)(null);
476
+ (0, import_react11.useEffect)(() => {
422
477
  if (typeof window === "undefined") return void 0;
423
478
  const compute = () => {
424
479
  const scrollYPosition = window.scrollY;
@@ -452,25 +507,25 @@ function useScroll() {
452
507
  }
453
508
 
454
509
  // src/hooks/use-shader-material/use-shader-material.ts
455
- var import_react11 = require("react");
510
+ var import_react12 = require("react");
456
511
  var import_webgpu2 = require("three/webgpu");
457
512
  function useShaderMaterial(build) {
458
- const material = (0, import_react11.useMemo)(() => {
513
+ const material = (0, import_react12.useMemo)(() => {
459
514
  const nodeMaterial = new import_webgpu2.MeshBasicNodeMaterial();
460
515
  nodeMaterial.colorNode = build();
461
516
  return nodeMaterial;
462
517
  }, [build]);
463
- (0, import_react11.useEffect)(() => {
518
+ (0, import_react12.useEffect)(() => {
464
519
  return () => material.dispose();
465
520
  }, [material]);
466
521
  return material;
467
522
  }
468
523
 
469
524
  // src/hooks/use-static-hint/use-static-hint.ts
470
- var import_react12 = require("react");
525
+ var import_react13 = require("react");
471
526
  function useStaticSceneHint(isStatic) {
472
527
  const shaderContext = useShaderContext();
473
- (0, import_react12.useEffect)(() => {
528
+ (0, import_react13.useEffect)(() => {
474
529
  if (!shaderContext) return;
475
530
  return shaderContext.scheduler.setIdle(isStatic);
476
531
  }, [shaderContext, isStatic]);
@@ -482,6 +537,7 @@ function useStaticSceneHint(isStatic) {
482
537
  ShaderScene,
483
538
  useAnimatableUniform,
484
539
  useCursor,
540
+ useDisplayGamut,
485
541
  usePostProcessPass,
486
542
  useResize,
487
543
  useScroll,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../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/internal/create-signal.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":["// @lovo/matter-react — React binding for Matter.\n\nexport * from './components/index.js';\nexport * from './hooks/index.js';\n\nexport type { ShaderContextValue, PostProcessTransform } from './context/shader-context.js';\n","'use client';\n\nimport { type ReactNode, useEffect, useState } from 'react';\n\nexport interface FallbackBoundaryProps {\n fallback?: ReactNode;\n children: ReactNode;\n}\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 ShaderMonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nconst anchorStyle: Record<ShaderMonitorAnchor, 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?: ShaderMonitorAnchor;\n}\n\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const shaderContext = 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 (!shaderContext) return;\n const schedulerTickHandler = (tick: { now: number }) => {\n ticksRef.current += 1;\n const fpsAccumulator = fpsAccumRef.current;\n\n fpsAccumulator.frames += 1;\n if (fpsAccumulator.lastSampleAt === 0) fpsAccumulator.lastSampleAt = tick.now;\n const deltaTimeSinceLastSample = tick.now - fpsAccumulator.lastSampleAt;\n\n if (deltaTimeSinceLastSample >= 500) {\n fpsAccumulator.fps = Math.round((fpsAccumulator.frames * 1000) / deltaTimeSinceLastSample);\n fpsAccumulator.frames = 0;\n fpsAccumulator.lastSampleAt = tick.now;\n }\n setStats({ fps: fpsAccumulator.fps, ticks: ticksRef.current, frames: fpsAccumulator.frames });\n };\n\n shaderContext.scheduler.add(schedulerTickHandler);\n\n return () => shaderContext.scheduler.remove(schedulerTickHandler);\n }, [shaderContext]);\n\n if (!shaderContext) {\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 { createContext } from 'react';\n\nimport type { FrameScheduler, GpuRenderer } from '@lovo/matter';\nimport type { Camera, Scene } from 'three';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport type PostProcessTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>;\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer;\n scene: Scene;\n camera: Camera;\n scheduler: FrameScheduler;\n registerOverlay: (transform: PostProcessTransform) => () => void;\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null);\n","'use client';\n\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n FrameScheduler,\n} from '@lovo/matter';\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 PostProcessTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js';\n\nexport interface ShaderSceneProps {\n children?: ReactNode;\n fallback?: ReactNode;\n className?: string;\n style?: CSSProperties;\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\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR } = props;\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [shaderContext, setShaderContext] = 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, PostProcessTransform>();\n\n const basePass = pass(scene, camera);\n\n const rebuildOutputNode = () => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n const basePassNode = basePass as unknown as ShaderNodeObject<Node>;\n\n postProcessing.outputNode = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n postProcessing.needsUpdate = true;\n };\n\n rebuildOutputNode(); // initial: just basePass, no overlays\n\n const registerOverlay = (transform: PostProcessTransform): (() => 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 setShaderContext({ renderer, scene, camera, scheduler, registerOverlay });\n } catch (caughtError) {\n if (cancelled) return;\n const normalizedError =\n caughtError instanceof Error ? caughtError : new Error(String(caughtError));\n\n console.error('[ShaderScene] renderer init failed:', normalizedError);\n setError(normalizedError);\n }\n };\n\n void setup();\n\n return () => {\n cancelled = true;\n cleanup?.();\n cleanup = null;\n setShaderContext(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 (shaderContext) {\n content = <ShaderContext.Provider value={shaderContext}>{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';\n\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\nexport function useAnimatableUniform<T>(\n value: AnimatableProp<T>,\n): ShaderNodeObject<Node> & { value: T } {\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 // 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 { useEffect, useState } from 'react';\n\nimport { CursorInput, type CursorInputOptions, type Vector2 } from '@lovo/matter';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport interface CursorSignal {\n get(): Vector2;\n on(event: 'change', cb: (value: Vector2) => void): () => void;\n}\n\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n};\n\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const shaderContext = useShaderContext();\n const [input, setInput] = useState<CursorInput | null>(null);\n\n useEffect(() => {\n const canvas = shaderContext?.renderer.three.domElement;\n const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined);\n const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });\n\n setInput(newCursorInput);\n\n let detach: (() => void) | null = null;\n\n if (shaderContext?.scheduler) {\n const schedulerTickHandler = ({ delta }: { delta: number }) => newCursorInput.tick(delta);\n\n shaderContext.scheduler.add(schedulerTickHandler);\n detach = () => shaderContext.scheduler.remove(schedulerTickHandler);\n } else {\n let animationFrameId: number | null = null;\n let lastNow = performance.now();\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000;\n\n lastNow = now;\n newCursorInput.tick(delta);\n animationFrameId = requestAnimationFrame(loop);\n };\n\n animationFrameId = requestAnimationFrame(loop);\n detach = () => {\n if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);\n };\n }\n\n return () => {\n detach();\n newCursorInput.dispose();\n setInput(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext]);\n\n return input ?? STUB_SIGNAL;\n}\n","import { useContext } from 'react';\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js';\n\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext);\n}\n","'use client';\n\nimport { type DependencyList, useEffect } from 'react';\n\nimport type { PostProcessTransform } from '../../context/shader-context.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function usePostProcessPass(transform: PostProcessTransform, deps: DependencyList): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n const unregister = shaderContext.registerOverlay(transform);\n\n return unregister;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext, ...deps]);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\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 get(): ResizeValue;\n on(event: 'change', cb: (value: ResizeValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n};\n\nexport function useResize(): ResizeSignal {\n const shaderContext = useShaderContext();\n const [signal, setSignal] = useState<ResizeSignal | null>(null);\n\n useEffect(() => {\n if (!shaderContext) return undefined;\n\n const canvas = shaderContext.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 { signal: newSignal, listeners } = createSignal<ResizeValue>(() => value);\n\n setSignal(newSignal);\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 listener of listeners) listener(next);\n };\n\n const observer = new ResizeObserver(emit);\n\n observer.observe(canvas);\n\n let mediaQueryList: MediaQueryList | null = null;\n let mediaQueryListener: (() => void) | null = null;\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return;\n const dpr = window.devicePixelRatio;\n const nextMediaQueryList = window.matchMedia(`(resolution: ${dpr}dppx)`);\n const nextMediaQueryListener = () => {\n emit();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n setupDprWatch();\n };\n\n nextMediaQueryList.addEventListener('change', nextMediaQueryListener);\n mediaQueryList = nextMediaQueryList;\n mediaQueryListener = nextMediaQueryListener;\n };\n\n setupDprWatch();\n\n return () => {\n observer.disconnect();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n mediaQueryList = null;\n mediaQueryListener = null;\n listeners.clear();\n setSignal(null);\n };\n }, [shaderContext]);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nexport function createSignal<T>(getValue: () => T): {\n signal: { get(): T; on(event: string, listener: (v: T) => void): () => void };\n listeners: Set<(v: T) => void>;\n} {\n const listeners = new Set<(v: T) => void>();\n\n return {\n listeners,\n signal: {\n get: getValue,\n on: (_event, listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n },\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\n\nexport type ScrollValue = readonly [scrollY: number, progress: number];\n\nexport interface ScrollSignal {\n get(): ScrollValue;\n on(event: 'change', cb: (value: ScrollValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n};\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 scrollYPosition = window.scrollY;\n\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1);\n const progress = Math.max(0, Math.min(1, scrollYPosition / max));\n\n return [scrollYPosition, progress];\n };\n\n let value: ScrollValue = compute();\n const { signal: newSignal, listeners } = createSignal<ScrollValue>(() => value);\n\n setSignal(newSignal);\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 listener of listeners) listener(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';\n\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { MeshBasicNodeMaterial } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nexport type ColorTSL = Node | ShaderNodeObject<Node>;\n\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const nodeMaterial = new MeshBasicNodeMaterial();\n\n nodeMaterial.colorNode = build();\n\n return nodeMaterial;\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\nexport function useStaticSceneHint(isStatic: boolean): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n\n return shaderContext.scheduler.setIdle(isStatic);\n }, [shaderContext, isStatic]);\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_tsl","import_react","import_matter","import_react","import_react","import_react","STUB_SIGNAL","import_react","STUB_SIGNAL","import_react","import_webgpu","import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../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-display-gamut/use-display-gamut.ts","../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/internal/create-signal.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":["// @lovo/matter-react — React binding for Matter.\n\nexport * from './components/index.js';\nexport * from './hooks/index.js';\n\nexport type { ShaderContextValue, PostProcessTransform } from './context/shader-context.js';\n","'use client';\n\nimport { type ReactNode, useEffect, useState } from 'react';\n\nexport interface FallbackBoundaryProps {\n fallback?: ReactNode;\n children: ReactNode;\n}\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 ShaderMonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nconst anchorStyle: Record<ShaderMonitorAnchor, 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?: ShaderMonitorAnchor;\n}\n\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const shaderContext = 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 (!shaderContext) return;\n const schedulerTickHandler = (tick: { now: number }) => {\n ticksRef.current += 1;\n const fpsAccumulator = fpsAccumRef.current;\n\n fpsAccumulator.frames += 1;\n if (fpsAccumulator.lastSampleAt === 0) fpsAccumulator.lastSampleAt = tick.now;\n const deltaTimeSinceLastSample = tick.now - fpsAccumulator.lastSampleAt;\n\n if (deltaTimeSinceLastSample >= 500) {\n fpsAccumulator.fps = Math.round((fpsAccumulator.frames * 1000) / deltaTimeSinceLastSample);\n fpsAccumulator.frames = 0;\n fpsAccumulator.lastSampleAt = tick.now;\n }\n setStats({ fps: fpsAccumulator.fps, ticks: ticksRef.current, frames: fpsAccumulator.frames });\n };\n\n shaderContext.scheduler.add(schedulerTickHandler);\n\n return () => shaderContext.scheduler.remove(schedulerTickHandler);\n }, [shaderContext]);\n\n if (!shaderContext) {\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 { createContext } from 'react';\n\nimport type { FrameScheduler, GpuRenderer } from '@lovo/matter';\nimport type { Camera, Scene } from 'three';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport type PostProcessTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>;\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer;\n scene: Scene;\n camera: Camera;\n scheduler: FrameScheduler;\n registerOverlay: (transform: PostProcessTransform) => () => void;\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null);\n","'use client';\n\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n dither,\n FrameScheduler,\n} from '@lovo/matter';\nimport { OrthographicCamera, Scene } from 'three';\nimport { pass, renderOutput, vec4 } from 'three/tsl';\nimport { PostProcessing } from 'three/webgpu';\n\nimport {\n type PostProcessTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js';\nimport {\n type GamutPreference,\n useDisplayGamut,\n} from '../../hooks/use-display-gamut/use-display-gamut.js';\n\nexport interface ShaderSceneProps {\n children?: ReactNode;\n fallback?: ReactNode;\n className?: string;\n style?: CSSProperties;\n maxDPR?: number;\n /** Output color gamut. 'auto' (default) uses the widest the display supports. */\n gamut?: GamutPreference;\n}\n\nconst defaultStyle: CSSProperties = {\n position: 'absolute',\n inset: 0,\n display: 'block',\n width: '100%',\n height: '100%',\n};\n\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR, gamut = 'auto' } = props;\n const resolvedGamut = useDisplayGamut(gamut);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [shaderContext, setShaderContext] = useState<ShaderContextValue | null>(null);\n const [error, setError] = useState<Error | null>(null);\n // Stays false until the renderer has actually painted a frame containing the\n // shader. The fallback is held until then so there's no gap between dropping\n // the fallback and the first shader frame (which would otherwise flash the\n // canvas's clear state).\n const [firstFramePainted, setFirstFramePainted] = useState(false);\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 let firstPaintRaf: number | null = null;\n\n const setup = async () => {\n try {\n const renderer = await createRenderer(canvas, { maxDPR, gamut: resolvedGamut });\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\n // Take ownership of the output color transform so we can dither in\n // display-encoded space (see rebuildOutputNode below).\n postProcessing.outputColorTransform = false;\n const scheduler = new FrameScheduler();\n\n const overlays = new Map<symbol, PostProcessTransform>();\n\n const basePassNode = vec4(pass(scene, camera));\n\n const rebuildOutputNode = () => {\n // Overlays (Grain, Vignette, ...) compose in linear working space.\n const composed = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n\n // renderOutput applies tone mapping + the working->output color-space\n // transfer (it reads both from the context three sets because\n // outputColorTransform is false), so dither runs last, in\n // display-encoded space, right before 8-bit quantization. This breaks\n // up gradient banding uniformly across every component in the scene.\n postProcessing.outputNode = dither(renderOutput(composed));\n postProcessing.needsUpdate = true;\n };\n\n rebuildOutputNode(); // initial: just basePass, no overlays\n\n const registerOverlay = (transform: PostProcessTransform): (() => 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 // Signal \"first paint\" only once the scene actually has something to\n // draw (a base shader mesh, or at least an overlay pass) — the scheduler\n // renders empty frames before the child shader mounts its mesh, and we\n // don't want to drop the fallback over an empty canvas. Defer the state\n // flip by one rAF so the just-submitted frame composites before the\n // fallback is removed.\n let firstPaintSignaled = false;\n const renderFrame = () => {\n postProcessing.render();\n\n if (!firstPaintSignaled && (scene.children.length > 0 || overlays.size > 0)) {\n firstPaintSignaled = true;\n firstPaintRaf = requestAnimationFrame(() => {\n firstPaintRaf = null;\n if (!cancelled) setFirstFramePainted(true);\n });\n }\n };\n\n scheduler.add(renderFrame);\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 // Track the canvas's actual box size, not just window 'resize'. The\n // canvas commonly gets its real size from layout AFTER renderer init\n // (with no window resize firing), which would otherwise leave the\n // renderer stuck at the default 300x150 and render the scene into an\n // undersized target — compressing every shader's output. ResizeObserver\n // fires once on observe() and on every subsequent box change.\n const resizeObserver = new ResizeObserver(() => renderer.resize());\n\n resizeObserver.observe(canvas);\n\n cleanup = () => {\n unsubVisibility();\n unsubIntersection();\n visibility.dispose();\n intersection.dispose();\n resizeObserver.disconnect();\n scheduler.dispose();\n renderer.dispose();\n };\n\n setShaderContext({ renderer, scene, camera, scheduler, registerOverlay });\n } catch (caughtError) {\n if (cancelled) return;\n const normalizedError =\n caughtError instanceof Error ? caughtError : new Error(String(caughtError));\n\n console.error('[ShaderScene] renderer init failed:', normalizedError);\n setError(normalizedError);\n }\n };\n\n void setup();\n\n return () => {\n cancelled = true;\n if (firstPaintRaf !== null) {\n cancelAnimationFrame(firstPaintRaf);\n firstPaintRaf = null;\n }\n cleanup?.();\n cleanup = null;\n setShaderContext(null);\n // A fresh renderer (e.g. on gamut change) must re-prove its first paint,\n // so show the fallback again until it does.\n setFirstFramePainted(false);\n };\n }, [maxDPR, resolvedGamut]);\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 {\n // Mount the children as soon as the context exists so the shader can build\n // and paint, but keep the fallback overlaid on top until that first frame\n // lands. The children render no visible DOM of their own (they drive the\n // canvas), so the fallback sits above the canvas and is removed only once\n // the shader is actually on screen.\n content = (\n <>\n {shaderContext && (\n <ShaderContext.Provider value={shaderContext}>{children}</ShaderContext.Provider>\n )}\n {!firstFramePainted && (fallback ?? null)}\n </>\n );\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","import { useEffect, useState } from 'react';\n\nimport type { OutputGamut } from '@lovo/matter';\n\n/** What the consumer asks for: a fixed gamut, or 'auto' to detect the display. */\nexport type GamutPreference = 'auto' | OutputGamut;\n\nconst P3_QUERY = '(color-gamut: p3)';\n\nfunction detectGamut(): OutputGamut {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return 'srgb';\n }\n\n return window.matchMedia(P3_QUERY).matches ? 'p3' : 'srgb';\n}\n\n/**\n * Resolve a gamut preference to a concrete output gamut. Explicit 'srgb'/'p3'\n * pass through untouched; 'auto' queries `(color-gamut: p3)` and re-resolves\n * when the display capability changes (e.g. window dragged to another monitor).\n */\nexport function useDisplayGamut(preference: GamutPreference): OutputGamut {\n const [resolved, setResolved] = useState<OutputGamut>(() =>\n preference === 'auto' ? detectGamut() : preference,\n );\n\n useEffect(() => {\n if (preference !== 'auto') {\n setResolved(preference);\n\n return;\n }\n\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n setResolved('srgb');\n\n return;\n }\n\n const mediaQuery = window.matchMedia(P3_QUERY);\n const update = () => setResolved(mediaQuery.matches ? 'p3' : 'srgb');\n\n update();\n mediaQuery.addEventListener('change', update);\n\n return () => mediaQuery.removeEventListener('change', update);\n }, [preference]);\n\n return resolved;\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport { uniform } from 'three/tsl';\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\nexport function useAnimatableUniform<T>(value: AnimatableProp<T>): ReturnType<typeof uniform<T>> {\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 return uniformNode;\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { CursorInput, type CursorInputOptions, type Vector2 } from '@lovo/matter';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport interface CursorSignal {\n get(): Vector2;\n on(event: 'change', cb: (value: Vector2) => void): () => void;\n}\n\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n};\n\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const shaderContext = useShaderContext();\n const [input, setInput] = useState<CursorInput | null>(null);\n\n useEffect(() => {\n const canvas = shaderContext?.renderer.three.domElement;\n const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined);\n const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });\n\n setInput(newCursorInput);\n\n let detach: (() => void) | null = null;\n\n if (shaderContext?.scheduler) {\n const schedulerTickHandler = ({ delta }: { delta: number }) => newCursorInput.tick(delta);\n\n shaderContext.scheduler.add(schedulerTickHandler);\n detach = () => shaderContext.scheduler.remove(schedulerTickHandler);\n } else {\n let animationFrameId: number | null = null;\n let lastNow = performance.now();\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000;\n\n lastNow = now;\n newCursorInput.tick(delta);\n animationFrameId = requestAnimationFrame(loop);\n };\n\n animationFrameId = requestAnimationFrame(loop);\n detach = () => {\n if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);\n };\n }\n\n return () => {\n detach();\n newCursorInput.dispose();\n setInput(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext]);\n\n return input ?? STUB_SIGNAL;\n}\n","import { useContext } from 'react';\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js';\n\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext);\n}\n","'use client';\n\nimport { type DependencyList, useEffect } from 'react';\n\nimport type { PostProcessTransform } from '../../context/shader-context.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function usePostProcessPass(transform: PostProcessTransform, deps: DependencyList): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n const unregister = shaderContext.registerOverlay(transform);\n\n return unregister;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext, ...deps]);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\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 get(): ResizeValue;\n on(event: 'change', cb: (value: ResizeValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n};\n\nexport function useResize(): ResizeSignal {\n const shaderContext = useShaderContext();\n const [signal, setSignal] = useState<ResizeSignal | null>(null);\n\n useEffect(() => {\n if (!shaderContext) return undefined;\n\n const canvas = shaderContext.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 { signal: newSignal, listeners } = createSignal<ResizeValue>(() => value);\n\n setSignal(newSignal);\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 listener of listeners) listener(next);\n };\n\n const observer = new ResizeObserver(emit);\n\n observer.observe(canvas);\n\n let mediaQueryList: MediaQueryList | null = null;\n let mediaQueryListener: (() => void) | null = null;\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return;\n const dpr = window.devicePixelRatio;\n const nextMediaQueryList = window.matchMedia(`(resolution: ${dpr}dppx)`);\n const nextMediaQueryListener = () => {\n emit();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n setupDprWatch();\n };\n\n nextMediaQueryList.addEventListener('change', nextMediaQueryListener);\n mediaQueryList = nextMediaQueryList;\n mediaQueryListener = nextMediaQueryListener;\n };\n\n setupDprWatch();\n\n return () => {\n observer.disconnect();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n mediaQueryList = null;\n mediaQueryListener = null;\n listeners.clear();\n setSignal(null);\n };\n }, [shaderContext]);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nexport function createSignal<T>(getValue: () => T): {\n signal: { get(): T; on(event: string, listener: (v: T) => void): () => void };\n listeners: Set<(v: T) => void>;\n} {\n const listeners = new Set<(v: T) => void>();\n\n return {\n listeners,\n signal: {\n get: getValue,\n on: (_event, listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n },\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\n\nexport type ScrollValue = readonly [scrollY: number, progress: number];\n\nexport interface ScrollSignal {\n get(): ScrollValue;\n on(event: 'change', cb: (value: ScrollValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n};\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 scrollYPosition = window.scrollY;\n\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1);\n const progress = Math.max(0, Math.min(1, scrollYPosition / max));\n\n return [scrollYPosition, progress];\n };\n\n let value: ScrollValue = compute();\n const { signal: newSignal, listeners } = createSignal<ScrollValue>(() => value);\n\n setSignal(newSignal);\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 listener of listeners) listener(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';\n\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { MeshBasicNodeMaterial } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nexport type ColorTSL = Node | ShaderNodeObject<Node>;\n\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const nodeMaterial = new MeshBasicNodeMaterial();\n\n nodeMaterial.colorNode = build();\n\n return nodeMaterial;\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\nexport function useStaticSceneHint(isStatic: boolean): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n\n return shaderContext.scheduler.setIdle(isStatic);\n }, [shaderContext, isStatic]);\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","import_react","import_react","import_jsx_runtime","import_react","import_tsl","import_react","import_matter","import_react","import_react","import_react","STUB_SIGNAL","import_react","STUB_SIGNAL","import_react","import_webgpu","import_react"]}
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, CSSProperties, DependencyList } from 'react';
3
- import { ShaderNodeObject } from 'three/tsl';
4
- import { Node, MeshBasicNodeMaterial } from 'three/webgpu';
5
- import { Vector2, CursorInputOptions, GpuRenderer, FrameScheduler } from '@lovo/matter';
3
+ import { OutputGamut, Vector2, CursorInputOptions, GpuRenderer, FrameScheduler } from '@lovo/matter';
4
+ import { uniform, ShaderNodeObject } from 'three/tsl';
6
5
  import { Scene, Camera } from 'three';
6
+ import { Node, MeshBasicNodeMaterial } from 'three/webgpu';
7
7
 
8
8
  interface FallbackBoundaryProps {
9
9
  fallback?: ReactNode;
@@ -17,12 +17,23 @@ interface ShaderMonitorProps {
17
17
  }
18
18
  declare function ShaderMonitor({ anchor }: ShaderMonitorProps): react_jsx_runtime.JSX.Element;
19
19
 
20
+ /** What the consumer asks for: a fixed gamut, or 'auto' to detect the display. */
21
+ type GamutPreference = 'auto' | OutputGamut;
22
+ /**
23
+ * Resolve a gamut preference to a concrete output gamut. Explicit 'srgb'/'p3'
24
+ * pass through untouched; 'auto' queries `(color-gamut: p3)` and re-resolves
25
+ * when the display capability changes (e.g. window dragged to another monitor).
26
+ */
27
+ declare function useDisplayGamut(preference: GamutPreference): OutputGamut;
28
+
20
29
  interface ShaderSceneProps {
21
30
  children?: ReactNode;
22
31
  fallback?: ReactNode;
23
32
  className?: string;
24
33
  style?: CSSProperties;
25
34
  maxDPR?: number;
35
+ /** Output color gamut. 'auto' (default) uses the widest the display supports. */
36
+ gamut?: GamutPreference;
26
37
  }
27
38
  declare function ShaderScene(props: ShaderSceneProps): react_jsx_runtime.JSX.Element;
28
39
 
@@ -31,9 +42,7 @@ interface AnimatableSignal<T> {
31
42
  on(event: 'change', cb: (value: T) => void): () => void;
32
43
  }
33
44
  type AnimatableProp<T> = T | AnimatableSignal<T>;
34
- declare function useAnimatableUniform<T>(value: AnimatableProp<T>): ShaderNodeObject<Node> & {
35
- value: T;
36
- };
45
+ declare function useAnimatableUniform<T>(value: AnimatableProp<T>): ReturnType<typeof uniform<T>>;
37
46
 
38
47
  interface CursorSignal {
39
48
  get(): Vector2;
@@ -73,4 +82,4 @@ declare function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial
73
82
 
74
83
  declare function useStaticSceneHint(isStatic: boolean): void;
75
84
 
76
- export { type AnimatableProp, type AnimatableSignal, type CursorSignal, FallbackBoundary, type FallbackBoundaryProps, type PostProcessTransform, type ResizeSignal, type ResizeValue, type ScrollSignal, type ScrollValue, type ShaderContextValue, ShaderMonitor, type ShaderMonitorAnchor, type ShaderMonitorProps, ShaderScene, type ShaderSceneProps, useAnimatableUniform, useCursor, usePostProcessPass, useResize, useScroll, useShaderContext, useShaderMaterial, useStaticSceneHint };
85
+ export { type AnimatableProp, type AnimatableSignal, type CursorSignal, FallbackBoundary, type FallbackBoundaryProps, type GamutPreference, type PostProcessTransform, type ResizeSignal, type ResizeValue, type ScrollSignal, type ScrollValue, type ShaderContextValue, ShaderMonitor, type ShaderMonitorAnchor, type ShaderMonitorProps, ShaderScene, type ShaderSceneProps, useAnimatableUniform, useCursor, useDisplayGamut, usePostProcessPass, useResize, useScroll, useShaderContext, useShaderMaterial, useStaticSceneHint };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, CSSProperties, DependencyList } from 'react';
3
- import { ShaderNodeObject } from 'three/tsl';
4
- import { Node, MeshBasicNodeMaterial } from 'three/webgpu';
5
- import { Vector2, CursorInputOptions, GpuRenderer, FrameScheduler } from '@lovo/matter';
3
+ import { OutputGamut, Vector2, CursorInputOptions, GpuRenderer, FrameScheduler } from '@lovo/matter';
4
+ import { uniform, ShaderNodeObject } from 'three/tsl';
6
5
  import { Scene, Camera } from 'three';
6
+ import { Node, MeshBasicNodeMaterial } from 'three/webgpu';
7
7
 
8
8
  interface FallbackBoundaryProps {
9
9
  fallback?: ReactNode;
@@ -17,12 +17,23 @@ interface ShaderMonitorProps {
17
17
  }
18
18
  declare function ShaderMonitor({ anchor }: ShaderMonitorProps): react_jsx_runtime.JSX.Element;
19
19
 
20
+ /** What the consumer asks for: a fixed gamut, or 'auto' to detect the display. */
21
+ type GamutPreference = 'auto' | OutputGamut;
22
+ /**
23
+ * Resolve a gamut preference to a concrete output gamut. Explicit 'srgb'/'p3'
24
+ * pass through untouched; 'auto' queries `(color-gamut: p3)` and re-resolves
25
+ * when the display capability changes (e.g. window dragged to another monitor).
26
+ */
27
+ declare function useDisplayGamut(preference: GamutPreference): OutputGamut;
28
+
20
29
  interface ShaderSceneProps {
21
30
  children?: ReactNode;
22
31
  fallback?: ReactNode;
23
32
  className?: string;
24
33
  style?: CSSProperties;
25
34
  maxDPR?: number;
35
+ /** Output color gamut. 'auto' (default) uses the widest the display supports. */
36
+ gamut?: GamutPreference;
26
37
  }
27
38
  declare function ShaderScene(props: ShaderSceneProps): react_jsx_runtime.JSX.Element;
28
39
 
@@ -31,9 +42,7 @@ interface AnimatableSignal<T> {
31
42
  on(event: 'change', cb: (value: T) => void): () => void;
32
43
  }
33
44
  type AnimatableProp<T> = T | AnimatableSignal<T>;
34
- declare function useAnimatableUniform<T>(value: AnimatableProp<T>): ShaderNodeObject<Node> & {
35
- value: T;
36
- };
45
+ declare function useAnimatableUniform<T>(value: AnimatableProp<T>): ReturnType<typeof uniform<T>>;
37
46
 
38
47
  interface CursorSignal {
39
48
  get(): Vector2;
@@ -73,4 +82,4 @@ declare function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial
73
82
 
74
83
  declare function useStaticSceneHint(isStatic: boolean): void;
75
84
 
76
- export { type AnimatableProp, type AnimatableSignal, type CursorSignal, FallbackBoundary, type FallbackBoundaryProps, type PostProcessTransform, type ResizeSignal, type ResizeValue, type ScrollSignal, type ScrollValue, type ShaderContextValue, ShaderMonitor, type ShaderMonitorAnchor, type ShaderMonitorProps, ShaderScene, type ShaderSceneProps, useAnimatableUniform, useCursor, usePostProcessPass, useResize, useScroll, useShaderContext, useShaderMaterial, useStaticSceneHint };
85
+ export { type AnimatableProp, type AnimatableSignal, type CursorSignal, FallbackBoundary, type FallbackBoundaryProps, type GamutPreference, type PostProcessTransform, type ResizeSignal, type ResizeValue, type ScrollSignal, type ScrollValue, type ShaderContextValue, ShaderMonitor, type ShaderMonitorAnchor, type ShaderMonitorProps, ShaderScene, type ShaderSceneProps, useAnimatableUniform, useCursor, useDisplayGamut, usePostProcessPass, useResize, useScroll, useShaderContext, useShaderMaterial, useStaticSceneHint };
package/dist/index.js CHANGED
@@ -76,17 +76,51 @@ function ShaderMonitor({ anchor = "top-right" }) {
76
76
  }
77
77
 
78
78
  // src/components/shader-scene/shader-scene.tsx
79
- import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
79
+ import { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
80
80
  import {
81
81
  createIntersectionWatcher,
82
82
  createRenderer,
83
83
  createVisibilityWatcher,
84
+ dither,
84
85
  FrameScheduler
85
86
  } from "@lovo/matter";
86
87
  import { OrthographicCamera, Scene } from "three";
87
- import { pass } from "three/tsl";
88
+ import { pass, renderOutput, vec4 } from "three/tsl";
88
89
  import { PostProcessing } from "three/webgpu";
89
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
90
+
91
+ // src/hooks/use-display-gamut/use-display-gamut.ts
92
+ import { useEffect as useEffect3, useState as useState3 } from "react";
93
+ var P3_QUERY = "(color-gamut: p3)";
94
+ function detectGamut() {
95
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
96
+ return "srgb";
97
+ }
98
+ return window.matchMedia(P3_QUERY).matches ? "p3" : "srgb";
99
+ }
100
+ function useDisplayGamut(preference) {
101
+ const [resolved, setResolved] = useState3(
102
+ () => preference === "auto" ? detectGamut() : preference
103
+ );
104
+ useEffect3(() => {
105
+ if (preference !== "auto") {
106
+ setResolved(preference);
107
+ return;
108
+ }
109
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
110
+ setResolved("srgb");
111
+ return;
112
+ }
113
+ const mediaQuery = window.matchMedia(P3_QUERY);
114
+ const update = () => setResolved(mediaQuery.matches ? "p3" : "srgb");
115
+ update();
116
+ mediaQuery.addEventListener("change", update);
117
+ return () => mediaQuery.removeEventListener("change", update);
118
+ }, [preference]);
119
+ return resolved;
120
+ }
121
+
122
+ // src/components/shader-scene/shader-scene.tsx
123
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
90
124
  var defaultStyle = {
91
125
  position: "absolute",
92
126
  inset: 0,
@@ -95,18 +129,21 @@ var defaultStyle = {
95
129
  height: "100%"
96
130
  };
97
131
  function ShaderScene(props) {
98
- const { children, fallback, className, style, maxDPR } = props;
132
+ const { children, fallback, className, style, maxDPR, gamut = "auto" } = props;
133
+ const resolvedGamut = useDisplayGamut(gamut);
99
134
  const canvasRef = useRef2(null);
100
- const [shaderContext, setShaderContext] = useState3(null);
101
- const [error, setError] = useState3(null);
102
- useEffect3(() => {
135
+ const [shaderContext, setShaderContext] = useState4(null);
136
+ const [error, setError] = useState4(null);
137
+ const [firstFramePainted, setFirstFramePainted] = useState4(false);
138
+ useEffect4(() => {
103
139
  const canvas = canvasRef.current;
104
140
  if (!canvas) return;
105
141
  let cancelled = false;
106
142
  let cleanup = null;
143
+ let firstPaintRaf = null;
107
144
  const setup = async () => {
108
145
  try {
109
- const renderer = await createRenderer(canvas, { maxDPR });
146
+ const renderer = await createRenderer(canvas, { maxDPR, gamut: resolvedGamut });
110
147
  if (cancelled) {
111
148
  renderer.dispose();
112
149
  return;
@@ -115,15 +152,16 @@ function ShaderScene(props) {
115
152
  const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
116
153
  camera.position.z = 1;
117
154
  const postProcessing = new PostProcessing(renderer.three);
155
+ postProcessing.outputColorTransform = false;
118
156
  const scheduler = new FrameScheduler();
119
157
  const overlays = /* @__PURE__ */ new Map();
120
- const basePass = pass(scene, camera);
158
+ const basePassNode = vec4(pass(scene, camera));
121
159
  const rebuildOutputNode = () => {
122
- const basePassNode = basePass;
123
- postProcessing.outputNode = Array.from(overlays.values()).reduce(
160
+ const composed = Array.from(overlays.values()).reduce(
124
161
  (currentPipeline, transform) => transform(currentPipeline),
125
162
  basePassNode
126
163
  );
164
+ postProcessing.outputNode = dither(renderOutput(composed));
127
165
  postProcessing.needsUpdate = true;
128
166
  };
129
167
  rebuildOutputNode();
@@ -136,7 +174,18 @@ function ShaderScene(props) {
136
174
  rebuildOutputNode();
137
175
  };
138
176
  };
139
- scheduler.add(() => postProcessing.render());
177
+ let firstPaintSignaled = false;
178
+ const renderFrame = () => {
179
+ postProcessing.render();
180
+ if (!firstPaintSignaled && (scene.children.length > 0 || overlays.size > 0)) {
181
+ firstPaintSignaled = true;
182
+ firstPaintRaf = requestAnimationFrame(() => {
183
+ firstPaintRaf = null;
184
+ if (!cancelled) setFirstFramePainted(true);
185
+ });
186
+ }
187
+ };
188
+ scheduler.add(renderFrame);
140
189
  scheduler.start();
141
190
  const visibility = createVisibilityWatcher();
142
191
  const intersection = createIntersectionWatcher(canvas);
@@ -148,14 +197,14 @@ function ShaderScene(props) {
148
197
  updatePauseState();
149
198
  const unsubVisibility = visibility.subscribe(updatePauseState);
150
199
  const unsubIntersection = intersection.subscribe(updatePauseState);
151
- const onResize = () => renderer.resize();
152
- window.addEventListener("resize", onResize);
200
+ const resizeObserver = new ResizeObserver(() => renderer.resize());
201
+ resizeObserver.observe(canvas);
153
202
  cleanup = () => {
154
203
  unsubVisibility();
155
204
  unsubIntersection();
156
205
  visibility.dispose();
157
206
  intersection.dispose();
158
- window.removeEventListener("resize", onResize);
207
+ resizeObserver.disconnect();
159
208
  scheduler.dispose();
160
209
  renderer.dispose();
161
210
  };
@@ -170,11 +219,16 @@ function ShaderScene(props) {
170
219
  void setup();
171
220
  return () => {
172
221
  cancelled = true;
222
+ if (firstPaintRaf !== null) {
223
+ cancelAnimationFrame(firstPaintRaf);
224
+ firstPaintRaf = null;
225
+ }
173
226
  cleanup?.();
174
227
  cleanup = null;
175
228
  setShaderContext(null);
229
+ setFirstFramePainted(false);
176
230
  };
177
- }, [maxDPR]);
231
+ }, [maxDPR, resolvedGamut]);
178
232
  let content;
179
233
  if (error) {
180
234
  content = /* @__PURE__ */ jsxs2(
@@ -200,10 +254,11 @@ function ShaderScene(props) {
200
254
  ]
201
255
  }
202
256
  );
203
- } else if (shaderContext) {
204
- content = /* @__PURE__ */ jsx3(ShaderContext.Provider, { value: shaderContext, children });
205
257
  } else {
206
- content = fallback ?? null;
258
+ content = /* @__PURE__ */ jsxs2(Fragment2, { children: [
259
+ shaderContext && /* @__PURE__ */ jsx3(ShaderContext.Provider, { value: shaderContext, children }),
260
+ !firstFramePainted && (fallback ?? null)
261
+ ] });
207
262
  }
208
263
  return /* @__PURE__ */ jsxs2("div", { className, style: { ...defaultStyle, ...style }, children: [
209
264
  /* @__PURE__ */ jsx3("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block" } }),
@@ -212,7 +267,7 @@ function ShaderScene(props) {
212
267
  }
213
268
 
214
269
  // src/hooks/use-animatable-uniform/use-animatable-uniform.ts
215
- import { useEffect as useEffect4, useMemo } from "react";
270
+ import { useEffect as useEffect5, useMemo } from "react";
216
271
  import { uniform } from "three/tsl";
217
272
  var isSignal = (value) => {
218
273
  if (typeof value !== "object" || value === null) return false;
@@ -223,7 +278,7 @@ function useAnimatableUniform(value) {
223
278
  const initial = isSignal(value) ? value.get() : value;
224
279
  return uniform(initial);
225
280
  }, []);
226
- useEffect4(() => {
281
+ useEffect5(() => {
227
282
  if (isSignal(value)) {
228
283
  const unsub = value.on("change", (next) => {
229
284
  uniformNode.value = next;
@@ -237,7 +292,7 @@ function useAnimatableUniform(value) {
237
292
  }
238
293
 
239
294
  // src/hooks/use-cursor/use-cursor.ts
240
- import { useEffect as useEffect5, useState as useState4 } from "react";
295
+ import { useEffect as useEffect6, useState as useState5 } from "react";
241
296
  import { CursorInput } from "@lovo/matter";
242
297
 
243
298
  // src/hooks/use-shader-context/use-shader-context.ts
@@ -253,8 +308,8 @@ var STUB_SIGNAL = {
253
308
  };
254
309
  function useCursor(opts = {}) {
255
310
  const shaderContext = useShaderContext();
256
- const [input, setInput] = useState4(null);
257
- useEffect5(() => {
311
+ const [input, setInput] = useState5(null);
312
+ useEffect6(() => {
258
313
  const canvas = shaderContext?.renderer.three.domElement;
259
314
  const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : void 0);
260
315
  const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });
@@ -288,10 +343,10 @@ function useCursor(opts = {}) {
288
343
  }
289
344
 
290
345
  // src/hooks/use-overlay-pass/use-overlay-pass.ts
291
- import { useEffect as useEffect6 } from "react";
346
+ import { useEffect as useEffect7 } from "react";
292
347
  function usePostProcessPass(transform, deps) {
293
348
  const shaderContext = useShaderContext();
294
- useEffect6(() => {
349
+ useEffect7(() => {
295
350
  if (!shaderContext) return;
296
351
  const unregister = shaderContext.registerOverlay(transform);
297
352
  return unregister;
@@ -299,7 +354,7 @@ function usePostProcessPass(transform, deps) {
299
354
  }
300
355
 
301
356
  // src/hooks/use-resize/use-resize.ts
302
- import { useEffect as useEffect7, useState as useState5 } from "react";
357
+ import { useEffect as useEffect8, useState as useState6 } from "react";
303
358
 
304
359
  // src/internal/create-signal.ts
305
360
  function createSignal(getValue) {
@@ -325,8 +380,8 @@ var STUB_SIGNAL2 = {
325
380
  };
326
381
  function useResize() {
327
382
  const shaderContext = useShaderContext();
328
- const [signal, setSignal] = useState5(null);
329
- useEffect7(() => {
383
+ const [signal, setSignal] = useState6(null);
384
+ useEffect8(() => {
330
385
  if (!shaderContext) return void 0;
331
386
  const canvas = shaderContext.renderer.three.domElement;
332
387
  if (!(canvas instanceof HTMLCanvasElement)) return void 0;
@@ -380,14 +435,14 @@ function useResize() {
380
435
  }
381
436
 
382
437
  // src/hooks/use-scroll/use-scroll.ts
383
- import { useEffect as useEffect8, useState as useState6 } from "react";
438
+ import { useEffect as useEffect9, useState as useState7 } from "react";
384
439
  var STUB_SIGNAL3 = {
385
440
  get: () => [0, 0],
386
441
  on: () => () => void 0
387
442
  };
388
443
  function useScroll() {
389
- const [signal, setSignal] = useState6(null);
390
- useEffect8(() => {
444
+ const [signal, setSignal] = useState7(null);
445
+ useEffect9(() => {
391
446
  if (typeof window === "undefined") return void 0;
392
447
  const compute = () => {
393
448
  const scrollYPosition = window.scrollY;
@@ -421,7 +476,7 @@ function useScroll() {
421
476
  }
422
477
 
423
478
  // src/hooks/use-shader-material/use-shader-material.ts
424
- import { useEffect as useEffect9, useMemo as useMemo2 } from "react";
479
+ import { useEffect as useEffect10, useMemo as useMemo2 } from "react";
425
480
  import { MeshBasicNodeMaterial } from "three/webgpu";
426
481
  function useShaderMaterial(build) {
427
482
  const material = useMemo2(() => {
@@ -429,17 +484,17 @@ function useShaderMaterial(build) {
429
484
  nodeMaterial.colorNode = build();
430
485
  return nodeMaterial;
431
486
  }, [build]);
432
- useEffect9(() => {
487
+ useEffect10(() => {
433
488
  return () => material.dispose();
434
489
  }, [material]);
435
490
  return material;
436
491
  }
437
492
 
438
493
  // src/hooks/use-static-hint/use-static-hint.ts
439
- import { useEffect as useEffect10 } from "react";
494
+ import { useEffect as useEffect11 } from "react";
440
495
  function useStaticSceneHint(isStatic) {
441
496
  const shaderContext = useShaderContext();
442
- useEffect10(() => {
497
+ useEffect11(() => {
443
498
  if (!shaderContext) return;
444
499
  return shaderContext.scheduler.setIdle(isStatic);
445
500
  }, [shaderContext, isStatic]);
@@ -450,6 +505,7 @@ export {
450
505
  ShaderScene,
451
506
  useAnimatableUniform,
452
507
  useCursor,
508
+ useDisplayGamut,
453
509
  usePostProcessPass,
454
510
  useResize,
455
511
  useScroll,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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/internal/create-signal.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 fallback?: ReactNode;\n children: ReactNode;\n}\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 ShaderMonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nconst anchorStyle: Record<ShaderMonitorAnchor, 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?: ShaderMonitorAnchor;\n}\n\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const shaderContext = 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 (!shaderContext) return;\n const schedulerTickHandler = (tick: { now: number }) => {\n ticksRef.current += 1;\n const fpsAccumulator = fpsAccumRef.current;\n\n fpsAccumulator.frames += 1;\n if (fpsAccumulator.lastSampleAt === 0) fpsAccumulator.lastSampleAt = tick.now;\n const deltaTimeSinceLastSample = tick.now - fpsAccumulator.lastSampleAt;\n\n if (deltaTimeSinceLastSample >= 500) {\n fpsAccumulator.fps = Math.round((fpsAccumulator.frames * 1000) / deltaTimeSinceLastSample);\n fpsAccumulator.frames = 0;\n fpsAccumulator.lastSampleAt = tick.now;\n }\n setStats({ fps: fpsAccumulator.fps, ticks: ticksRef.current, frames: fpsAccumulator.frames });\n };\n\n shaderContext.scheduler.add(schedulerTickHandler);\n\n return () => shaderContext.scheduler.remove(schedulerTickHandler);\n }, [shaderContext]);\n\n if (!shaderContext) {\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 { createContext } from 'react';\n\nimport type { FrameScheduler, GpuRenderer } from '@lovo/matter';\nimport type { Camera, Scene } from 'three';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport type PostProcessTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>;\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer;\n scene: Scene;\n camera: Camera;\n scheduler: FrameScheduler;\n registerOverlay: (transform: PostProcessTransform) => () => void;\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null);\n","'use client';\n\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n FrameScheduler,\n} from '@lovo/matter';\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 PostProcessTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js';\n\nexport interface ShaderSceneProps {\n children?: ReactNode;\n fallback?: ReactNode;\n className?: string;\n style?: CSSProperties;\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\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR } = props;\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [shaderContext, setShaderContext] = 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, PostProcessTransform>();\n\n const basePass = pass(scene, camera);\n\n const rebuildOutputNode = () => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n const basePassNode = basePass as unknown as ShaderNodeObject<Node>;\n\n postProcessing.outputNode = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n postProcessing.needsUpdate = true;\n };\n\n rebuildOutputNode(); // initial: just basePass, no overlays\n\n const registerOverlay = (transform: PostProcessTransform): (() => 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 setShaderContext({ renderer, scene, camera, scheduler, registerOverlay });\n } catch (caughtError) {\n if (cancelled) return;\n const normalizedError =\n caughtError instanceof Error ? caughtError : new Error(String(caughtError));\n\n console.error('[ShaderScene] renderer init failed:', normalizedError);\n setError(normalizedError);\n }\n };\n\n void setup();\n\n return () => {\n cancelled = true;\n cleanup?.();\n cleanup = null;\n setShaderContext(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 (shaderContext) {\n content = <ShaderContext.Provider value={shaderContext}>{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';\n\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\nexport function useAnimatableUniform<T>(\n value: AnimatableProp<T>,\n): ShaderNodeObject<Node> & { value: T } {\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 // 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 { useEffect, useState } from 'react';\n\nimport { CursorInput, type CursorInputOptions, type Vector2 } from '@lovo/matter';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport interface CursorSignal {\n get(): Vector2;\n on(event: 'change', cb: (value: Vector2) => void): () => void;\n}\n\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n};\n\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const shaderContext = useShaderContext();\n const [input, setInput] = useState<CursorInput | null>(null);\n\n useEffect(() => {\n const canvas = shaderContext?.renderer.three.domElement;\n const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined);\n const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });\n\n setInput(newCursorInput);\n\n let detach: (() => void) | null = null;\n\n if (shaderContext?.scheduler) {\n const schedulerTickHandler = ({ delta }: { delta: number }) => newCursorInput.tick(delta);\n\n shaderContext.scheduler.add(schedulerTickHandler);\n detach = () => shaderContext.scheduler.remove(schedulerTickHandler);\n } else {\n let animationFrameId: number | null = null;\n let lastNow = performance.now();\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000;\n\n lastNow = now;\n newCursorInput.tick(delta);\n animationFrameId = requestAnimationFrame(loop);\n };\n\n animationFrameId = requestAnimationFrame(loop);\n detach = () => {\n if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);\n };\n }\n\n return () => {\n detach();\n newCursorInput.dispose();\n setInput(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext]);\n\n return input ?? STUB_SIGNAL;\n}\n","import { useContext } from 'react';\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js';\n\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext);\n}\n","'use client';\n\nimport { type DependencyList, useEffect } from 'react';\n\nimport type { PostProcessTransform } from '../../context/shader-context.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function usePostProcessPass(transform: PostProcessTransform, deps: DependencyList): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n const unregister = shaderContext.registerOverlay(transform);\n\n return unregister;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext, ...deps]);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\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 get(): ResizeValue;\n on(event: 'change', cb: (value: ResizeValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n};\n\nexport function useResize(): ResizeSignal {\n const shaderContext = useShaderContext();\n const [signal, setSignal] = useState<ResizeSignal | null>(null);\n\n useEffect(() => {\n if (!shaderContext) return undefined;\n\n const canvas = shaderContext.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 { signal: newSignal, listeners } = createSignal<ResizeValue>(() => value);\n\n setSignal(newSignal);\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 listener of listeners) listener(next);\n };\n\n const observer = new ResizeObserver(emit);\n\n observer.observe(canvas);\n\n let mediaQueryList: MediaQueryList | null = null;\n let mediaQueryListener: (() => void) | null = null;\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return;\n const dpr = window.devicePixelRatio;\n const nextMediaQueryList = window.matchMedia(`(resolution: ${dpr}dppx)`);\n const nextMediaQueryListener = () => {\n emit();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n setupDprWatch();\n };\n\n nextMediaQueryList.addEventListener('change', nextMediaQueryListener);\n mediaQueryList = nextMediaQueryList;\n mediaQueryListener = nextMediaQueryListener;\n };\n\n setupDprWatch();\n\n return () => {\n observer.disconnect();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n mediaQueryList = null;\n mediaQueryListener = null;\n listeners.clear();\n setSignal(null);\n };\n }, [shaderContext]);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nexport function createSignal<T>(getValue: () => T): {\n signal: { get(): T; on(event: string, listener: (v: T) => void): () => void };\n listeners: Set<(v: T) => void>;\n} {\n const listeners = new Set<(v: T) => void>();\n\n return {\n listeners,\n signal: {\n get: getValue,\n on: (_event, listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n },\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\n\nexport type ScrollValue = readonly [scrollY: number, progress: number];\n\nexport interface ScrollSignal {\n get(): ScrollValue;\n on(event: 'change', cb: (value: ScrollValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n};\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 scrollYPosition = window.scrollY;\n\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1);\n const progress = Math.max(0, Math.min(1, scrollYPosition / max));\n\n return [scrollYPosition, progress];\n };\n\n let value: ScrollValue = compute();\n const { signal: newSignal, listeners } = createSignal<ScrollValue>(() => value);\n\n setSignal(newSignal);\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 listener of listeners) listener(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';\n\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { MeshBasicNodeMaterial } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nexport type ColorTSL = Node | ShaderNodeObject<Node>;\n\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const nodeMaterial = new MeshBasicNodeMaterial();\n\n nodeMaterial.colorNode = build();\n\n return nodeMaterial;\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\nexport function useStaticSceneHint(isStatic: boolean): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n\n return shaderContext.scheduler.setIdle(isStatic);\n }, [shaderContext, isStatic]);\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","useState","useEffect","useEffect","useMemo","useEffect","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-display-gamut/use-display-gamut.ts","../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/internal/create-signal.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 fallback?: ReactNode;\n children: ReactNode;\n}\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 ShaderMonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nconst anchorStyle: Record<ShaderMonitorAnchor, 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?: ShaderMonitorAnchor;\n}\n\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const shaderContext = 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 (!shaderContext) return;\n const schedulerTickHandler = (tick: { now: number }) => {\n ticksRef.current += 1;\n const fpsAccumulator = fpsAccumRef.current;\n\n fpsAccumulator.frames += 1;\n if (fpsAccumulator.lastSampleAt === 0) fpsAccumulator.lastSampleAt = tick.now;\n const deltaTimeSinceLastSample = tick.now - fpsAccumulator.lastSampleAt;\n\n if (deltaTimeSinceLastSample >= 500) {\n fpsAccumulator.fps = Math.round((fpsAccumulator.frames * 1000) / deltaTimeSinceLastSample);\n fpsAccumulator.frames = 0;\n fpsAccumulator.lastSampleAt = tick.now;\n }\n setStats({ fps: fpsAccumulator.fps, ticks: ticksRef.current, frames: fpsAccumulator.frames });\n };\n\n shaderContext.scheduler.add(schedulerTickHandler);\n\n return () => shaderContext.scheduler.remove(schedulerTickHandler);\n }, [shaderContext]);\n\n if (!shaderContext) {\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 { createContext } from 'react';\n\nimport type { FrameScheduler, GpuRenderer } from '@lovo/matter';\nimport type { Camera, Scene } from 'three';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport type PostProcessTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>;\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer;\n scene: Scene;\n camera: Camera;\n scheduler: FrameScheduler;\n registerOverlay: (transform: PostProcessTransform) => () => void;\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null);\n","'use client';\n\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n dither,\n FrameScheduler,\n} from '@lovo/matter';\nimport { OrthographicCamera, Scene } from 'three';\nimport { pass, renderOutput, vec4 } from 'three/tsl';\nimport { PostProcessing } from 'three/webgpu';\n\nimport {\n type PostProcessTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js';\nimport {\n type GamutPreference,\n useDisplayGamut,\n} from '../../hooks/use-display-gamut/use-display-gamut.js';\n\nexport interface ShaderSceneProps {\n children?: ReactNode;\n fallback?: ReactNode;\n className?: string;\n style?: CSSProperties;\n maxDPR?: number;\n /** Output color gamut. 'auto' (default) uses the widest the display supports. */\n gamut?: GamutPreference;\n}\n\nconst defaultStyle: CSSProperties = {\n position: 'absolute',\n inset: 0,\n display: 'block',\n width: '100%',\n height: '100%',\n};\n\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR, gamut = 'auto' } = props;\n const resolvedGamut = useDisplayGamut(gamut);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [shaderContext, setShaderContext] = useState<ShaderContextValue | null>(null);\n const [error, setError] = useState<Error | null>(null);\n // Stays false until the renderer has actually painted a frame containing the\n // shader. The fallback is held until then so there's no gap between dropping\n // the fallback and the first shader frame (which would otherwise flash the\n // canvas's clear state).\n const [firstFramePainted, setFirstFramePainted] = useState(false);\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 let firstPaintRaf: number | null = null;\n\n const setup = async () => {\n try {\n const renderer = await createRenderer(canvas, { maxDPR, gamut: resolvedGamut });\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\n // Take ownership of the output color transform so we can dither in\n // display-encoded space (see rebuildOutputNode below).\n postProcessing.outputColorTransform = false;\n const scheduler = new FrameScheduler();\n\n const overlays = new Map<symbol, PostProcessTransform>();\n\n const basePassNode = vec4(pass(scene, camera));\n\n const rebuildOutputNode = () => {\n // Overlays (Grain, Vignette, ...) compose in linear working space.\n const composed = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n\n // renderOutput applies tone mapping + the working->output color-space\n // transfer (it reads both from the context three sets because\n // outputColorTransform is false), so dither runs last, in\n // display-encoded space, right before 8-bit quantization. This breaks\n // up gradient banding uniformly across every component in the scene.\n postProcessing.outputNode = dither(renderOutput(composed));\n postProcessing.needsUpdate = true;\n };\n\n rebuildOutputNode(); // initial: just basePass, no overlays\n\n const registerOverlay = (transform: PostProcessTransform): (() => 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 // Signal \"first paint\" only once the scene actually has something to\n // draw (a base shader mesh, or at least an overlay pass) — the scheduler\n // renders empty frames before the child shader mounts its mesh, and we\n // don't want to drop the fallback over an empty canvas. Defer the state\n // flip by one rAF so the just-submitted frame composites before the\n // fallback is removed.\n let firstPaintSignaled = false;\n const renderFrame = () => {\n postProcessing.render();\n\n if (!firstPaintSignaled && (scene.children.length > 0 || overlays.size > 0)) {\n firstPaintSignaled = true;\n firstPaintRaf = requestAnimationFrame(() => {\n firstPaintRaf = null;\n if (!cancelled) setFirstFramePainted(true);\n });\n }\n };\n\n scheduler.add(renderFrame);\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 // Track the canvas's actual box size, not just window 'resize'. The\n // canvas commonly gets its real size from layout AFTER renderer init\n // (with no window resize firing), which would otherwise leave the\n // renderer stuck at the default 300x150 and render the scene into an\n // undersized target — compressing every shader's output. ResizeObserver\n // fires once on observe() and on every subsequent box change.\n const resizeObserver = new ResizeObserver(() => renderer.resize());\n\n resizeObserver.observe(canvas);\n\n cleanup = () => {\n unsubVisibility();\n unsubIntersection();\n visibility.dispose();\n intersection.dispose();\n resizeObserver.disconnect();\n scheduler.dispose();\n renderer.dispose();\n };\n\n setShaderContext({ renderer, scene, camera, scheduler, registerOverlay });\n } catch (caughtError) {\n if (cancelled) return;\n const normalizedError =\n caughtError instanceof Error ? caughtError : new Error(String(caughtError));\n\n console.error('[ShaderScene] renderer init failed:', normalizedError);\n setError(normalizedError);\n }\n };\n\n void setup();\n\n return () => {\n cancelled = true;\n if (firstPaintRaf !== null) {\n cancelAnimationFrame(firstPaintRaf);\n firstPaintRaf = null;\n }\n cleanup?.();\n cleanup = null;\n setShaderContext(null);\n // A fresh renderer (e.g. on gamut change) must re-prove its first paint,\n // so show the fallback again until it does.\n setFirstFramePainted(false);\n };\n }, [maxDPR, resolvedGamut]);\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 {\n // Mount the children as soon as the context exists so the shader can build\n // and paint, but keep the fallback overlaid on top until that first frame\n // lands. The children render no visible DOM of their own (they drive the\n // canvas), so the fallback sits above the canvas and is removed only once\n // the shader is actually on screen.\n content = (\n <>\n {shaderContext && (\n <ShaderContext.Provider value={shaderContext}>{children}</ShaderContext.Provider>\n )}\n {!firstFramePainted && (fallback ?? null)}\n </>\n );\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","import { useEffect, useState } from 'react';\n\nimport type { OutputGamut } from '@lovo/matter';\n\n/** What the consumer asks for: a fixed gamut, or 'auto' to detect the display. */\nexport type GamutPreference = 'auto' | OutputGamut;\n\nconst P3_QUERY = '(color-gamut: p3)';\n\nfunction detectGamut(): OutputGamut {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return 'srgb';\n }\n\n return window.matchMedia(P3_QUERY).matches ? 'p3' : 'srgb';\n}\n\n/**\n * Resolve a gamut preference to a concrete output gamut. Explicit 'srgb'/'p3'\n * pass through untouched; 'auto' queries `(color-gamut: p3)` and re-resolves\n * when the display capability changes (e.g. window dragged to another monitor).\n */\nexport function useDisplayGamut(preference: GamutPreference): OutputGamut {\n const [resolved, setResolved] = useState<OutputGamut>(() =>\n preference === 'auto' ? detectGamut() : preference,\n );\n\n useEffect(() => {\n if (preference !== 'auto') {\n setResolved(preference);\n\n return;\n }\n\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n setResolved('srgb');\n\n return;\n }\n\n const mediaQuery = window.matchMedia(P3_QUERY);\n const update = () => setResolved(mediaQuery.matches ? 'p3' : 'srgb');\n\n update();\n mediaQuery.addEventListener('change', update);\n\n return () => mediaQuery.removeEventListener('change', update);\n }, [preference]);\n\n return resolved;\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport { uniform } from 'three/tsl';\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\nexport function useAnimatableUniform<T>(value: AnimatableProp<T>): ReturnType<typeof uniform<T>> {\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 return uniformNode;\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { CursorInput, type CursorInputOptions, type Vector2 } from '@lovo/matter';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport interface CursorSignal {\n get(): Vector2;\n on(event: 'change', cb: (value: Vector2) => void): () => void;\n}\n\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n};\n\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const shaderContext = useShaderContext();\n const [input, setInput] = useState<CursorInput | null>(null);\n\n useEffect(() => {\n const canvas = shaderContext?.renderer.three.domElement;\n const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined);\n const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });\n\n setInput(newCursorInput);\n\n let detach: (() => void) | null = null;\n\n if (shaderContext?.scheduler) {\n const schedulerTickHandler = ({ delta }: { delta: number }) => newCursorInput.tick(delta);\n\n shaderContext.scheduler.add(schedulerTickHandler);\n detach = () => shaderContext.scheduler.remove(schedulerTickHandler);\n } else {\n let animationFrameId: number | null = null;\n let lastNow = performance.now();\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000;\n\n lastNow = now;\n newCursorInput.tick(delta);\n animationFrameId = requestAnimationFrame(loop);\n };\n\n animationFrameId = requestAnimationFrame(loop);\n detach = () => {\n if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);\n };\n }\n\n return () => {\n detach();\n newCursorInput.dispose();\n setInput(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext]);\n\n return input ?? STUB_SIGNAL;\n}\n","import { useContext } from 'react';\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js';\n\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext);\n}\n","'use client';\n\nimport { type DependencyList, useEffect } from 'react';\n\nimport type { PostProcessTransform } from '../../context/shader-context.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function usePostProcessPass(transform: PostProcessTransform, deps: DependencyList): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n const unregister = shaderContext.registerOverlay(transform);\n\n return unregister;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext, ...deps]);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\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 get(): ResizeValue;\n on(event: 'change', cb: (value: ResizeValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n};\n\nexport function useResize(): ResizeSignal {\n const shaderContext = useShaderContext();\n const [signal, setSignal] = useState<ResizeSignal | null>(null);\n\n useEffect(() => {\n if (!shaderContext) return undefined;\n\n const canvas = shaderContext.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 { signal: newSignal, listeners } = createSignal<ResizeValue>(() => value);\n\n setSignal(newSignal);\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 listener of listeners) listener(next);\n };\n\n const observer = new ResizeObserver(emit);\n\n observer.observe(canvas);\n\n let mediaQueryList: MediaQueryList | null = null;\n let mediaQueryListener: (() => void) | null = null;\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return;\n const dpr = window.devicePixelRatio;\n const nextMediaQueryList = window.matchMedia(`(resolution: ${dpr}dppx)`);\n const nextMediaQueryListener = () => {\n emit();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n setupDprWatch();\n };\n\n nextMediaQueryList.addEventListener('change', nextMediaQueryListener);\n mediaQueryList = nextMediaQueryList;\n mediaQueryListener = nextMediaQueryListener;\n };\n\n setupDprWatch();\n\n return () => {\n observer.disconnect();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n mediaQueryList = null;\n mediaQueryListener = null;\n listeners.clear();\n setSignal(null);\n };\n }, [shaderContext]);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nexport function createSignal<T>(getValue: () => T): {\n signal: { get(): T; on(event: string, listener: (v: T) => void): () => void };\n listeners: Set<(v: T) => void>;\n} {\n const listeners = new Set<(v: T) => void>();\n\n return {\n listeners,\n signal: {\n get: getValue,\n on: (_event, listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n },\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\n\nexport type ScrollValue = readonly [scrollY: number, progress: number];\n\nexport interface ScrollSignal {\n get(): ScrollValue;\n on(event: 'change', cb: (value: ScrollValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n};\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 scrollYPosition = window.scrollY;\n\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1);\n const progress = Math.max(0, Math.min(1, scrollYPosition / max));\n\n return [scrollYPosition, progress];\n };\n\n let value: ScrollValue = compute();\n const { signal: newSignal, listeners } = createSignal<ScrollValue>(() => value);\n\n setSignal(newSignal);\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 listener of listeners) listener(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';\n\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { MeshBasicNodeMaterial } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nexport type ColorTSL = Node | ShaderNodeObject<Node>;\n\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const nodeMaterial = new MeshBasicNodeMaterial();\n\n nodeMaterial.colorNode = build();\n\n return nodeMaterial;\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\nexport function useStaticSceneHint(isStatic: boolean): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n\n return shaderContext.scheduler.setIdle(isStatic);\n }, [shaderContext, isStatic]);\n}\n"],"mappings":"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","names":["useEffect","useState","jsx","useState","useEffect","useEffect","useRef","useState","useEffect","useState","Fragment","jsx","jsxs","useRef","useState","useEffect","useEffect","useEffect","useState","useContext","useContext","useState","useEffect","useEffect","useEffect","useEffect","useState","STUB_SIGNAL","useState","useEffect","useEffect","useState","STUB_SIGNAL","useState","useEffect","useEffect","useMemo","useEffect","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovo/matter-react",
3
- "version": "0.5.0",
3
+ "version": "1.0.0",
4
4
  "description": "React binding for Matter — MatterScene, useShaderMaterial, input hooks.",
5
5
  "keywords": [
6
6
  "components",
@@ -57,7 +57,7 @@
57
57
  "typescript": "^5.6.0",
58
58
  "vite": "^8.0.14",
59
59
  "vitest": "^4.1.7",
60
- "@lovo/matter": "0.5.0",
60
+ "@lovo/matter": "1.0.0",
61
61
  "@matter/tsconfig": "0.0.0"
62
62
  },
63
63
  "peerDependencies": {