@uppercut-labs/ps1-backgrounds 0.1.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.
Files changed (66) hide show
  1. package/LICENSE +216 -0
  2. package/NOTICE +24 -0
  3. package/README.md +159 -0
  4. package/dist/element.d.ts +12 -0
  5. package/dist/element.js +115 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.js +2 -0
  8. package/dist/kit.d.ts +74 -0
  9. package/dist/kit.js +383 -0
  10. package/dist/runtime.d.ts +20 -0
  11. package/dist/runtime.js +415 -0
  12. package/dist/scene.d.ts +137 -0
  13. package/dist/scene.js +10 -0
  14. package/dist/scenes/generated/atlas-of-little-worlds.d.ts +48 -0
  15. package/dist/scenes/generated/atlas-of-little-worlds.js +100 -0
  16. package/dist/scenes/generated/black-ocean-causeway.d.ts +35 -0
  17. package/dist/scenes/generated/black-ocean-causeway.js +118 -0
  18. package/dist/scenes/generated/cassette-cathedral.d.ts +35 -0
  19. package/dist/scenes/generated/cassette-cathedral.js +121 -0
  20. package/dist/scenes/generated/clockwork-tidelocks.d.ts +35 -0
  21. package/dist/scenes/generated/clockwork-tidelocks.js +165 -0
  22. package/dist/scenes/generated/coilwell-descent.d.ts +35 -0
  23. package/dist/scenes/generated/coilwell-descent.js +156 -0
  24. package/dist/scenes/generated/copper-fungal-mesa.d.ts +35 -0
  25. package/dist/scenes/generated/copper-fungal-mesa.js +94 -0
  26. package/dist/scenes/generated/coral-signal-trench.d.ts +40 -0
  27. package/dist/scenes/generated/coral-signal-trench.js +136 -0
  28. package/dist/scenes/generated/counterweight-monastery.d.ts +35 -0
  29. package/dist/scenes/generated/counterweight-monastery.js +111 -0
  30. package/dist/scenes/generated/flooded-arcade.d.ts +35 -0
  31. package/dist/scenes/generated/flooded-arcade.js +144 -0
  32. package/dist/scenes/generated/fossil-sail-harbor.d.ts +35 -0
  33. package/dist/scenes/generated/fossil-sail-harbor.js +170 -0
  34. package/dist/scenes/generated/inverted-orchard.d.ts +35 -0
  35. package/dist/scenes/generated/inverted-orchard.js +100 -0
  36. package/dist/scenes/generated/kilnstep-foundry.d.ts +35 -0
  37. package/dist/scenes/generated/kilnstep-foundry.js +134 -0
  38. package/dist/scenes/generated/kitebone-viaduct.d.ts +35 -0
  39. package/dist/scenes/generated/kitebone-viaduct.js +167 -0
  40. package/dist/scenes/generated/lantern-moth-cloister.d.ts +35 -0
  41. package/dist/scenes/generated/lantern-moth-cloister.js +126 -0
  42. package/dist/scenes/generated/mesh-codec.d.ts +21 -0
  43. package/dist/scenes/generated/mesh-codec.js +46 -0
  44. package/dist/scenes/generated/midnight-platform-09.d.ts +40 -0
  45. package/dist/scenes/generated/midnight-platform-09.js +112 -0
  46. package/dist/scenes/generated/moonseed-conservatory.d.ts +35 -0
  47. package/dist/scenes/generated/moonseed-conservatory.js +134 -0
  48. package/dist/scenes/generated/neon-drift-interchange.d.ts +46 -0
  49. package/dist/scenes/generated/neon-drift-interchange.js +224 -0
  50. package/dist/scenes/generated/ochre-relay-ruins.d.ts +40 -0
  51. package/dist/scenes/generated/ochre-relay-ruins.js +111 -0
  52. package/dist/scenes/generated/paper-moon-parade.d.ts +40 -0
  53. package/dist/scenes/generated/paper-moon-parade.js +127 -0
  54. package/dist/scenes/generated/pocket-planet-orchard.d.ts +40 -0
  55. package/dist/scenes/generated/pocket-planet-orchard.js +90 -0
  56. package/dist/scenes/generated/prism-quarry.d.ts +35 -0
  57. package/dist/scenes/generated/prism-quarry.js +94 -0
  58. package/dist/scenes/generated/radio-observatory.d.ts +35 -0
  59. package/dist/scenes/generated/radio-observatory.js +120 -0
  60. package/dist/scenes/generated/signal-freightyard.d.ts +35 -0
  61. package/dist/scenes/generated/signal-freightyard.js +146 -0
  62. package/dist/scenes/generated/tape-terraces.d.ts +35 -0
  63. package/dist/scenes/generated/tape-terraces.js +96 -0
  64. package/dist/scenes/generated/vector-dojo-showdown.d.ts +46 -0
  65. package/dist/scenes/generated/vector-dojo-showdown.js +110 -0
  66. package/package.json +48 -0
