@wave3d/core 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # @wave3d/core
2
2
 
3
- Framework-agnostic **3D gradient-wave** renderer + config model — the engine behind [Wave Studio](https://github.com/Amir-Abushanab/wave3d).
3
+ The framework-agnostic renderer and config model behind [Wave Studio](https://wave-studio.pages.dev): glossy, twisting 3D gradient waves driven by one JSON config.
4
4
 
5
- A glossy, twisting _wave of light_: a strip swept along a curve, with a gradient running along its length and a satin sheen. Everything is driven by a single JSON-serializable `WaveConfig`, so the renderer and every export read from one source of truth.
5
+ A wave is a strip swept along a curve, with a gradient running down its length and a satin sheen. The renderer and every export read from the same `WaveConfig`, so what you design is exactly what ships.
6
6
 
7
- ## Install
7
+ > 💡 Fastest way in: design a wave in **[Wave Studio](https://wave-studio.pages.dev)**, hit **⟨⟩ Export code**, and copy the snippet. This package is what that snippet imports.
8
+
9
+ ## 📦 Install
8
10
 
9
11
  ```sh
10
12
  pnpm add @wave3d/core three # three is a peer dependency
@@ -12,9 +14,9 @@ pnpm add @wave3d/core three # three is a peer dependency
12
14
 
13
15
  `three` is a peer dependency (`>=0.180 <1`); add `@types/three` for TypeScript.
14
16
 
15
- ## Drop-in wave (poster-first, self-optimizing)
17
+ ## Drop-in wave
16
18
 
17
- The `.` entry is a lightweight shell: it shows a poster immediately, then lazily upgrades to the live WebGL wave only when the browser can run it (falling back to the poster on no-WebGL, Save-Data, reduced-motion, or context loss). `three.js` is code-split out of the initial load.
19
+ The default entry is poster-first: it shows a poster immediately, then upgrades to live WebGL only when the browser can run it. It falls back to the poster on no-WebGL, Save-Data, reduced motion, or a lost context, and `three.js` is code-split out of the initial load.
18
20
 
19
21
  ```ts
20
22
  import { createWave } from "@wave3d/core";
@@ -22,38 +24,48 @@ import { createWave } from "@wave3d/core";
22
24
  const handle = createWave(document.getElementById("wave"), config, {
23
25
  poster: "/wave.png",
24
26
  });
25
- // handle.set(nextConfig) · handle.pause() · handle.play() · handle.destroy()
27
+ // handle.set(nextConfig) · handle.pause() · handle.play() · handle.snapshot() · handle.destroy()
26
28
  ```
27
29
 
28
- ## Single `<script>` from a CDN (three bundled)
30
+ Capture the live frame as an image you can host and reuse as the poster for reduced-motion, no-WebGL, and Save-Data visitors:
31
+
32
+ ```ts
33
+ const blob = await handle.snapshot(); // resolves null until running. options: { type?, quality?, transparent? }
34
+ ```
35
+
36
+ ## Single `<script>` from a CDN
37
+
38
+ `three` is bundled into the standalone build, so this needs nothing else:
29
39
 
30
40
  ```html
31
41
  <script type="module">
32
42
  import { mountWave } from "https://esm.sh/@wave3d/core/standalone";
33
43
  mountWave(document.getElementById("wave"), {
34
- /* config */
44
+ /* your exported config */
35
45
  });
36
46
  </script>
37
47
  ```
38
48
 
39
49
  ## Entry points
40
50
 
41
- | Import | What |
42
- | ------------------------- | ------------------------------------------------------------ |
43
- | `@wave3d/core` | poster-first shell (`createWave`/`mountWave`) + config model |
44
- | `@wave3d/core/renderer` | the raw `WaveRenderer` + `WaveGeometry` (static three) |
45
- | `@wave3d/core/presets` | the built-in `PRESETS` |
46
- | `@wave3d/core/studio` | `StudioWaveRenderer` + randomizers |
47
- | `@wave3d/core/standalone` | single-file build with three bundled (the CDN entry) |
51
+ | Import | What |
52
+ | ------------------------- | -------------------------------------------------------------- |
53
+ | `@wave3d/core` | poster-first shell (`createWave` / `mountWave`) + config model |
54
+ | `@wave3d/core/renderer` | the raw `WaveRenderer` and `WaveGeometry` (static three) |
55
+ | `@wave3d/core/presets` | the built-in `PRESETS` |
56
+ | `@wave3d/core/studio` | `StudioWaveRenderer` + randomizers |
57
+ | `@wave3d/core/standalone` | single-file build with three bundled (the CDN entry) |
48
58
 
49
59
  ## Framework wrappers
50
60
 
51
- - **React** [`@wave3d/react`](https://www.npmjs.com/package/@wave3d/react): the `<Wave3D>` component.
52
- - **Vue / Svelte / plain HTML** — [`@wave3d/element`](https://www.npmjs.com/package/@wave3d/element): the `<wave-3d>` custom element.
61
+ Prefer a component? Reach for the wrapper for your framework:
62
+
63
+ - **React** → [`@wave3d/react`](https://www.npmjs.com/package/@wave3d/react): the `<Wave3D>` component.
64
+ - **Vue, Svelte, or plain HTML** → [`@wave3d/element`](https://www.npmjs.com/package/@wave3d/element): the `<wave-3d>` custom element.
53
65
 
54
66
  ## Credits
55
67
 
56
- Created by [Amir Abushanab](https://github.com/Amir-Abushanab).
68
+ Built by [Amir Abushanab](https://github.com/Amir-Abushanab).
57
69
 
58
70
  ## License
59
71
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { BackgroundImageFit, BackgroundMode, BasicGradientType, BlendMode, ColorStop, DEFAULT_LIGHT_POSITION, GradientType, LightConfig, MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS, MAX_WAVES, MeshGradientPoint, NoiseBand, PaletteSource, SceneConfig, StudioConfig, Vec2, Vec3, WaveConfig, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, normalizeBackground, normalizeWave, resizeWaves } from "./config/model.js";
2
2
  import { WaveRenderer, WaveRendererOptions } from "./renderer/WaveRenderer.js";
3
- import { FallbackReason, WaveHandle, WaveOptions, WaveState, createWave, mountWave } from "./shell/createWave.js";
4
- export { BackgroundImageFit, BackgroundMode, BasicGradientType, BlendMode, ColorStop, DEFAULT_LIGHT_POSITION, type FallbackReason, GradientType, LightConfig, MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS, MAX_WAVES, MeshGradientPoint, NoiseBand, PaletteSource, SceneConfig, StudioConfig, Vec2, Vec3, WaveConfig, type WaveHandle, type WaveOptions, type WaveRenderer, type WaveRendererOptions, type WaveState, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, createWave, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, mountWave, normalizeBackground, normalizeWave, resizeWaves };
3
+ import { FallbackReason, SnapshotOptions, WaveHandle, WaveOptions, WaveState, createWave, mountWave } from "./shell/createWave.js";
4
+ export { BackgroundImageFit, BackgroundMode, BasicGradientType, BlendMode, ColorStop, DEFAULT_LIGHT_POSITION, type FallbackReason, GradientType, LightConfig, MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS, MAX_WAVES, MeshGradientPoint, NoiseBand, PaletteSource, SceneConfig, type SnapshotOptions, StudioConfig, Vec2, Vec3, WaveConfig, type WaveHandle, type WaveOptions, type WaveRenderer, type WaveRendererOptions, type WaveState, createDefaultConfig, createDefaultMeshPoints, createLight, createNoiseBand, createWave, ensureCamera, ensureSceneDefaults, ensureStudioConfig, makeStops, makeWave, makeWaveSpread, mountWave, normalizeBackground, normalizeWave, resizeWaves };
@@ -91,7 +91,7 @@ declare class WaveRenderer {
91
91
  private readonly clipSphere;
92
92
  private readonly clipTmpA;
93
93
  private readonly clipTmpB;
94
- private readonly clock;
94
+ private readonly timer;
95
95
  private time;
96
96
  private rafId;
97
97
  protected running: boolean;
@@ -174,7 +174,7 @@ declare class WaveRenderer {
174
174
  private onVisibilityChange;
175
175
  private updateRunning;
176
176
  private loop;
177
- /** Advance the per-frame clock uniforms (geometry itself is static). Time model:
177
+ /** Advance the per-frame time uniforms (geometry itself is static). Time model:
178
178
  * time = elapsed·introTimeRamp + timeOffset — the ramp eases the animation in on load. */
179
179
  private updateTime;
180
180
  /** Render exactly one frame at the current time. */
@@ -220,7 +220,7 @@ declare class WaveRenderer {
220
220
  * of hidden exactly on top of it or (for the hero, which fills the right of the frame) pushed
221
221
  * off-frame. Camera-relative: it's "left on screen" no matter how the view is rotated/zoomed,
222
222
  * because it's built from the camera's right/up axes and the frame's visible world size. */
223
- captureImage(mime: string, transparent?: boolean, quality?: number): Promise<Blob>;
223
+ captureImage(mime: string, transparent?: boolean, quality?: number, time?: number): Promise<Blob>;
224
224
  captureStream(fps?: number): MediaStream;
225
225
  get canvas(): HTMLCanvasElement;
226
226
  getConfig(): StudioConfig;
@@ -169,7 +169,7 @@ var WaveRenderer = class {
169
169
  clipSphere = new THREE.Sphere();
170
170
  clipTmpA = new THREE.Vector3();
171
171
  clipTmpB = new THREE.Vector3();
172
- clock = new THREE.Clock();
172
+ timer = new THREE.Timer();
173
173
  time = 0;
174
174
  rafId = 0;
175
175
  running = false;
@@ -925,8 +925,7 @@ var WaveRenderer = class {
925
925
  const shouldAnimate = this.started && this.visible && this.pageVisible && !this.config.paused && !this.reducedMotion;
926
926
  if (shouldAnimate && !this.running) {
927
927
  this.running = true;
928
- this.clock.start();
929
- this.clock.getDelta();
928
+ this.timer.update();
930
929
  this.rafId = requestAnimationFrame(this.loop);
931
930
  } else if (!shouldAnimate && this.running) {
932
931
  this.running = false;
@@ -940,12 +939,13 @@ var WaveRenderer = class {
940
939
  }
941
940
  loop = () => {
942
941
  if (!this.running) return;
943
- this.time += this.clock.getDelta();
942
+ this.timer.update();
943
+ this.time += this.timer.getDelta();
944
944
  if (this.introTimeRamp < 1) this.introTimeRamp = Math.min(1, this.introTimeRamp + .016);
945
945
  this.renderOnce();
946
946
  this.rafId = requestAnimationFrame(this.loop);
947
947
  };
948
- /** Advance the per-frame clock uniforms (geometry itself is static). Time model:
948
+ /** Advance the per-frame time uniforms (geometry itself is static). Time model:
949
949
  * time = elapsed·introTimeRamp + timeOffset — the ramp eases the animation in on load. */
950
950
  updateTime() {
951
951
  const ramp = this.config.introRamp === false || this.skipIntroRamp ? 1 : this.introTimeRamp;
@@ -1051,12 +1051,19 @@ var WaveRenderer = class {
1051
1051
  * of hidden exactly on top of it or (for the hero, which fills the right of the frame) pushed
1052
1052
  * off-frame. Camera-relative: it's "left on screen" no matter how the view is rotated/zoomed,
1053
1053
  * because it's built from the camera's right/up axes and the frame's visible world size. */
1054
- async captureImage(mime, transparent = true, quality) {
1055
- const prev = this.config.transparentBackground;
1056
- if (transparent !== prev) {
1054
+ async captureImage(mime, transparent = true, quality, time) {
1055
+ const prevBg = this.config.transparentBackground;
1056
+ if (transparent !== prevBg) {
1057
1057
  this.config.transparentBackground = transparent;
1058
1058
  this.applyBackground();
1059
1059
  }
1060
+ const fixTime = time !== void 0;
1061
+ const prevTime = this.time;
1062
+ const prevRamp = this.introTimeRamp;
1063
+ if (fixTime) {
1064
+ this.time = time;
1065
+ this.introTimeRamp = 1;
1066
+ }
1060
1067
  let blob = null;
1061
1068
  try {
1062
1069
  this.capturing = true;
@@ -1064,10 +1071,14 @@ var WaveRenderer = class {
1064
1071
  blob = await new Promise((resolve) => this.canvas.toBlob(resolve, mime, quality));
1065
1072
  } finally {
1066
1073
  this.capturing = false;
1067
- if (transparent !== prev) {
1068
- this.config.transparentBackground = prev;
1074
+ if (transparent !== prevBg) {
1075
+ this.config.transparentBackground = prevBg;
1069
1076
  this.applyBackground();
1070
1077
  }
1078
+ if (fixTime) {
1079
+ this.time = prevTime;
1080
+ this.introTimeRamp = prevRamp;
1081
+ }
1071
1082
  this.renderOnce();
1072
1083
  }
1073
1084
  if (!blob || blob.type !== mime) throw new Error(`Failed to capture ${mime}`);