effect-motion 0.2.0 → 0.3.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.
Files changed (103) hide show
  1. package/dist/Camera.d.ts +50 -28
  2. package/dist/Camera.js +71 -19
  3. package/dist/CameraHelpers.d.ts +70 -0
  4. package/dist/CameraHelpers.js +239 -0
  5. package/dist/CanvasExporter.d.ts +12 -0
  6. package/dist/CanvasExporter.js +40 -0
  7. package/dist/Color.d.ts +428 -0
  8. package/dist/Color.js +535 -0
  9. package/dist/EffectMotionError.d.ts +10 -0
  10. package/dist/EffectMotionError.js +6 -0
  11. package/dist/Entity.d.ts +9 -8
  12. package/dist/Entity.js +1 -4
  13. package/dist/Fonts.d.ts +10 -0
  14. package/dist/Fonts.js +16 -0
  15. package/dist/Images.d.ts +33 -0
  16. package/dist/Images.js +24 -0
  17. package/dist/Instance.d.ts +4 -4
  18. package/dist/Motion.d.ts +24 -7
  19. package/dist/Motion.js +54 -9
  20. package/dist/Phaser.d.ts +1 -1
  21. package/dist/Phaser.js +13 -9
  22. package/dist/Physics.d.ts +3 -3
  23. package/dist/Physics.js +4 -4
  24. package/dist/PngExporter.d.ts +6 -0
  25. package/dist/PngExporter.js +85 -0
  26. package/dist/Projection.d.ts +225 -0
  27. package/dist/Projection.js +473 -0
  28. package/dist/Renderer.d.ts +102 -53
  29. package/dist/Renderer.js +369 -78
  30. package/dist/Runner.d.ts +126 -42
  31. package/dist/Runner.js +42 -17
  32. package/dist/Scene.d.ts +88 -59
  33. package/dist/Scene.js +24 -15
  34. package/dist/Shapes.d.ts +11 -0
  35. package/dist/Shapes.js +11 -0
  36. package/dist/demo.d.ts +3 -186
  37. package/dist/demo.js +24 -35
  38. package/dist/index.d.ts +14 -13
  39. package/dist/index.js +14 -13
  40. package/dist/particles/Particle.d.ts +4 -3
  41. package/dist/particles/ParticleField.d.ts +11 -48
  42. package/dist/particles/ParticleField.js +5 -4
  43. package/dist/particles/constructors.d.ts +8 -7
  44. package/dist/particles/constructors.js +2 -2
  45. package/dist/particles/index.d.ts +5 -6
  46. package/dist/particles/index.js +5 -6
  47. package/dist/particles/overLife.d.ts +1 -1
  48. package/dist/particles/overLife.js +1 -1
  49. package/dist/particles/simulate.d.ts +2 -2
  50. package/dist/particles/simulate.js +6 -6
  51. package/dist/particles/step.d.ts +2 -2
  52. package/dist/particles/step.js +5 -3
  53. package/dist/render/dof.d.ts +27 -0
  54. package/dist/render/dof.js +37 -0
  55. package/dist/render/paint.d.ts +30 -0
  56. package/dist/render/paint.js +36 -0
  57. package/dist/render/shapes.d.ts +42 -0
  58. package/dist/render/shapes.js +310 -0
  59. package/dist/shapes/Circle.d.ts +10 -15
  60. package/dist/shapes/Circle.js +2 -2
  61. package/dist/shapes/Ellipse.d.ts +10 -16
  62. package/dist/shapes/Ellipse.js +2 -2
  63. package/dist/shapes/Group.d.ts +5 -39
  64. package/dist/shapes/Group.js +4 -4
  65. package/dist/shapes/Hud.d.ts +38 -0
  66. package/dist/shapes/Hud.js +35 -0
  67. package/dist/shapes/Image.d.ts +45 -0
  68. package/dist/shapes/Image.js +28 -0
  69. package/dist/shapes/Line.d.ts +12 -12
  70. package/dist/shapes/Line.js +11 -6
  71. package/dist/shapes/Path.d.ts +87 -18
  72. package/dist/shapes/Path.js +25 -6
  73. package/dist/shapes/Rect.d.ts +33 -23
  74. package/dist/shapes/Rect.js +14 -2
  75. package/dist/shapes/Shape2D.d.ts +13 -4
  76. package/dist/shapes/Shape2D.js +16 -5
  77. package/dist/shapes/Shapes.d.ts +11 -0
  78. package/dist/shapes/Shapes.js +11 -0
  79. package/dist/shapes/Square.d.ts +10 -15
  80. package/dist/shapes/Square.js +2 -2
  81. package/dist/shapes/Text.d.ts +10 -19
  82. package/dist/shapes/Text.js +2 -2
  83. package/package.json +13 -3
  84. package/dist/particles/render.d.ts +0 -13
  85. package/dist/particles/render.js +0 -33
  86. package/dist/shapes/Layer.d.ts +0 -9
  87. package/dist/shapes/Layer.js +0 -23
  88. package/dist/shapes/index.d.ts +0 -10
  89. package/dist/shapes/index.js +0 -10
  90. package/dist/svg/SvgDomRenderer.d.ts +0 -28
  91. package/dist/svg/SvgDomRenderer.js +0 -50
  92. package/dist/svg/SvgNode.d.ts +0 -13
  93. package/dist/svg/SvgNode.js +0 -18
  94. package/dist/svg/SvgRenderer.d.ts +0 -22
  95. package/dist/svg/SvgRenderer.js +0 -20
  96. package/dist/svg/camera.d.ts +0 -21
  97. package/dist/svg/camera.js +0 -43
  98. package/dist/svg/index.d.ts +0 -6
  99. package/dist/svg/index.js +0 -6
  100. package/dist/svg/layers.d.ts +0 -18
  101. package/dist/svg/layers.js +0 -13
  102. package/dist/svg/shapes.d.ts +0 -1089
  103. package/dist/svg/shapes.js +0 -119
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-motion",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Deterministic, frame-exact motion graphics in code, composed with Effect",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,6 +23,10 @@
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
25
  "default": "./dist/index.js"