package/dist/kit.js ADDED
@@ -0,0 +1,383 @@
1
+ import * as THREE from "three";
2
+ const vector = (point) => new THREE.Vector3(...point);
3
+ const legacyVertexShader = `
4
+ varying vec2 vUv;
5
+ varying vec3 vLight;
6
+ varying float vFog;
7
+ varying float vCap;
8
+ uniform vec2 fogRange;
9
+ void main() {
10
+ vec4 world = modelMatrix * vec4(position, 1.0);
11
+ vec4 view = viewMatrix * world;
12
+ vec3 normalWorld = normalize(mat3(modelMatrix) * normal);
13
+ float moon = abs(dot(normalWorld, normalize(vec3(0.45, 0.8, 0.35))));
14
+ vLight = vec3(0.48, 0.54, 0.62) + vec3(0.72, 0.71, 0.66) * moon;
15
+ vUv = uv;
16
+ vFog = clamp((-view.z - fogRange.x) / max(fogRange.y - fogRange.x, 0.001), 0.0, 1.0);
17
+ #ifdef USE_CAP
18
+ vCap = color.r;
19
+ #else
20
+ vCap = 0.0;
21
+ #endif
22
+ gl_Position = projectionMatrix * view;
23
+ }`;
24
+ const legacyFragmentShader = `
25
+ varying vec2 vUv;
26
+ varying vec3 vLight;
27
+ varying float vFog;
28
+ varying float vCap;
29
+ uniform sampler2D pattern;
30
+ uniform vec3 baseColor;
31
+ uniform vec3 capColor;
32
+ uniform vec3 fogColor;
33
+ uniform float emission;
34
+ void main() {
35
+ vec3 base = texture2D(pattern, vUv).rgb * baseColor;
36
+ vec3 color = base * (vLight + emission) + capColor * vCap * 1.1;
37
+ gl_FragColor = vec4(mix(color, fogColor, vFog), 1.0);
38
+ }`;
39
+ function numbers(value, count, name) {
40
+ if (!Array.isArray(value) || value.length !== count || value.some((n) => !Number.isFinite(n))) {
41
+ throw new Error(`${name} must contain ${count} finite numbers`);
42
+ }
43
+ return value;
44
+ }
45
+ function number(value, name) {
46
+ if (typeof value !== "number" || !Number.isFinite(value))
47
+ throw new Error(`${name} must be a finite number`);
48
+ return value;
49
+ }
50
+ function roleMaterial(role) {
51
+ return { role, kind: "legacy" };
52
+ }
53
+ function transform(position, rotation = [0, 0, 0]) {
54
+ const q = new THREE.Quaternion().setFromEuler(new THREE.Euler(...rotation));
55
+ return { position, quaternion: [q.x, q.y, q.z, q.w], scale: [1, 1, 1] };
56
+ }
57
+ export class Kit {
58
+ definition;
59
+ onTextureReady;
60
+ onTextureError;
61
+ scene = new THREE.Scene();
62
+ nodes = [];
63
+ geometries = new Set();
64
+ materials = new Map();
65
+ textures = new Set();
66
+ rolePattern;
67
+ animations = [];
68
+ disposed = false;
69
+ constructor(definition, onTextureReady, onTextureError) {
70
+ this.definition = definition;
71
+ this.onTextureReady = onTextureReady;
72
+ this.onTextureError = onTextureError;
73
+ this.scene.background = new THREE.Color(definition.palette.sky);
74
+ this.scene.fog = new THREE.Fog(definition.palette.fog, definition.fog[0], definition.fog[1]);
75
+ this.scene.add(new THREE.AmbientLight(0xc3d8ef, 1.15));
76
+ const sun = new THREE.DirectionalLight(0xffedca, 1.9);
77
+ sun.position.set(24, 60, 18);
78
+ this.scene.add(sun);
79
+ }
80
+ material(spec, vertexColors, hasUv) {
81
+ const color = spec.color ?? (spec.kind === "cutout" && spec.texture
82
+ ? "#ffffff" : this.definition.palette[spec.role ?? "body"]);
83
+ if (!color)
84
+ throw new Error(`Unknown material role: ${spec.role ?? "body"}`);
85
+ const opacity = spec.opacity ?? 1;
86
+ const key = JSON.stringify([spec.kind, color, opacity, spec.texture, spec.role, vertexColors, hasUv]);
87
+ const cached = this.materials.get(key);
88
+ if (cached)
89
+ return cached;
90
+ if (spec.kind === "legacy") {
91
+ const material = new THREE.ShaderMaterial({
92
+ vertexShader: legacyVertexShader,
93
+ fragmentShader: legacyFragmentShader,
94
+ uniforms: {
95
+ pattern: { value: this.roleTexture() },
96
+ baseColor: { value: new THREE.Color(color) },
97
+ capColor: { value: new THREE.Color(this.definition.palette.accent) },
98
+ fogColor: { value: new THREE.Color(this.definition.palette.fog) },
99
+ fogRange: { value: new THREE.Vector2(...this.definition.fog) },
100
+ emission: { value: spec.role === "accent" || spec.role === "light" ? 0.65 : 0 },
101
+ },
102
+ defines: vertexColors ? { USE_CAP: 1 } : {},
103
+ side: THREE.DoubleSide,
104
+ vertexColors,
105
+ });
106
+ this.materials.set(key, material);
107
+ return material;
108
+ }
109
+ const transparent = opacity < 1 || spec.kind === "card";
110
+ const options = {
111
+ color: vertexColors && spec.kind !== "painted" ? 0xffffff : color,
112
+ side: THREE.DoubleSide,
113
+ transparent,
114
+ opacity,
115
+ depthWrite: !transparent,
116
+ alphaTest: spec.kind === "cutout" ? 0.5 : 0,
117
+ vertexColors,
118
+ };
119
+ if (spec.kind === "painted") {
120
+ if (spec.role === "light" || spec.role === "accent") {
121
+ options.emissive = vertexColors ? 0x666666 : color;
122
+ options.emissiveIntensity = spec.role === "light" ? 0.42 : 0.24;
123
+ }
124
+ if (hasUv && !vertexColors && !spec.texture)
125
+ options.map = this.roleTexture();
126
+ }
127
+ if (spec.texture && hasUv) {
128
+ const texture = new THREE.TextureLoader().load(spec.texture, () => this.onTextureReady?.(), undefined, (error) => this.onTextureError?.(error));
129
+ texture.colorSpace = THREE.SRGBColorSpace;
130
+ texture.magFilter = THREE.NearestFilter;
131
+ texture.minFilter = THREE.NearestFilter;
132
+ this.textures.add(texture);
133
+ options.map = texture;
134
+ }
135
+ const material = spec.kind === "card" || spec.kind === "cutout"
136
+ ? new THREE.MeshBasicMaterial(options)
137
+ : new THREE.MeshLambertMaterial({ ...options, flatShading: true });
138
+ this.materials.set(key, material);
139
+ return material;
140
+ }
141
+ roleTexture() {
142
+ if (this.rolePattern)
143
+ return this.rolePattern;
144
+ const pixels = new Uint8Array(64 * 64 * 4);
145
+ for (let y = 0; y < 64; y++)
146
+ for (let x = 0; x < 64; x++) {
147
+ const shade = y % 16 < 2 ? 255 : (Math.floor(x / 8) + Math.floor(y / 8)) % 2 ? 245 : 214;
148
+ const offset = (y * 64 + x) * 4;
149
+ pixels[offset] = pixels[offset + 1] = pixels[offset + 2] = shade;
150
+ pixels[offset + 3] = 255;
151
+ }
152
+ const texture = new THREE.DataTexture(pixels, 64, 64);
153
+ texture.colorSpace = THREE.SRGBColorSpace;
154
+ texture.magFilter = THREE.NearestFilter;
155
+ texture.minFilter = THREE.NearestFilter;
156
+ texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
157
+ texture.needsUpdate = true;
158
+ this.textures.add(texture);
159
+ this.rolePattern = texture;
160
+ return texture;
161
+ }
162
+ put(geometry, record, vertexColors = false) {
163
+ const hasUv = geometry.hasAttribute("uv");
164
+ const mesh = new THREE.Mesh(geometry, this.material(record.material, vertexColors, hasUv));
165
+ const pose = record.transform;
166
+ mesh.position.set(...pose.position);
167
+ mesh.quaternion.set(...pose.quaternion);
168
+ mesh.scale.set(...pose.scale);
169
+ mesh.visible = record.visible ?? true;
170
+ this.scene.add(mesh);
171
+ this.geometries.add(geometry);
172
+ const handle = { id: this.nodes.length };
173
+ this.nodes.push(mesh);
174
+ if (record.motion)
175
+ this.animations.push({ mesh, samples: record.motion });
176
+ return handle;
177
+ }
178
+ addPrimitive(record) {
179
+ const args = record.args;
180
+ let geometry;
181
+ switch (record.kind) {
182
+ case "box": {
183
+ const size = numbers(args[0], 3, "box size");
184
+ geometry = new THREE.BoxGeometry(size[0], size[1], size[2]);
185
+ break;
186
+ }
187
+ case "cylinder": {
188
+ const bottom = number(args[0], "bottom radius");
189
+ const top = number(args[1], "top radius");
190
+ const height = number(args[2], "cylinder height");
191
+ const segments = number(args[3], "cylinder segments");
192
+ geometry = new THREE.CylinderGeometry(top, bottom, height, segments, 1);
193
+ break;
194
+ }
195
+ case "sphere": {
196
+ const radius = number(args[0], "sphere radius");
197
+ const rings = number(args[1], "sphere rings");
198
+ const segments = number(args[2], "sphere segments");
199
+ geometry = new THREE.SphereGeometry(radius, segments, rings);
200
+ break;
201
+ }
202
+ case "torus": {
203
+ const inner = number(args[0], "torus inner radius");
204
+ const outer = number(args[1], "torus outer radius");
205
+ const rings = number(args[2], "torus rings");
206
+ const segments = number(args[3], "torus segments");
207
+ geometry = new THREE.TorusGeometry((inner + outer) / 2, (outer - inner) / 2, rings, segments);
208
+ break;
209
+ }
210
+ case "quad": {
211
+ const size = numbers(args[0], 2, "quad size");
212
+ geometry = new THREE.PlaneGeometry(size[0], size[1]);
213
+ break;
214
+ }
215
+ default:
216
+ throw new Error(`Unknown primitive kind: ${String(record.kind)}`);
217
+ }
218
+ return this.put(geometry, record);
219
+ }
220
+ addMesh(record) {
221
+ if (record.positions.length < 9 || record.positions.length % 3 !== 0) {
222
+ throw new Error("Mesh positions must contain at least one triangle");
223
+ }
224
+ if (record.normals?.length && record.normals.length !== record.positions.length) {
225
+ throw new Error("Mesh normals must match positions");
226
+ }
227
+ if (record.uvs?.length && record.uvs.length !== record.positions.length / 3 * 2) {
228
+ throw new Error("Mesh UVs must match positions");
229
+ }
230
+ if (record.colors?.length && record.colors.length !== record.positions.length / 3 * 4) {
231
+ throw new Error("Mesh colors must have four channels per vertex");
232
+ }
233
+ if (record.indices && (record.indices.length % 3 !== 0 || record.indices.some((index) => index < 0 || index >= record.positions.length / 3))) {
234
+ throw new Error("Mesh indices must reference triangles within the vertex array");
235
+ }
236
+ const geometry = new THREE.BufferGeometry();
237
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(record.positions, 3));
238
+ if (record.indices?.length)
239
+ geometry.setIndex(record.indices);
240
+ if (record.normals?.length === record.positions.length) {
241
+ geometry.setAttribute("normal", new THREE.Float32BufferAttribute(record.normals, 3));
242
+ }
243
+ else {
244
+ geometry.computeVertexNormals();
245
+ }
246
+ if (record.uvs?.length === record.positions.length / 3 * 2) {
247
+ geometry.setAttribute("uv", new THREE.Float32BufferAttribute(record.uvs, 2));
248
+ }
249
+ const vertexColors = record.colors?.length === record.positions.length / 3 * 4;
250
+ if (vertexColors && record.colors) {
251
+ geometry.setAttribute("color", new THREE.Float32BufferAttribute(record.colors, 4));
252
+ }
253
+ return this.put(geometry, record, vertexColors);
254
+ }
255
+ box({ position, size, material, rotation }) {
256
+ return this.addPrimitive({ kind: "box", args: [[...size]], transform: transform(position, rotation), material: roleMaterial(material) });
257
+ }
258
+ cylinder({ position, radius, height, material, segments = 8, topRadius = radius }) {
259
+ return this.addPrimitive({ kind: "cylinder", args: [radius, topRadius, height, segments], transform: transform(position), material: roleMaterial(material) });
260
+ }
261
+ sphere({ position, radius, material, rings = 4, segments = 8 }) {
262
+ return this.addPrimitive({ kind: "sphere", args: [radius, rings, segments], transform: transform(position), material: roleMaterial(material) });
263
+ }
264
+ torus({ position, inner, outer, material, rotation }) {
265
+ return this.addPrimitive({ kind: "torus", args: [inner, outer, 4, 16], transform: transform(position, rotation), material: roleMaterial(material) });
266
+ }
267
+ beam({ from, to, width, material }) {
268
+ const start = vector(from);
269
+ const end = vector(to);
270
+ const length = start.distanceTo(end);
271
+ if (length < 0.001)
272
+ throw new Error("Zero-length beam");
273
+ const center = start.clone().add(end).multiplyScalar(0.5);
274
+ const quaternion = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), end.sub(start).normalize());
275
+ return this.addPrimitive({
276
+ kind: "box", args: [[width, length, width]],
277
+ transform: { position: [center.x, center.y, center.z], quaternion: [quaternion.x, quaternion.y, quaternion.z, quaternion.w], scale: [1, 1, 1] },
278
+ material: roleMaterial(material),
279
+ });
280
+ }
281
+ island({ position, radius, height = 1, depth = 1, phase = [0, 0] }) {
282
+ const edge = [];
283
+ const top = (x, z, ratio) => (1.5 * (1 - ratio * ratio) + 0.45 * Math.sin(x * 0.52 + phase[0]) * Math.cos(z * 0.47 + phase[1])) * height;
284
+ for (let i = 0; i < 24; i++) {
285
+ const angle = i * Math.PI / 12;
286
+ const r = radius * (1 + 0.07 * Math.sin(3 * angle + phase[0]) + 0.045 * Math.sin(5 * angle + phase[1]));
287
+ const x = Math.cos(angle) * r;
288
+ const z = Math.sin(angle) * r;
289
+ edge.push([x, top(x, z, 1), z]);
290
+ }
291
+ const positions = [];
292
+ const uvs = [];
293
+ const triangle = (a, b, c) => {
294
+ for (const p of [a, b, c]) {
295
+ positions.push(...p);
296
+ uvs.push(p[0] / 8, p[2] / 8);
297
+ }
298
+ };
299
+ for (let i = 0; i < edge.length; i++) {
300
+ const a = edge[i];
301
+ const b = edge[(i + 1) % edge.length];
302
+ const midA = [a[0] * 0.46, top(a[0] * 0.46, a[2] * 0.46, 0.46), a[2] * 0.46];
303
+ const midB = [b[0] * 0.46, top(b[0] * 0.46, b[2] * 0.46, 0.46), b[2] * 0.46];
304
+ triangle([0, 1.25 * height, 0], midA, midB);
305
+ triangle(midA, a, b);
306
+ triangle(midA, b, midB);
307
+ const bottomA = [a[0] * 0.18, -radius * depth * 0.72, a[2] * 0.18];
308
+ const bottomB = [b[0] * 0.18, -radius * depth * 0.72, b[2] * 0.18];
309
+ triangle(a, bottomA, b);
310
+ triangle(b, bottomA, bottomB);
311
+ }
312
+ return this.addMesh({ positions, uvs, transform: transform(position), material: roleMaterial("body") });
313
+ }
314
+ ribbonArch({ position, radius, width, material }) {
315
+ const pieces = [];
316
+ for (let i = 0; i < 12; i++) {
317
+ const a = Math.PI * i / 12;
318
+ const b = Math.PI * (i + 1) / 12;
319
+ pieces.push(this.beam({
320
+ from: [position[0] + Math.cos(a) * radius, position[1] + Math.sin(a) * radius, position[2]],
321
+ to: [position[0] + Math.cos(b) * radius, position[1] + Math.sin(b) * radius, position[2]],
322
+ width, material,
323
+ }));
324
+ }
325
+ return pieces;
326
+ }
327
+ rotate(node, { axis, speed }) {
328
+ const mesh = this.nodes[node.id];
329
+ if (!mesh)
330
+ throw new Error("Scene node belongs to a different or disposed kit");
331
+ this.animations.push({ mesh, axis: vector(axis).normalize(), speed, base: mesh.quaternion.clone() });
332
+ }
333
+ setTime(seconds) {
334
+ const sampleTime = 8 - Math.abs((seconds % 16 + 16) % 16 - 8);
335
+ for (const animation of this.animations) {
336
+ if (animation.samples) {
337
+ const samples = animation.samples;
338
+ if (samples.times.length === 0)
339
+ continue;
340
+ let right = samples.times.findIndex((time) => time >= sampleTime);
341
+ if (right < 0)
342
+ right = samples.times.length - 1;
343
+ const left = Math.max(0, right - 1);
344
+ const span = samples.times[right] - samples.times[left];
345
+ const blend = span > 0 ? (sampleTime - samples.times[left]) / span : 0;
346
+ const a = samples.positions[left];
347
+ const b = samples.positions[right];
348
+ if (a && b)
349
+ animation.mesh.position.copy(vector(a).lerp(vector(b), blend));
350
+ const qa = samples.quaternions[left];
351
+ const qb = samples.quaternions[right];
352
+ if (qa && qb)
353
+ animation.mesh.quaternion.copy(new THREE.Quaternion(...qa).slerp(new THREE.Quaternion(...qb), blend));
354
+ const sa = samples.scales[left];
355
+ const sb = samples.scales[right];
356
+ if (sa && sb)
357
+ animation.mesh.scale.copy(vector(sa).lerp(vector(sb), blend));
358
+ animation.mesh.visible = samples.visible[left] ?? true;
359
+ }
360
+ else if (animation.axis && animation.base && animation.speed !== undefined) {
361
+ animation.mesh.quaternion.copy(animation.base).premultiply(new THREE.Quaternion().setFromAxisAngle(animation.axis, seconds * animation.speed));
362
+ }
363
+ }
364
+ }
365
+ dispose() {
366
+ if (this.disposed)
367
+ return;
368
+ this.disposed = true;
369
+ this.scene.remove(...this.nodes);
370
+ for (const geometry of this.geometries)
371
+ geometry.dispose();
372
+ for (const material of this.materials.values())
373
+ material.dispose();
374
+ for (const texture of this.textures)
375
+ texture.dispose();
376
+ this.geometries.clear();
377
+ this.materials.clear();
378
+ this.textures.clear();
379
+ this.rolePattern = undefined;
380
+ this.nodes.length = 0;
381
+ this.animations.length = 0;
382
+ }
383
+ }
@@ -0,0 +1,20 @@
1
+ import type { SceneDefinition } from "./scene.js";
2
+ export type MotionMode = "ambient" | "fixed" | "cinematic" | "still";
3
+ export type QualityMode = "auto" | "high" | "low";
4
+ export type BackgroundState = "running" | "paused" | "fallback" | "disposed";
5
+ export interface BackgroundOptions {
6
+ element?: HTMLElement | string;
7
+ scene: SceneDefinition;
8
+ motion?: MotionMode;
9
+ quality?: QualityMode;
10
+ camera?: "survey" | "detail";
11
+ speed?: number;
12
+ }
13
+ export interface BackgroundHandle {
14
+ readonly state: BackgroundState;
15
+ readonly canvas: HTMLCanvasElement;
16
+ setMotion(mode: MotionMode): void;
17
+ setQuality(mode: QualityMode): void;
18
+ dispose(): void;
19
+ }
20
+ export declare function createBackground(options: BackgroundOptions): BackgroundHandle;