@anvpx/motion-scenes 0.1.0 → 0.1.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/dist/custom.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isAbsolute, posix, relative, resolve } from "node:path";
2
2
  import { SceneEntryBaseSchema, } from "@anvpx/motion-schema";
3
3
  import { rootElement, safePadding } from "./define-scene.js";
4
+ import { isSharedModulePath } from "./runtime-files.js";
4
5
  /** The storyboard `type` that means "the author wrote this scene". */
5
6
  export const CUSTOM_SCENE_TYPE = "custom";
6
7
  /** The bare specifiers a scene script may import; everything else must be a relative path. */
@@ -77,6 +78,11 @@ export const readRelativeClosure = async (entryJs, read) => {
77
78
  for (const specifier of scan.escaping)
78
79
  escaping.add(specifier);
79
80
  for (const specifier of scan.paths) {
81
+ // Shared modules are virtual build outputs, never reads from the source
82
+ // filesystem. Resolve from the same scenes/<id>/ location used by emit.
83
+ const emitted = posix.normalize(posix.join("scenes/custom", item.base, specifier));
84
+ if (isSharedModulePath(emitted))
85
+ continue;
80
86
  const path = resolveRelative(item.base, specifier);
81
87
  if (path === undefined) {
82
88
  escaping.add(specifier);
@@ -0,0 +1,21 @@
1
+ export { clamp01, ease } from "./easing.js";
2
+ /**
3
+ * Normalized interval in seconds. A zero duration is an instantaneous cut;
4
+ * negative durations are invalid. No prior samples or DOM state are read.
5
+ * @param {number} time
6
+ * @param {number} start
7
+ * @param {number} duration
8
+ */
9
+ export declare const progress: (time: number, start: number, duration: number) => number;
10
+ /** Numeric interpolation; amount may overshoot for spring or anticipation.
11
+ * @param {number} from
12
+ * @param {number} to
13
+ * @param {number} amount
14
+ */
15
+ export declare const interpolate: (from: number, to: number, amount: number) => number;
16
+ /** Start time for an item. A negative step expresses a reversed order.
17
+ * @param {number} index non-negative integer
18
+ * @param {number} step seconds between items
19
+ * @param {number} [start=0] first item's time
20
+ */
21
+ export declare const stagger: (index: number, step: number, start?: number) => number;
@@ -0,0 +1,43 @@
1
+ // @ts-check
2
+ // Style-neutral authoring helpers. Every sample depends only on its inputs.
3
+ export { clamp01, ease } from "./easing.js";
4
+ /** @param {number[]} values */
5
+ const finite = (...values) => {
6
+ if (!values.every(Number.isFinite))
7
+ throw new RangeError("composition inputs must be finite");
8
+ };
9
+ /**
10
+ * Normalized interval in seconds. A zero duration is an instantaneous cut;
11
+ * negative durations are invalid. No prior samples or DOM state are read.
12
+ * @param {number} time
13
+ * @param {number} start
14
+ * @param {number} duration
15
+ */
16
+ export const progress = (time, start, duration) => {
17
+ finite(time, start, duration);
18
+ if (duration < 0)
19
+ throw new RangeError("duration must be non-negative");
20
+ if (duration === 0)
21
+ return time < start ? 0 : 1;
22
+ return Math.max(0, Math.min(1, (time - start) / duration));
23
+ };
24
+ /** Numeric interpolation; amount may overshoot for spring or anticipation.
25
+ * @param {number} from
26
+ * @param {number} to
27
+ * @param {number} amount
28
+ */
29
+ export const interpolate = (from, to, amount) => {
30
+ finite(from, to, amount);
31
+ return from + (to - from) * amount;
32
+ };
33
+ /** Start time for an item. A negative step expresses a reversed order.
34
+ * @param {number} index non-negative integer
35
+ * @param {number} step seconds between items
36
+ * @param {number} [start=0] first item's time
37
+ */
38
+ export const stagger = (index, step, start = 0) => {
39
+ if (!Number.isInteger(index) || index < 0)
40
+ throw new RangeError("index must be a non-negative integer");
41
+ finite(step, start);
42
+ return start + index * step;
43
+ };
@@ -1,2 +1,4 @@
1
+ /** True only for a browser module that this package emits; does not read disk. */
2
+ export declare const isSharedModulePath: (path: string) => boolean;
1
3
  /** Shared browser modules; the build emits them once under `runtime/` and `shared/`. */
2
4
  export declare const runtimeFiles: () => Map<string, string>;
@@ -1,7 +1,10 @@
1
1
  import { readText } from "./text-file.js";
2
- const RUNTIME_MODULES = ["easing.js", "timeline.js", "mount.js"];
2
+ const RUNTIME_MODULES = ["easing.js", "timeline.js", "mount.js", "compose.js"];
3
3
  /** Pure helpers shared with emit time; they import `../runtime/easing.js`, so they sit beside it. */
4
4
  const SHARED_MODULES = ["motion.js", "format.js"];
5
+ /** True only for a browser module that this package emits; does not read disk. */
6
+ export const isSharedModulePath = (path) => RUNTIME_MODULES.some((name) => path === `runtime/${name}`) ||
7
+ SHARED_MODULES.some((name) => path === `shared/${name}`);
5
8
  /** Shared browser modules; the build emits them once under `runtime/` and `shared/`. */
6
9
  export const runtimeFiles = () => new Map([
7
10
  ...RUNTIME_MODULES.map((name) => [
@@ -32,6 +32,7 @@ const copy = (props) => {
32
32
  };
33
33
  const render = (props) => {
34
34
  const data = [
35
+ `data-model="${esc(props.model ?? "knot")}"`,
35
36
  `data-post="${esc(props.post)}"`,
36
37
  `data-light="${esc(props.light)}"`,
37
38
  `data-material="${esc(props.material)}"`,
@@ -45,7 +46,7 @@ export const scene3dScene = defineScene({
45
46
  type: "scene3d",
46
47
  configSchema,
47
48
  meta: {
48
- purpose: "WebGL diorama (vendored three.js): post, light, material, environment presets; flat 2D canvas fallback without WebGL. 3D output is machine+GPU stable, not portable.",
49
+ purpose: "WebGL diorama (vendored three.js): post, light, material, environment presets; model presets knot|gem|sphere|torus; flat 2D canvas fallback without WebGL. 3D output is machine+GPU stable, not portable.",
49
50
  example: { title: "Orbit" },
50
51
  },
51
52
  style: readText("./style.css", import.meta.url),
@@ -43,6 +43,25 @@ const MATERIAL = {
43
43
  metal: { roughness: 0.3, metalness: 1, emissiveIntensity: 0 },
44
44
  emissive: { roughness: 0.45, metalness: 0, emissiveIntensity: 1.6 },
45
45
  };
46
+ /**
47
+ * Hero geometry per `model` preset. These are procedural shapes, not loaded
48
+ * GLB files: the scene stays offline and deterministic. `gem` is a faceted
49
+ * low-poly stone; unknown names fall back to the knot.
50
+ * @param {string} name
51
+ * @returns {any}
52
+ */
53
+ const modelGeometry = (name) => {
54
+ switch (name) {
55
+ case "gem":
56
+ return new THREE.OctahedronGeometry(1.28, 0);
57
+ case "sphere":
58
+ return new THREE.SphereGeometry(0.95, 48, 32);
59
+ case "torus":
60
+ return new THREE.TorusGeometry(0.95, 0.28, 32, 96);
61
+ default:
62
+ return new THREE.TorusKnotGeometry(0.82, 0.27, 160, 32);
63
+ }
64
+ };
46
65
  /**
47
66
  * @param {Record<string, any>} table
48
67
  * @param {string | undefined} name
@@ -241,7 +260,7 @@ const flatStage = (canvas, presets, duration) => {
241
260
  * library) returns null so the caller falls back to the 2D diorama instead of a black frame.
242
261
  * @param {HTMLCanvasElement} canvas
243
262
  * @param {HTMLElement} root
244
- * @param {{ post: any, light: any, material: any, env: any }} presets
263
+ * @param {{ post: any, light: any, material: any, model?: string, env: any }} presets
245
264
  * @param {number} duration
246
265
  */
247
266
  const glStage = (canvas, root, presets, duration) => {
@@ -293,14 +312,17 @@ const glStage = (canvas, root, presets, duration) => {
293
312
  floor.receiveShadow = true;
294
313
  scene.add(floor);
295
314
  const surface = presets.material;
315
+ const modelName = presets.model ?? "knot";
316
+ const geometry = modelGeometry(modelName);
296
317
  const heroMaterial = new THREE.MeshStandardMaterial({
297
318
  color: new THREE.Color(accent),
298
319
  roughness: surface.roughness,
299
320
  metalness: surface.metalness,
300
321
  emissive: new THREE.Color(accent2),
301
322
  emissiveIntensity: surface.emissiveIntensity * 0.35,
323
+ flatShading: modelName === "gem",
302
324
  });
303
- const hero = new THREE.Mesh(new THREE.TorusKnotGeometry(0.82, 0.27, 160, 32), heroMaterial);
325
+ const hero = new THREE.Mesh(geometry, heroMaterial);
304
326
  hero.position.y = 0.12;
305
327
  hero.castShadow = true;
306
328
  scene.add(hero);
@@ -386,6 +408,7 @@ export function createScene(root, config, ctx) {
386
408
  post: pick(POST, box?.dataset.post) ?? POST.cinematic,
387
409
  light: pick(LIGHT, box?.dataset.light) ?? LIGHT.studio,
388
410
  material: pick(MATERIAL, box?.dataset.material) ?? MATERIAL.clay,
411
+ model: box?.dataset.model ?? "knot",
389
412
  env,
390
413
  bg: env.bg,
391
414
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anvpx/motion-scenes",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Scene registry and browser renderer for @anvpx/motion storyboards.",
5
5
  "license": "MIT",
6
6
  "author": "anvpx",
@@ -28,9 +28,9 @@
28
28
  "dist/**/*.css"
29
29
  ],
30
30
  "dependencies": {
31
- "@anvpx/motion-assets": "0.1.0",
32
- "@anvpx/motion-schema": "0.1.0",
33
- "@anvpx/motion-themes": "0.1.0",
31
+ "@anvpx/motion-assets": "0.1.1",
32
+ "@anvpx/motion-schema": "0.1.1",
33
+ "@anvpx/motion-themes": "0.1.1",
34
34
  "postprocessing": "6.39.5",
35
35
  "three": "0.186.0",
36
36
  "zod": "4.6.7"