26
+ },
27
+ "./*": {
28
+ "types": "./dist/*.d.ts",
29
+ "default": "./dist/*.js"
26
30
  }
27
31
  },
28
32
  "files": [
@@ -32,16 +36,22 @@
32
36
  "access": "public"
33
37
  },
34
38
  "peerDependencies": {
35
- "effect": ">=4.0.0-beta.94"
39
+ "effect": ">=4.0.0-beta.98"
36
40
  },
37
41
  "devDependencies": {
42
+ "@types/chroma-js": "^3.1.2",
38
43
  "@types/node": "^26.1.1",
39
- "effect": "4.0.0-beta.94",
44
+ "effect": "4.0.0-beta.98",
40
45
  "happy-dom": "^20.10.6",
41
46
  "tsx": "^4.23.0",
42
47
  "typescript": "^7.0.2",
43
48
  "vitest": "^4.1.10"
44
49
  },
50
+ "dependencies": {
51
+ "@effect/platform-node": "4.0.0-beta.98",
52
+ "chroma-js": "^3.2.0",
53
+ "@effect-motion/thorvg": "^0.1.0"
54
+ },
45
55
  "scripts": {
46
56
  "build": "tsc -p tsconfig.build.json",
47
57
  "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
@@ -1,13 +0,0 @@
1
- import type * as Renderer from "../Renderer";
2
- import type { SvgNode } from "../svg/SvgNode";
3
- import type { ParticleField } from "./ParticleField";
4
- /**
5
- * Render a ParticleField: one <g> whose children are one <circle> per LIVE
6
- * particle, each sized/faded by the over-life curves. Dead slots emit
7
- * nothing. This is where the O(1)-simulation buffer expands back into N
8
- * SVG nodes.
9
- * ponytail: N SVG nodes is the RENDERING ceiling (the DOM/string cost), not
10
- * the simulation ceiling. A batched/instanced sink is the upgrade path if
11
- * node count becomes the wall.
12
- */
13
- export declare const particleField: Renderer.RenderFunction<SvgNode, typeof ParticleField>;
@@ -1,33 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import { renderOpacity, renderSize } from "./overLife";
3
- /**
4
- * Render a ParticleField: one <g> whose children are one <circle> per LIVE
5
- * particle, each sized/faded by the over-life curves. Dead slots emit
6
- * nothing. This is where the O(1)-simulation buffer expands back into N
7
- * SVG nodes.
8
- * ponytail: N SVG nodes is the RENDERING ceiling (the DOM/string cost), not
9
- * the simulation ceiling. A batched/instanced sink is the upgrade path if
10
- * node count becomes the wall.
11
- */
12
- export const particleField = ({ data }) => Effect.succeed({
13
- tag: "g",
14
- props: data.x !== 0 || data.y !== 0
15
- ? { transform: `translate(${data.x} ${data.y})` }
16
- : {},
17
- children: data.buffer
18
- .filter((p) => p.alive)
19
- .map((p) => {
20
- const r = renderSize(p, data.sizeOverLife);
21
- const opacity = renderOpacity(p, data.opacityOverLife);
22
- return {
23
- tag: "circle",
24
- props: {
25
- cx: p.x,
26
- cy: p.y,
27
- r,
28
- fill: p.color,
29
- ...(opacity !== 1 ? { opacity } : {}),
30
- },
31
- };
32
- }),
33
- });
@@ -1,9 +0,0 @@
1
- import * as Schema from "effect/Schema";
2
- import * as Entity from "../Entity";
3
- export declare const Layer: Entity.Entity<"shapes/Layer", Schema.Struct<{
4
- depth: Schema.withConstructorDefault<Schema.Number>;
5
- children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
6
- }>, {}, {
7
- readonly depth?: number;
8
- readonly children?: readonly string[];
9
- }>;
@@ -1,23 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Schema from "effect/Schema";
3
- import * as Entity from "../Entity";
4
- import * as Shape2D from "./Shape2D";
5
- // A parallax layer: a container that holds children at a camera `depth`, and
6
- // nothing else. Unlike Group it carries NO position or opacity of its own —
7
- // `depth` is the only field, so it can never collide with the transform
8
- // semantics a Group is expected to grow (position now, rotation/scale later),
9
- // and a future guard can restrict Layers without touching Group.
10
- //
11
- // `depth` is the fraction of the camera this layer feels (pan AND zoom): 1 =
12
- // full camera (default), 0 = pinned to the screen (a HUD), between = parallax.
13
- // The sink reads it off each top-level layer (see svg/camera.ts).
14
- //
15
- // ponytail: a Layer nested inside another Layer is undefined behavior — the
16
- // point of a dedicated entity is to leave room for a rule here, but the
17
- // semantics ("a depth inside a depth"?) aren't decided yet, so no guard ships.
18
- // Add one in Runner.instantiate (dispatch on entity.name === Layer.name) once
19
- // nesting semantics are settled.
20
- export const Layer = Entity.make("shapes/Layer", {
21
- depth: Shape2D.defaultedNumber(1),
22
- children: Schema.Array(Schema.String).pipe(Schema.withConstructorDefault(Effect.sync(() => []))),
23
- });
@@ -1,10 +0,0 @@
1
- export { Circle } from "./Circle";
2
- export { Ellipse } from "./Ellipse";
3
- export * from "./Group";
4
- export { Layer } from "./Layer";
5
- export { Line } from "./Line";
6
- export { Path } from "./Path";
7
- export { Rect } from "./Rect";
8
- export * as Shape2D from "./Shape2D";
9
- export { Square } from "./Square";
10
- export { Text } from "./Text";
@@ -1,10 +0,0 @@
1
- export { Circle } from "./Circle";
2
- export { Ellipse } from "./Ellipse";
3
- export * from "./Group";
4
- export { Layer } from "./Layer";
5
- export { Line } from "./Line";
6
- export { Path } from "./Path";
7
- export { Rect } from "./Rect";
8
- export * as Shape2D from "./Shape2D";
9
- export { Square } from "./Square";
10
- export { Text } from "./Text";
@@ -1,28 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Renderer from "../Renderer";
3
- import { type SvgNode } from "./SvgNode";
4
- export interface SvgDomConfig {
5
- readonly target: HTMLElement;
6
- /** viewport size — defaults to the frame's own width/height metadata */
7
- readonly width?: number;
8
- readonly height?: number;
9
- }
10
- /**
11
- * Materializes a frame into `target` as live DOM.
12
- *
13
- * ponytail: clear-and-rebuild per frame; upgrade to keyed reconciliation
14
- * (instance ids are stable keys) inside this sink if it ever gets slow.
15
- */
16
- export declare const SvgDomRenderer: {
17
- Context: import("effect/Context").Service<{
18
- render: <const Entities extends import("../Entity").AnyEntity>(frame: import("../Scene").Frame<Entities>, config: SvgDomConfig) => Effect.Effect<void, never, Entities extends import("../Entity").AnyEntity ? { [K in Entities as K["name"]]: Renderer.EntityRenderer<`SvgDomRenderer/${K["name"]}`, SvgNode, K, never, never>; }[Entities["name"]] : never>;
19
- }, {
20
- render: <const Entities extends import("../Entity").AnyEntity>(frame: import("../Scene").Frame<Entities>, config: SvgDomConfig) => Effect.Effect<void, never, Entities extends import("../Entity").AnyEntity ? { [K in Entities as K["name"]]: Renderer.EntityRenderer<`SvgDomRenderer/${K["name"]}`, SvgNode, K, never, never>; }[Entities["name"]] : never>;
21
- }>;
22
- layer: import("effect/Layer").Layer<{
23
- render: <const Entities extends import("../Entity").AnyEntity>(frame: import("../Scene").Frame<Entities>, config: SvgDomConfig) => Effect.Effect<void, never, Entities extends import("../Entity").AnyEntity ? { [K in Entities as K["name"]]: Renderer.EntityRenderer<`SvgDomRenderer/${K["name"]}`, SvgNode, K, never, never>; }[Entities["name"]] : never>;
24
- }, never, never>;
25
- makeEntityRendererContext: <const Ent extends import("../Entity").AnyEntity, E = never, R = never>(entity: Ent) => import("effect/Context").Service<Renderer.EntityRenderer<`SvgDomRenderer/${Ent["name"]}`, SvgNode, Ent, E, R>, Renderer.EntityRenderer<`SvgDomRenderer/${Ent["name"]}`, SvgNode, Ent, E, R>>;
26
- makeEntityRendererService: <const Ent extends import("../Entity").AnyEntity, E = never, R = never>(entity: Ent, render: Renderer.RenderFunction<SvgNode, Ent, E, R>) => Renderer.EntityRenderer<`SvgDomRenderer/${Ent["name"]}`, SvgNode, Ent, E, R>;
27
- makeEntityRendererLayer: <const Ent extends import("../Entity").AnyEntity, E = never, R = never>(entity: Ent, render: Renderer.RenderFunction<SvgNode, Ent, E, R>) => import("effect/Layer").Layer<Renderer.EntityRenderer<`SvgDomRenderer/${Ent["name"]}`, SvgNode, Ent, E, R>, never, never>;
28
- };
@@ -1,50 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Renderer from "../Renderer";
3
- import { depthOf, layerTransform, wrapLayer } from "./camera";
4
- import { SVG_NS } from "./SvgNode";
5
- const createSvgElement = (doc, node) => {
6
- // namespace is sink-owned: entity renderers never see it
7
- const el = doc.createElementNS(SVG_NS, node.tag);
8
- for (const [key, value] of Object.entries(node.props)) {
9
- el.setAttribute(key, String(value));
10
- }
11
- if (typeof node.children === "string") {
12
- el.textContent = node.children;
13
- }
14
- else if (node.children !== undefined) {
15
- for (const child of node.children) {
16
- el.append(createSvgElement(doc, child));
17
- }
18
- }
19
- return el;
20
- };
21
- /**
22
- * Materializes a frame into `target` as live DOM.
23
- *
24
- * ponytail: clear-and-rebuild per frame; upgrade to keyed reconciliation
25
- * (instance ids are stable keys) inside this sink if it ever gets slow.
26
- */
27
- export const SvgDomRenderer = Renderer.make()("SvgDomRenderer", {
28
- render: (entities, config, meta) => Effect.gen(function* () {
29
- const doc = config.target.ownerDocument;
30
- const width = config.width ?? meta.width;
31
- const height = config.height ?? meta.height;
32
- const root = doc.createElementNS(SVG_NS, "svg");
33
- root.setAttribute("width", String(width));
34
- root.setAttribute("height", String(height));
35
- root.append(createSvgElement(doc, {
36
- tag: "rect",
37
- props: {
38
- width: "100%",
39
- height: "100%",
40
- fill: meta.backgroundColor,
41
- },
42
- }));
43
- // each top-level layer gets the camera scaled by its own depth
44
- for (const { render, entry } of entities) {
45
- const transform = layerTransform(meta.camera, depthOf(entry.data), width, height);
46
- root.append(createSvgElement(doc, wrapLayer(yield* render, transform)));
47
- }
48
- config.target.replaceChildren(root);
49
- }),
50
- });
@@ -1,13 +0,0 @@
1
- /**
2
- * Serialized SVG node — the data contract between entity renderers and
3
- * sinks. Entity renderers return descriptions; sinks own materialization
4
- * (string folding, DOM creation, namespaces).
5
- */
6
- export interface SvgNode {
7
- readonly tag: string;
8
- readonly props: Record<string, string | number>;
9
- /** nested nodes, or a string for text content */
10
- readonly children?: ReadonlyArray<SvgNode> | string;
11
- }
12
- export declare const SVG_NS = "http://www.w3.org/2000/svg";
13
- export declare const vnodeToString: (node: SvgNode) => string;
@@ -1,18 +0,0 @@
1
- export const SVG_NS = "http://www.w3.org/2000/svg";
2
- const escapeAttr = (value) => String(value)
3
- .replaceAll("&", "&amp;")
4
- .replaceAll('"', "&quot;")
5
- .replaceAll("<", "&lt;");
6
- const escapeText = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;");
7
- export const vnodeToString = (node) => {
8
- const props = Object.entries(node.props)
9
- .map(([key, value]) => ` ${key}="${escapeAttr(value)}"`)
10
- .join("");
11
- if (node.children === undefined) {
12
- return `<${node.tag}${props} />`;
13
- }
14
- const children = typeof node.children === "string"
15
- ? escapeText(node.children)
16
- : node.children.map(vnodeToString).join("");
17
- return `<${node.tag}${props}>${children}</${node.tag}>`;
18
- };
@@ -1,22 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Renderer from "../Renderer";
3
- import { type SvgNode } from "./SvgNode";
4
- export interface SvgConfig {
5
- /** viewport size — defaults to the frame's own width/height metadata */
6
- readonly width?: number;
7
- readonly height?: number;
8
- }
9
- /** Folds a frame into a single self-contained SVG document string. */
10
- export declare const SvgRenderer: {
11
- Context: import("effect/Context").Service<{
12
- render: <const Entities extends import("../Entity").AnyEntity>(frame: import("../Scene").Frame<Entities>, config: SvgConfig) => Effect.Effect<string, never, Entities extends import("../Entity").AnyEntity ? { [K in Entities as K["name"]]: Renderer.EntityRenderer<`SvgRenderer/${K["name"]}`, SvgNode, K, never, never>; }[Entities["name"]] : never>;
13
- }, {
14
- render: <const Entities extends import("../Entity").AnyEntity>(frame: import("../Scene").Frame<Entities>, config: SvgConfig) => Effect.Effect<string, never, Entities extends import("../Entity").AnyEntity ? { [K in Entities as K["name"]]: Renderer.EntityRenderer<`SvgRenderer/${K["name"]}`, SvgNode, K, never, never>; }[Entities["name"]] : never>;
15
- }>;
16
- layer: import("effect/Layer").Layer<{
17
- render: <const Entities extends import("../Entity").AnyEntity>(frame: import("../Scene").Frame<Entities>, config: SvgConfig) => Effect.Effect<string, never, Entities extends import("../Entity").AnyEntity ? { [K in Entities as K["name"]]: Renderer.EntityRenderer<`SvgRenderer/${K["name"]}`, SvgNode, K, never, never>; }[Entities["name"]] : never>;
18
- }, never, never>;
19
- makeEntityRendererContext: <const Ent extends import("../Entity").AnyEntity, E = never, R = never>(entity: Ent) => import("effect/Context").Service<Renderer.EntityRenderer<`SvgRenderer/${Ent["name"]}`, SvgNode, Ent, E, R>, Renderer.EntityRenderer<`SvgRenderer/${Ent["name"]}`, SvgNode, Ent, E, R>>;
20
- makeEntityRendererService: <const Ent extends import("../Entity").AnyEntity, E = never, R = never>(entity: Ent, render: Renderer.RenderFunction<SvgNode, Ent, E, R>) => Renderer.EntityRenderer<`SvgRenderer/${Ent["name"]}`, SvgNode, Ent, E, R>;
21
- makeEntityRendererLayer: <const Ent extends import("../Entity").AnyEntity, E = never, R = never>(entity: Ent, render: Renderer.RenderFunction<SvgNode, Ent, E, R>) => import("effect/Layer").Layer<Renderer.EntityRenderer<`SvgRenderer/${Ent["name"]}`, SvgNode, Ent, E, R>, never, never>;
22
- };
@@ -1,20 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Renderer from "../Renderer";
3
- import { depthOf, layerTransform, wrapLayer } from "./camera";
4
- import { SVG_NS, vnodeToString } from "./SvgNode";
5
- /** Folds a frame into a single self-contained SVG document string. */
6
- export const SvgRenderer = Renderer.make()("SvgRenderer", {
7
- render: (entities, config, meta) => Effect.gen(function* () {
8
- const width = config.width ?? meta.width;
9
- const height = config.height ?? meta.height;
10
- let svg = `<svg xmlns="${SVG_NS}" width="${width}" height="${height}">`;
11
- // a rect, not a style attr — survives rasterizers that ignore CSS
12
- svg += `<rect width="100%" height="100%" fill="${meta.backgroundColor}"/>`;
13
- // each top-level layer gets the camera scaled by its own depth
14
- for (const { render, entry } of entities) {
15
- const transform = layerTransform(meta.camera, depthOf(entry.data), width, height);
16
- svg += vnodeToString(wrapLayer(yield* render, transform));
17
- }
18
- return `${svg}</svg>`;
19
- }),
20
- });
@@ -1,21 +0,0 @@
1
- import type { FrameMeta } from "../Renderer";
2
- import type { SvgNode } from "./SvgNode";
3
- /**
4
- * The camera as an SVG transform for one top-level layer, scaled by that
5
- * layer's `depth` (pan AND zoom together). `depth` lerps the layer's
6
- * transform from identity toward the full camera:
7
- *
8
- * depth 1 → full camera depth 0 → identity (screen-fixed HUD)
9
- * depth d → 1 + (zoom-1)*d zoom, d*pan translation
10
- *
11
- * Zoom is about the viewport center so it reads as "zoom into the middle of
12
- * the shot", not the world origin. Returns "" when the layer transform is
13
- * identity, so identity cameras add no attribute (output byte-identical to
14
- * before the camera existed).
15
- */
16
- export declare const layerTransform: (camera: FrameMeta["camera"], depth: number, width: number, height: number) => string;
17
- /** `depth` for a top-level layer's data (a Layer carries it; others = 1). */
18
- export declare const depthOf: (data: unknown) => number;
19
- /** Wrap a rendered layer node in a camera `<g>`, or return it unwrapped
20
- * when the transform is identity (keeps the common case flat). */
21
- export declare const wrapLayer: (node: SvgNode, transform: string) => SvgNode;
@@ -1,43 +0,0 @@
1
- /**
2
- * The camera as an SVG transform for one top-level layer, scaled by that
3
- * layer's `depth` (pan AND zoom together). `depth` lerps the layer's
4
- * transform from identity toward the full camera:
5
- *
6
- * depth 1 → full camera depth 0 → identity (screen-fixed HUD)
7
- * depth d → 1 + (zoom-1)*d zoom, d*pan translation
8
- *
9
- * Zoom is about the viewport center so it reads as "zoom into the middle of
10
- * the shot", not the world origin. Returns "" when the layer transform is
11
- * identity, so identity cameras add no attribute (output byte-identical to
12
- * before the camera existed).
13
- */
14
- export const layerTransform = (camera, depth, width, height) => {
15
- const layerZoom = 1 + (camera.zoom - 1) * depth;
16
- const panX = camera.x * depth;
17
- const panY = camera.y * depth;
18
- if (layerZoom === 1 && panX === 0 && panY === 0) {
19
- return "";
20
- }
21
- const cx = width / 2;
22
- const cy = height / 2;
23
- // pan first, then zoom about the viewport center: reads right-to-left as
24
- // translate(-pan) → recenter → scale → uncenter applied to the layer
25
- const parts = [];
26
- if (layerZoom !== 1) {
27
- parts.push(`translate(${cx} ${cy})`, `scale(${layerZoom})`, `translate(${-cx} ${-cy})`);
28
- }
29
- if (panX !== 0 || panY !== 0) {
30
- parts.push(`translate(${-panX} ${-panY})`);
31
- }
32
- return parts.join(" ");
33
- };
34
- /** `depth` for a top-level layer's data (a Layer carries it; others = 1). */
35
- export const depthOf = (data) => {
36
- const depth = data?.depth;
37
- return typeof depth === "number" ? depth : 1;
38
- };
39
- /** Wrap a rendered layer node in a camera `<g>`, or return it unwrapped
40
- * when the transform is identity (keeps the common case flat). */
41
- export const wrapLayer = (node, transform) => transform === ""
42
- ? node
43
- : { tag: "g", props: { transform }, children: [node] };
@@ -1,6 +0,0 @@
1
- export { entityRendererLayer, layer } from "./layers";
2
- export { type SvgDomConfig, SvgDomRenderer } from "./SvgDomRenderer";
3
- export { SVG_NS, type SvgNode, vnodeToString } from "./SvgNode";
4
- export { type SvgConfig, SvgRenderer } from "./SvgRenderer";
5
- export * as shapes from "./shapes";
6
- export { shapesLayer } from "./shapes";
package/dist/svg/index.js DELETED
@@ -1,6 +0,0 @@
1
- export { entityRendererLayer, layer } from "./layers";
2
- export { SvgDomRenderer } from "./SvgDomRenderer";
3
- export { SVG_NS, vnodeToString } from "./SvgNode";
4
- export { SvgRenderer } from "./SvgRenderer";
5
- export * as shapes from "./shapes";
6
- export { shapesLayer } from "./shapes";
@@ -1,18 +0,0 @@
1
- import { Layer } from "effect";
2
- import type * as Entity from "../Entity";
3
- import type * as Renderer from "../Renderer";
4
- import type { SvgNode } from "./SvgNode";
5
- /**
6
- * Register one SvgNode render function for an entity with BOTH sinks.
7
- *
8
- * Entity renderer context keys are per renderer family
9
- * (`SvgRenderer/<name>` and `SvgDomRenderer/<name>`), so sharing a render
10
- * function across sinks means registering it twice — this does both.
11
- */
12
- export declare const entityRendererLayer: <const Ent extends Entity.AnyEntity>(entity: Ent, render: Renderer.RenderFunction<SvgNode, Ent>) => Layer.Layer<Renderer.EntityRenderer<`SvgDomRenderer/${Ent["name"]}`, SvgNode, Ent, never, never> | Renderer.EntityRenderer<`SvgRenderer/${Ent["name"]}`, SvgNode, Ent, never, never>, never, never>;
13
- /** Both sinks' frame renderers, ready to provide. */
14
- export declare const layer: Layer.Layer<{
15
- render: <const Entities extends Entity.AnyEntity>(frame: import("../Scene").Frame<Entities>, config: import("./SvgRenderer").SvgConfig) => import("effect/Effect").Effect<string, never, Entities extends Entity.AnyEntity ? { [K in Entities as K["name"]]: Renderer.EntityRenderer<`SvgRenderer/${K["name"]}`, SvgNode, K, never, never>; }[Entities["name"]] : never>;
16
- } | {
17
- render: <const Entities extends Entity.AnyEntity>(frame: import("../Scene").Frame<Entities>, config: import("./SvgDomRenderer").SvgDomConfig) => import("effect/Effect").Effect<void, never, Entities extends Entity.AnyEntity ? { [K in Entities as K["name"]]: Renderer.EntityRenderer<`SvgDomRenderer/${K["name"]}`, SvgNode, K, never, never>; }[Entities["name"]] : never>;
18
- }, never, never>;
@@ -1,13 +0,0 @@
1
- import { Layer } from "effect";
2
- import { SvgDomRenderer } from "./SvgDomRenderer";
3
- import { SvgRenderer } from "./SvgRenderer";
4
- /**
5
- * Register one SvgNode render function for an entity with BOTH sinks.
6
- *
7
- * Entity renderer context keys are per renderer family
8
- * (`SvgRenderer/<name>` and `SvgDomRenderer/<name>`), so sharing a render
9
- * function across sinks means registering it twice — this does both.
10
- */
11
- export const entityRendererLayer = (entity, render) => Layer.mergeAll(SvgRenderer.makeEntityRendererLayer(entity, render), SvgDomRenderer.makeEntityRendererLayer(entity, render));
12
- /** Both sinks' frame renderers, ready to provide. */
13
- export const layer = Layer.mergeAll(SvgRenderer.layer, SvgDomRenderer.layer);