@idle-screens/schema 1.0.0 → 2.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.
- package/FORMAT.md +135 -0
- package/dist/index.d.ts +167 -4
- package/dist/index.js +778 -200
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
- package/saver-spec.schema.json +287 -0
package/dist/index.js
CHANGED
|
@@ -8,8 +8,20 @@ var LIMITS = {
|
|
|
8
8
|
// px/sec — bounds motion so nothing teleports
|
|
9
9
|
maxPulseAmp: 0.5,
|
|
10
10
|
// opacity breathing amplitude cap
|
|
11
|
-
minPulsePeriod: 500
|
|
11
|
+
minPulsePeriod: 500,
|
|
12
12
|
// ms — caps pulse at 2 Hz (WCAG flash threshold is 3 Hz)
|
|
13
|
+
maxSpin: 360,
|
|
14
|
+
// degrees/sec — one full revolution per second
|
|
15
|
+
maxGrowAmp: 0.8,
|
|
16
|
+
// size breathing amplitude cap (fraction of base size)
|
|
17
|
+
maxOrbitSpeed: 180,
|
|
18
|
+
// degrees/sec — half a revolution per second
|
|
19
|
+
maxLinksK: 8,
|
|
20
|
+
maxLinkLayerCount: 200,
|
|
21
|
+
minCyclePeriod: 500,
|
|
22
|
+
// ms — same flash-safety floor as pulse
|
|
23
|
+
referenceViewport: 1080
|
|
24
|
+
// for validating viewport-unit dimensional caps
|
|
13
25
|
};
|
|
14
26
|
|
|
15
27
|
// src/validate.ts
|
|
@@ -29,6 +41,9 @@ function validateSpec(spec) {
|
|
|
29
41
|
if (spec.motionIntensity !== void 0 && !["calm", "moderate", "energetic"].includes(spec.motionIntensity)) {
|
|
30
42
|
err("motionIntensity", "must be calm | moderate | energetic");
|
|
31
43
|
}
|
|
44
|
+
if (spec.units !== void 0 && spec.units !== "px" && spec.units !== "viewport") {
|
|
45
|
+
err("units", "must be 'px' | 'viewport'");
|
|
46
|
+
}
|
|
32
47
|
if (spec.background !== void 0) validateBackground(spec.background, err);
|
|
33
48
|
if (!Array.isArray(spec.layers) || spec.layers.length === 0) {
|
|
34
49
|
err("layers", "must be a non-empty array");
|
|
@@ -37,7 +52,7 @@ function validateSpec(spec) {
|
|
|
37
52
|
let total = 0;
|
|
38
53
|
spec.layers.forEach((layer, i) => {
|
|
39
54
|
total += isObj(layer) && isNum(layer.count) ? layer.count : 0;
|
|
40
|
-
validateLayer(layer, `layers[${i}]`, err);
|
|
55
|
+
validateLayer(layer, `layers[${i}]`, err, spec);
|
|
41
56
|
});
|
|
42
57
|
if (total > LIMITS.maxTotal) err("layers", `total entities ${total} exceeds cap ${LIMITS.maxTotal}`);
|
|
43
58
|
}
|
|
@@ -70,7 +85,7 @@ function validateBackground(bg, err) {
|
|
|
70
85
|
err("background.type", "must be solid | gradient");
|
|
71
86
|
}
|
|
72
87
|
}
|
|
73
|
-
function validateLayer(layer, path, err) {
|
|
88
|
+
function validateLayer(layer, path, err, spec) {
|
|
74
89
|
if (!isObj(layer)) return err(path, "must be an object");
|
|
75
90
|
if (!isNum(layer.count) || !Number.isInteger(layer.count) || layer.count < 1) {
|
|
76
91
|
err(`${path}.count`, "must be a positive integer");
|
|
@@ -120,8 +135,44 @@ function validateLayer(layer, path, err) {
|
|
|
120
135
|
}
|
|
121
136
|
}
|
|
122
137
|
}
|
|
138
|
+
if (layer.spin !== void 0) {
|
|
139
|
+
if (!isNum(layer.spin)) err(`${path}.spin`, "must be a number (degrees/sec)");
|
|
140
|
+
else if (Math.abs(layer.spin) > LIMITS.maxSpin) err(`${path}.spin`, `must be within \xB1${LIMITS.maxSpin} deg/sec`);
|
|
141
|
+
}
|
|
142
|
+
if (layer.grow !== void 0) {
|
|
143
|
+
if (!isObj(layer.grow)) err(`${path}.grow`, "must be an object");
|
|
144
|
+
else {
|
|
145
|
+
if (!isNum(layer.grow.amp) || layer.grow.amp <= 0 || layer.grow.amp > LIMITS.maxGrowAmp) {
|
|
146
|
+
err(`${path}.grow.amp`, `must be within 0..${LIMITS.maxGrowAmp}`);
|
|
147
|
+
}
|
|
148
|
+
if (!isNum(layer.grow.period) || layer.grow.period < LIMITS.minPulsePeriod) {
|
|
149
|
+
err(`${path}.grow.period`, `must be >= ${LIMITS.minPulsePeriod} ms (flash-safety floor)`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (layer.links !== void 0) {
|
|
154
|
+
if (!isObj(layer.links)) err(`${path}.links`, "must be an object");
|
|
155
|
+
else {
|
|
156
|
+
if (!isNum(layer.links.k) || !Number.isInteger(layer.links.k) || layer.links.k < 1 || layer.links.k > LIMITS.maxLinksK) {
|
|
157
|
+
err(`${path}.links.k`, `must be an integer 1..${LIMITS.maxLinksK}`);
|
|
158
|
+
}
|
|
159
|
+
if (!isNum(layer.links.maxDist) || layer.links.maxDist <= 0) {
|
|
160
|
+
err(`${path}.links.maxDist`, "must be > 0");
|
|
161
|
+
}
|
|
162
|
+
if (layer.links.color !== void 0) color(layer.links.color, `${path}.links.color`, err);
|
|
163
|
+
if (layer.links.alpha !== void 0 && (!isNum(layer.links.alpha) || layer.links.alpha < 0 || layer.links.alpha > 1)) {
|
|
164
|
+
err(`${path}.links.alpha`, "must be 0..1");
|
|
165
|
+
}
|
|
166
|
+
if (layer.links.width !== void 0 && (!isNum(layer.links.width) || layer.links.width <= 0)) {
|
|
167
|
+
err(`${path}.links.width`, "must be > 0");
|
|
168
|
+
}
|
|
169
|
+
if (isNum(layer.count) && layer.count > LIMITS.maxLinkLayerCount) {
|
|
170
|
+
err(`${path}.links`, `layer count must be <= ${LIMITS.maxLinkLayerCount} when links is set`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
123
174
|
validateSprite(layer.sprite, `${path}.sprite`, err);
|
|
124
|
-
validateMotion(layer.motion, `${path}.motion`, err);
|
|
175
|
+
validateMotion(layer.motion, `${path}.motion`, err, spec);
|
|
125
176
|
}
|
|
126
177
|
function validateSprite(sprite, path, err) {
|
|
127
178
|
if (!isObj(sprite)) return err(path, "must be an object");
|
|
@@ -129,6 +180,7 @@ function validateSprite(sprite, path, err) {
|
|
|
129
180
|
if (!Array.isArray(sprite.glyphs) || sprite.glyphs.length === 0 || !sprite.glyphs.every(isStr)) {
|
|
130
181
|
err(`${path}.glyphs`, "must be a non-empty array of strings");
|
|
131
182
|
}
|
|
183
|
+
validateCycle(sprite, path, err);
|
|
132
184
|
} else if (sprite.kind === "text") {
|
|
133
185
|
if (!Array.isArray(sprite.strings) || sprite.strings.length === 0 || !sprite.strings.every(isStr)) {
|
|
134
186
|
err(`${path}.strings`, "must be a non-empty array of strings");
|
|
@@ -143,19 +195,36 @@ function validateSprite(sprite, path, err) {
|
|
|
143
195
|
if (sprite.maxWidth !== void 0 && (!isNum(sprite.maxWidth) || sprite.maxWidth <= 0)) {
|
|
144
196
|
err(`${path}.maxWidth`, "must be a positive number");
|
|
145
197
|
}
|
|
198
|
+
validateCycle(sprite, path, err);
|
|
146
199
|
} else if (sprite.kind === "circle") {
|
|
147
200
|
if (!isRange(sprite.radius) || sprite.radius[0] <= 0) err(`${path}.radius`, "must be a [min,max] range of positive px");
|
|
148
201
|
color(sprite.color, `${path}.color`, err);
|
|
149
202
|
if (sprite.soft !== void 0 && typeof sprite.soft !== "boolean") err(`${path}.soft`, "must be a boolean");
|
|
203
|
+
if (sprite.colors !== void 0) {
|
|
204
|
+
if (!Array.isArray(sprite.colors) || sprite.colors.length === 0) {
|
|
205
|
+
err(`${path}.colors`, "must be a non-empty array of hex colours");
|
|
206
|
+
} else {
|
|
207
|
+
sprite.colors.forEach((c, ci) => color(c, `${path}.colors[${ci}]`, err));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
150
210
|
} else {
|
|
151
211
|
err(`${path}.kind`, "must be emoji | text | circle");
|
|
152
212
|
}
|
|
153
213
|
}
|
|
154
|
-
function
|
|
214
|
+
function validateCycle(sprite, path, err) {
|
|
215
|
+
if (sprite.cycle === void 0) return;
|
|
216
|
+
if (!isObj(sprite.cycle)) return err(`${path}.cycle`, "must be an object");
|
|
217
|
+
if (!isNum(sprite.cycle.period) || sprite.cycle.period < LIMITS.minCyclePeriod) {
|
|
218
|
+
err(`${path}.cycle.period`, `must be >= ${LIMITS.minCyclePeriod} ms (flash-safety floor)`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function validateMotion(motion, path, err, spec) {
|
|
155
222
|
if (!isObj(motion)) return err(path, "must be an object");
|
|
223
|
+
const isViewport = isObj(spec) && spec.units === "viewport";
|
|
224
|
+
const speedCap = isViewport ? LIMITS.maxSpeed / LIMITS.referenceViewport : LIMITS.maxSpeed;
|
|
156
225
|
const speedOk = (v, p) => {
|
|
157
226
|
if (!isRange(v)) err(p, "must be a [min,max] range");
|
|
158
|
-
else if (v[1] >
|
|
227
|
+
else if (v[1] > speedCap) err(p, `speed exceeds cap ${isViewport ? speedCap.toFixed(2) + " viewport-units/sec" : LIMITS.maxSpeed + " px/sec"}`);
|
|
159
228
|
};
|
|
160
229
|
if (motion.type === "drift") {
|
|
161
230
|
speedOk(motion.speed, `${path}.speed`);
|
|
@@ -168,8 +237,20 @@ function validateMotion(motion, path, err) {
|
|
|
168
237
|
} else if (motion.type === "bounce") {
|
|
169
238
|
speedOk(motion.speed, `${path}.speed`);
|
|
170
239
|
} else if (motion.type === "static") {
|
|
240
|
+
} else if (motion.type === "orbit") {
|
|
241
|
+
if (!isRange(motion.speed)) err(`${path}.speed`, "must be a [min,max] range");
|
|
242
|
+
else if (Math.abs(motion.speed[0]) > LIMITS.maxOrbitSpeed || Math.abs(motion.speed[1]) > LIMITS.maxOrbitSpeed) err(`${path}.speed`, `orbit speed exceeds cap ${LIMITS.maxOrbitSpeed} deg/sec`);
|
|
243
|
+
if (!isRange(motion.radius) || motion.radius[0] <= 0) err(`${path}.radius`, "must be a [min,max] range of positive px");
|
|
244
|
+
if (motion.center !== void 0) {
|
|
245
|
+
if (!isObj(motion.center) || !isNum(motion.center.x) || !isNum(motion.center.y)) {
|
|
246
|
+
err(`${path}.center`, "must be {x, y} with numbers 0..1");
|
|
247
|
+
} else {
|
|
248
|
+
if (motion.center.x < 0 || motion.center.x > 1) err(`${path}.center.x`, "must be 0..1");
|
|
249
|
+
if (motion.center.y < 0 || motion.center.y > 1) err(`${path}.center.y`, "must be 0..1");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
171
252
|
} else {
|
|
172
|
-
err(`${path}.type`, "must be drift | rise | bounce | static");
|
|
253
|
+
err(`${path}.type`, "must be drift | rise | bounce | static | orbit");
|
|
173
254
|
}
|
|
174
255
|
}
|
|
175
256
|
function assertValidSpec(spec) {
|
|
@@ -187,12 +268,14 @@ function spriteVariants(sprite) {
|
|
|
187
268
|
if (sprite.kind === "text") return sprite.strings.length;
|
|
188
269
|
return 1;
|
|
189
270
|
}
|
|
190
|
-
function buildEntities(layer, rng, w, h) {
|
|
271
|
+
function buildEntities(layer, rng, w, h, scale = 1) {
|
|
191
272
|
const [smin, smax] = layer.size ?? [20, 40];
|
|
192
273
|
const variants = spriteVariants(layer.sprite);
|
|
274
|
+
const colorsLen = layer.sprite.kind === "circle" && layer.sprite.colors ? layer.sprite.colors.length : 0;
|
|
275
|
+
const cycle = layer.sprite.kind === "emoji" || layer.sprite.kind === "text" ? layer.sprite.cycle : void 0;
|
|
193
276
|
const out = [];
|
|
194
277
|
for (let i = 0; i < layer.count; i++) {
|
|
195
|
-
const size = layer.sprite.kind === "circle" ? rng.range(layer.sprite.radius[0], layer.sprite.radius[1]) * 2 : rng.range(smin, smax);
|
|
278
|
+
const size = layer.sprite.kind === "circle" ? rng.range(layer.sprite.radius[0], layer.sprite.radius[1]) * 2 * scale : rng.range(smin, smax) * scale;
|
|
196
279
|
let vx = 0;
|
|
197
280
|
let vy = 0;
|
|
198
281
|
let bob = 0;
|
|
@@ -201,22 +284,25 @@ function buildEntities(layer, rng, w, h) {
|
|
|
201
284
|
if (m.type === "static") {
|
|
202
285
|
motion = "static";
|
|
203
286
|
} else if (m.type === "drift") {
|
|
204
|
-
const s = rng.range(m.speed[0], m.speed[1]);
|
|
287
|
+
const s = rng.range(m.speed[0], m.speed[1]) * scale;
|
|
205
288
|
let angle = (m.angle ?? 0) * Math.PI / 180;
|
|
206
289
|
if (m.bidirectional && rng.next() < 0.5) angle = Math.PI - angle;
|
|
207
290
|
vx = s * Math.cos(angle);
|
|
208
291
|
vy = s * Math.sin(angle);
|
|
209
|
-
bob = m.bob ?? 0;
|
|
292
|
+
bob = (m.bob ?? 0) * scale;
|
|
210
293
|
} else if (m.type === "rise") {
|
|
211
294
|
motion = "rise";
|
|
212
|
-
vy = -rng.range(m.speed[0], m.speed[1]);
|
|
213
|
-
bob = m.sway ?? 0;
|
|
214
|
-
} else {
|
|
295
|
+
vy = -rng.range(m.speed[0], m.speed[1]) * scale;
|
|
296
|
+
bob = (m.sway ?? 0) * scale;
|
|
297
|
+
} else if (m.type === "bounce") {
|
|
215
298
|
motion = "bounce";
|
|
216
|
-
const s = rng.range(m.speed[0], m.speed[1]);
|
|
299
|
+
const s = rng.range(m.speed[0], m.speed[1]) * scale;
|
|
217
300
|
const a = rng.range(0, Math.PI * 2);
|
|
218
301
|
vx = s * Math.cos(a);
|
|
219
302
|
vy = s * Math.sin(a);
|
|
303
|
+
} else if (m.type === "orbit") {
|
|
304
|
+
motion = "orbit";
|
|
305
|
+
vx = rng.range(m.speed[0], m.speed[1]);
|
|
220
306
|
}
|
|
221
307
|
let x0;
|
|
222
308
|
let y0;
|
|
@@ -243,7 +329,20 @@ function buildEntities(layer, rng, w, h) {
|
|
|
243
329
|
alpha: layer.alpha ? rng.range(layer.alpha[0], layer.alpha[1]) : 1,
|
|
244
330
|
pulseAmp: layer.pulse?.amp ?? 0,
|
|
245
331
|
pulsePeriod: layer.pulse?.period ?? 1e3,
|
|
246
|
-
pulsePhase: layer.pulse ? rng.range(0, Math.PI * 2) : 0
|
|
332
|
+
pulsePhase: layer.pulse ? rng.range(0, Math.PI * 2) : 0,
|
|
333
|
+
// New draws AFTER pulsePhase — guarded so existing specs keep identical streams.
|
|
334
|
+
spinSpeed: layer.spin ? layer.spin * Math.PI / 180 : 0,
|
|
335
|
+
// deg/sec → rad/sec
|
|
336
|
+
spinPhase: layer.spin ? rng.range(0, Math.PI * 2) : 0,
|
|
337
|
+
growAmp: layer.grow?.amp ?? 0,
|
|
338
|
+
growPeriod: layer.grow?.period ?? 1e3,
|
|
339
|
+
growPhase: layer.grow ? rng.range(0, Math.PI * 2) : 0,
|
|
340
|
+
orbitR: m.type === "orbit" ? rng.range(m.radius[0], m.radius[1]) * scale : 0,
|
|
341
|
+
orbitCx: m.type === "orbit" ? (m.center?.x ?? 0.5) * w : 0,
|
|
342
|
+
orbitCy: m.type === "orbit" ? (m.center?.y ?? 0.5) * h : 0,
|
|
343
|
+
// New draws AFTER orbit — guarded so existing specs keep identical streams.
|
|
344
|
+
colorIndex: colorsLen > 0 ? rng.int(0, colorsLen - 1) : -1,
|
|
345
|
+
cyclePeriod: cycle ? cycle.period : 0
|
|
247
346
|
});
|
|
248
347
|
}
|
|
249
348
|
return out;
|
|
@@ -253,6 +352,15 @@ function alphaAt(e, t) {
|
|
|
253
352
|
const a = e.alpha + e.pulseAmp * Math.sin(t * 2 * Math.PI / e.pulsePeriod + e.pulsePhase);
|
|
254
353
|
return a < 0 ? 0 : a > 1 ? 1 : a;
|
|
255
354
|
}
|
|
355
|
+
function sizeAt(e, t) {
|
|
356
|
+
if (!e.growAmp) return e.size;
|
|
357
|
+
const s = e.size * (1 + e.growAmp * Math.sin(t * 2 * Math.PI / e.growPeriod + e.growPhase));
|
|
358
|
+
return s > 0 ? s : 0.1;
|
|
359
|
+
}
|
|
360
|
+
function rotationAt(e, t) {
|
|
361
|
+
if (!e.spinSpeed) return 0;
|
|
362
|
+
return e.spinPhase + e.spinSpeed * (t / 1e3);
|
|
363
|
+
}
|
|
256
364
|
function wrap(v, min, max) {
|
|
257
365
|
const range = max - min;
|
|
258
366
|
return ((v - min) % range + range) % range + min;
|
|
@@ -266,6 +374,14 @@ function reflect(v, min, max) {
|
|
|
266
374
|
}
|
|
267
375
|
function positionAt(e, t, w, h) {
|
|
268
376
|
if (e.motion === "static") return { x: e.x0, y: e.y0, flip: false };
|
|
377
|
+
if (e.motion === "orbit") {
|
|
378
|
+
const angle = e.phase + e.vx * Math.PI / 180 * (t / 1e3);
|
|
379
|
+
return {
|
|
380
|
+
x: e.orbitCx + e.orbitR * Math.cos(angle),
|
|
381
|
+
y: e.orbitCy + e.orbitR * Math.sin(angle),
|
|
382
|
+
flip: false
|
|
383
|
+
};
|
|
384
|
+
}
|
|
269
385
|
const dt = t / 1e3;
|
|
270
386
|
const m = e.size;
|
|
271
387
|
if (e.motion === "bounce") {
|
|
@@ -287,11 +403,145 @@ function positionAt(e, t, w, h) {
|
|
|
287
403
|
if (e.bob) y += e.bob * Math.sin(t / 500 + e.phase);
|
|
288
404
|
return { x, y, flip: e.headingLeft };
|
|
289
405
|
}
|
|
406
|
+
function spriteIndexAt(e, t, variants) {
|
|
407
|
+
if (!e.cyclePeriod || variants <= 1) return e.spriteIndex;
|
|
408
|
+
return Math.floor(t / e.cyclePeriod + e.phase / (2 * Math.PI)) % variants;
|
|
409
|
+
}
|
|
410
|
+
function linkPairs(positions, k, maxDist, doWrap, w, h) {
|
|
411
|
+
const n = positions.length;
|
|
412
|
+
const edges = /* @__PURE__ */ new Set();
|
|
413
|
+
const out = [];
|
|
414
|
+
for (let i = 0; i < n; i++) {
|
|
415
|
+
const pi = positions[i];
|
|
416
|
+
const neighbors = [];
|
|
417
|
+
for (let j = 0; j < n; j++) {
|
|
418
|
+
if (i === j) continue;
|
|
419
|
+
const pj = positions[j];
|
|
420
|
+
let dx = pj.x - pi.x;
|
|
421
|
+
let dy = pj.y - pi.y;
|
|
422
|
+
if (doWrap) {
|
|
423
|
+
if (Math.abs(dx) > w / 2) dx = dx > 0 ? dx - w : dx + w;
|
|
424
|
+
if (Math.abs(dy) > h / 2) dy = dy > 0 ? dy - h : dy + h;
|
|
425
|
+
}
|
|
426
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
427
|
+
if (dist <= maxDist) neighbors.push({ dist, j });
|
|
428
|
+
}
|
|
429
|
+
neighbors.sort((a, b) => a.dist - b.dist || a.j - b.j);
|
|
430
|
+
for (let ni = 0; ni < Math.min(k, neighbors.length); ni++) {
|
|
431
|
+
const j = neighbors[ni].j;
|
|
432
|
+
const lo = Math.min(i, j);
|
|
433
|
+
const hi = Math.max(i, j);
|
|
434
|
+
const key = `${lo}:${hi}`;
|
|
435
|
+
if (!edges.has(key)) {
|
|
436
|
+
edges.add(key);
|
|
437
|
+
out.push([lo, hi]);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
290
443
|
|
|
291
444
|
// src/compile.ts
|
|
292
445
|
import {
|
|
293
446
|
createRng
|
|
294
447
|
} from "@idle-screens/core";
|
|
448
|
+
|
|
449
|
+
// src/steer.ts
|
|
450
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
451
|
+
function resolveSpecPath(spec, path) {
|
|
452
|
+
if (!spec || typeof spec !== "object" || !path) return null;
|
|
453
|
+
const parts = path.split(".");
|
|
454
|
+
if (parts.some((p) => UNSAFE_KEYS.has(p))) return null;
|
|
455
|
+
const s = spec;
|
|
456
|
+
if (parts[0] !== "layers" && parts[0] !== "background" && Array.isArray(s.layers)) {
|
|
457
|
+
const idx = s.layers.findIndex((l) => l && l.key === parts[0]);
|
|
458
|
+
if (idx === -1) return null;
|
|
459
|
+
parts.splice(0, 1, "layers", String(idx));
|
|
460
|
+
}
|
|
461
|
+
let node = spec;
|
|
462
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
463
|
+
if (node === null || typeof node !== "object") return null;
|
|
464
|
+
node = node[parts[i]];
|
|
465
|
+
}
|
|
466
|
+
if (node === null || typeof node !== "object") return null;
|
|
467
|
+
const last = parts[parts.length - 1];
|
|
468
|
+
const key = Array.isArray(node) ? Number(last) : last;
|
|
469
|
+
const exists = Array.isArray(node) ? Number.isInteger(key) && key >= 0 && key < node.length : last in node;
|
|
470
|
+
return exists ? { parent: node, key } : null;
|
|
471
|
+
}
|
|
472
|
+
function applyDeltasToSpec(spec, deltas) {
|
|
473
|
+
const copy = JSON.parse(JSON.stringify(spec));
|
|
474
|
+
for (const d of deltas) {
|
|
475
|
+
const loc = resolveSpecPath(copy, d.path);
|
|
476
|
+
if (loc) loc.parent[loc.key] = d.value;
|
|
477
|
+
}
|
|
478
|
+
return copy;
|
|
479
|
+
}
|
|
480
|
+
var HEX2 = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
481
|
+
function hexToRgb(hex) {
|
|
482
|
+
const h = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
483
|
+
const n = parseInt(h.slice(1), 16);
|
|
484
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
485
|
+
}
|
|
486
|
+
function lerpHex(a, b, k) {
|
|
487
|
+
const ca = hexToRgb(a);
|
|
488
|
+
const cb = hexToRgb(b);
|
|
489
|
+
const mix = ca.map((v, i) => Math.round(v + (cb[i] - v) * k));
|
|
490
|
+
return `#${mix.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
491
|
+
}
|
|
492
|
+
function lerpSpec(from, to, k) {
|
|
493
|
+
const kk = Math.max(0, Math.min(1, k));
|
|
494
|
+
const walk = (a, b, key) => {
|
|
495
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
496
|
+
const v = a + (b - a) * kk;
|
|
497
|
+
return key === "count" ? Math.max(1, Math.round(v)) : v;
|
|
498
|
+
}
|
|
499
|
+
if (typeof a === "string" && typeof b === "string" && HEX2.test(a) && HEX2.test(b)) {
|
|
500
|
+
return lerpHex(a, b, kk);
|
|
501
|
+
}
|
|
502
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
503
|
+
return b.map((bv, i) => walk(a[i], bv, i));
|
|
504
|
+
}
|
|
505
|
+
if (a && b && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
|
|
506
|
+
const out = {};
|
|
507
|
+
for (const kName of Object.keys(b)) {
|
|
508
|
+
out[kName] = walk(a[kName], b[kName], kName);
|
|
509
|
+
}
|
|
510
|
+
return out;
|
|
511
|
+
}
|
|
512
|
+
return kk > 0 ? b : a;
|
|
513
|
+
};
|
|
514
|
+
return walk(from, to);
|
|
515
|
+
}
|
|
516
|
+
function easeSmooth(k) {
|
|
517
|
+
const c = Math.max(0, Math.min(1, k));
|
|
518
|
+
return c * c * (3 - 2 * c);
|
|
519
|
+
}
|
|
520
|
+
function structuralSignature(spec) {
|
|
521
|
+
return JSON.stringify([
|
|
522
|
+
spec.units,
|
|
523
|
+
spec.layers.map((l) => [
|
|
524
|
+
l.count,
|
|
525
|
+
l.size,
|
|
526
|
+
l.region,
|
|
527
|
+
l.position,
|
|
528
|
+
l.motion,
|
|
529
|
+
l.wrap,
|
|
530
|
+
l.flip,
|
|
531
|
+
l.alpha,
|
|
532
|
+
l.pulse,
|
|
533
|
+
l.spin,
|
|
534
|
+
l.grow,
|
|
535
|
+
l.sprite.kind,
|
|
536
|
+
l.sprite.kind === "circle" ? l.sprite.radius : void 0,
|
|
537
|
+
l.sprite.kind === "circle" ? l.sprite.colors?.length : void 0,
|
|
538
|
+
l.sprite.kind === "emoji" ? [l.sprite.glyphs.length, l.sprite.cycle?.period] : l.sprite.kind === "text" ? [l.sprite.strings.length, l.sprite.cycle?.period] : void 0
|
|
539
|
+
])
|
|
540
|
+
]);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/compile.ts
|
|
544
|
+
var DEFAULT_STEER_DUR = 1e3;
|
|
295
545
|
function hexToRgba(hex, alpha) {
|
|
296
546
|
const h = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
297
547
|
const n = parseInt(h.slice(1), 16);
|
|
@@ -310,12 +560,14 @@ function manifestFor(spec) {
|
|
|
310
560
|
reducedMotionFallback: "static",
|
|
311
561
|
// Flash-safe by construction: static background + bounded sprites, no strobe
|
|
312
562
|
// primitive. Proven by sampling a compiled spec through @idle-screens/validator.
|
|
313
|
-
a11y: { flashSafe: true }
|
|
563
|
+
a11y: { flashSafe: true },
|
|
564
|
+
workerReady: true
|
|
314
565
|
};
|
|
315
566
|
}
|
|
316
567
|
var SpecInstance = class {
|
|
317
568
|
constructor(spec, ctx) {
|
|
318
569
|
this.spec = spec;
|
|
570
|
+
this.effSpec = spec;
|
|
319
571
|
this.saverCtx = ctx;
|
|
320
572
|
this.seed = (spec.seed ?? ctx.seed) >>> 0 || 1;
|
|
321
573
|
let canvas;
|
|
@@ -354,6 +606,11 @@ var SpecInstance = class {
|
|
|
354
606
|
baseT = 0;
|
|
355
607
|
// elapsed logical time carried across pause/resume
|
|
356
608
|
lastT = 0;
|
|
609
|
+
/** The spec currently being rendered (base spec + any applied steering). */
|
|
610
|
+
effSpec;
|
|
611
|
+
/** Active glide between two resolved specs (live setParam/applyTrack). */
|
|
612
|
+
transition = null;
|
|
613
|
+
lastStructural = "";
|
|
357
614
|
sizeCanvas() {
|
|
358
615
|
const dpr = Math.min(this.saverCtx.dpr, 2);
|
|
359
616
|
this.canvas.width = Math.max(1, Math.round(this.w * dpr));
|
|
@@ -363,7 +620,9 @@ var SpecInstance = class {
|
|
|
363
620
|
/** (Re)seed and place all entities — deterministic for the seed + size. */
|
|
364
621
|
rebuild() {
|
|
365
622
|
const rng = createRng(this.seed);
|
|
366
|
-
|
|
623
|
+
const scale = this.effSpec.units === "viewport" ? Math.min(this.w, this.h) : 1;
|
|
624
|
+
this.layers = this.effSpec.layers.map((layer) => ({ layer, entities: buildEntities(layer, rng, this.w, this.h, scale) }));
|
|
625
|
+
this.lastStructural = structuralSignature(this.effSpec);
|
|
367
626
|
}
|
|
368
627
|
start() {
|
|
369
628
|
if (this.frameId !== null || typeof requestAnimationFrame === "undefined") return;
|
|
@@ -385,7 +644,7 @@ var SpecInstance = class {
|
|
|
385
644
|
}
|
|
386
645
|
drawBackground() {
|
|
387
646
|
const { ctx, w, h } = this;
|
|
388
|
-
const bg = this.
|
|
647
|
+
const bg = this.effSpec.background;
|
|
389
648
|
if (!bg || bg.type === "solid") {
|
|
390
649
|
ctx.fillStyle = bg?.color ?? "#05050a";
|
|
391
650
|
ctx.fillRect(0, 0, w, h);
|
|
@@ -397,57 +656,145 @@ var SpecInstance = class {
|
|
|
397
656
|
ctx.fillRect(0, 0, w, h);
|
|
398
657
|
if (bg.band) {
|
|
399
658
|
ctx.fillStyle = bg.band.color;
|
|
400
|
-
|
|
659
|
+
const bh = bg.band.height * (this.effSpec.units === "viewport" ? Math.min(w, h) : 1);
|
|
660
|
+
ctx.fillRect(0, h - bh, w, bh);
|
|
401
661
|
}
|
|
402
662
|
}
|
|
403
663
|
drawEntity(built, e, t) {
|
|
404
664
|
const { ctx } = this;
|
|
405
665
|
const p = positionAt(e, t, this.w, this.h);
|
|
406
666
|
const sprite = built.layer.sprite;
|
|
667
|
+
const sz = sizeAt(e, t);
|
|
668
|
+
const rot = rotationAt(e, t);
|
|
407
669
|
ctx.globalAlpha = alphaAt(e, t);
|
|
408
670
|
if (sprite.kind === "circle") {
|
|
409
|
-
const r =
|
|
671
|
+
const r = sz / 2;
|
|
672
|
+
const resolvedColor = sprite.colors?.[e.colorIndex] ?? sprite.color;
|
|
673
|
+
ctx.save();
|
|
674
|
+
if (rot) {
|
|
675
|
+
ctx.translate(p.x, p.y);
|
|
676
|
+
ctx.rotate(rot);
|
|
677
|
+
ctx.translate(-p.x, -p.y);
|
|
678
|
+
}
|
|
410
679
|
if (sprite.soft) {
|
|
411
680
|
const g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r);
|
|
412
|
-
g.addColorStop(0,
|
|
413
|
-
g.addColorStop(0.35, hexToRgba(
|
|
414
|
-
g.addColorStop(1, hexToRgba(
|
|
681
|
+
g.addColorStop(0, resolvedColor);
|
|
682
|
+
g.addColorStop(0.35, hexToRgba(resolvedColor, 0.75));
|
|
683
|
+
g.addColorStop(1, hexToRgba(resolvedColor, 0));
|
|
415
684
|
ctx.fillStyle = g;
|
|
416
685
|
} else {
|
|
417
|
-
ctx.fillStyle =
|
|
686
|
+
ctx.fillStyle = resolvedColor;
|
|
418
687
|
}
|
|
419
688
|
ctx.beginPath();
|
|
420
689
|
ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
|
|
421
690
|
ctx.fill();
|
|
691
|
+
ctx.restore();
|
|
422
692
|
return;
|
|
423
693
|
}
|
|
424
694
|
ctx.save();
|
|
425
695
|
ctx.translate(p.x, p.y);
|
|
696
|
+
if (rot) ctx.rotate(rot);
|
|
426
697
|
if (p.flip && built.layer.flip) ctx.scale(-1, 1);
|
|
427
698
|
ctx.textAlign = "center";
|
|
428
699
|
ctx.textBaseline = "middle";
|
|
429
700
|
if (sprite.kind === "emoji") {
|
|
430
|
-
|
|
431
|
-
ctx.
|
|
701
|
+
const idx = spriteIndexAt(e, t, sprite.glyphs.length);
|
|
702
|
+
ctx.font = `${sz}px serif`;
|
|
703
|
+
ctx.fillText(sprite.glyphs[idx] ?? sprite.glyphs[0], 0, 0);
|
|
432
704
|
} else {
|
|
433
705
|
ctx.textAlign = sprite.align ?? "center";
|
|
434
706
|
ctx.textBaseline = sprite.baseline ?? "middle";
|
|
435
|
-
ctx.font = sprite.font ?? `${
|
|
707
|
+
ctx.font = sprite.font ?? `${sz}px system-ui, sans-serif`;
|
|
436
708
|
ctx.fillStyle = sprite.color ?? "#e6e8ef";
|
|
437
|
-
const
|
|
438
|
-
|
|
709
|
+
const idx = spriteIndexAt(e, t, sprite.strings.length);
|
|
710
|
+
const text = sprite.strings[idx] ?? sprite.strings[0];
|
|
711
|
+
const mw = sprite.maxWidth ? sprite.maxWidth * (this.effSpec.units === "viewport" ? Math.min(this.w, this.h) : 1) : void 0;
|
|
712
|
+
if (mw) ctx.fillText(text, 0, 0, mw);
|
|
439
713
|
else ctx.fillText(text, 0, 0);
|
|
440
714
|
}
|
|
441
715
|
ctx.restore();
|
|
442
716
|
}
|
|
717
|
+
/** Advance any live steering glide; rebuild entities on structural change. */
|
|
718
|
+
stepTransition(t) {
|
|
719
|
+
const tr = this.transition;
|
|
720
|
+
if (!tr) return;
|
|
721
|
+
const k = tr.dur <= 0 ? 1 : (t - tr.startT) / tr.dur;
|
|
722
|
+
this.effSpec = k >= 1 ? tr.to : lerpSpec(tr.from, tr.to, easeSmooth(k));
|
|
723
|
+
if (k >= 1) this.transition = null;
|
|
724
|
+
const sig = structuralSignature(this.effSpec);
|
|
725
|
+
if (sig !== this.lastStructural) this.rebuild();
|
|
726
|
+
else this.layers.forEach((b, i) => {
|
|
727
|
+
b.layer = this.effSpec.layers[i] ?? b.layer;
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Live steering: glide from what's currently rendered to the track's target
|
|
732
|
+
* state (deltas applied last-wins). Duration = the longest delta `dur`.
|
|
733
|
+
*/
|
|
734
|
+
applyTrack(track) {
|
|
735
|
+
const deltas = track?.deltas ?? [];
|
|
736
|
+
const target = applyDeltasToSpec(this.effSpec, deltas);
|
|
737
|
+
if (!validateSpec(target).valid) return;
|
|
738
|
+
const dur = deltas.length ? deltas.reduce((m, d) => Math.max(m, d.dur ?? DEFAULT_STEER_DUR), 0) : DEFAULT_STEER_DUR;
|
|
739
|
+
this.transition = {
|
|
740
|
+
from: JSON.parse(JSON.stringify(this.effSpec)),
|
|
741
|
+
to: target,
|
|
742
|
+
startT: this.lastT,
|
|
743
|
+
dur
|
|
744
|
+
};
|
|
745
|
+
if (this.paused) {
|
|
746
|
+
this.transition = null;
|
|
747
|
+
this.effSpec = target;
|
|
748
|
+
if (structuralSignature(this.effSpec) !== this.lastStructural) this.rebuild();
|
|
749
|
+
else this.layers.forEach((b, i) => {
|
|
750
|
+
b.layer = this.effSpec.layers[i] ?? b.layer;
|
|
751
|
+
});
|
|
752
|
+
this.renderFrame(this.lastT, this.seed);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
drawLinks(built, t) {
|
|
756
|
+
const { links } = built.layer;
|
|
757
|
+
if (!links) return;
|
|
758
|
+
const { ctx } = this;
|
|
759
|
+
const wrap2 = built.layer.wrap !== false;
|
|
760
|
+
const positions = built.entities.map((e) => positionAt(e, t, this.w, this.h));
|
|
761
|
+
const pairs = linkPairs(positions, links.k, links.maxDist * (this.effSpec.units === "viewport" ? Math.min(this.w, this.h) : 1), wrap2, this.w, this.h);
|
|
762
|
+
const lw = (links.width ?? 1) * (this.effSpec.units === "viewport" ? Math.min(this.w, this.h) : 1);
|
|
763
|
+
ctx.lineWidth = lw;
|
|
764
|
+
for (const [i, j] of pairs) {
|
|
765
|
+
const pi = positions[i];
|
|
766
|
+
const pj = positions[j];
|
|
767
|
+
const ei = built.entities[i];
|
|
768
|
+
let resolvedColor = links.color;
|
|
769
|
+
if (!resolvedColor) {
|
|
770
|
+
const sprite = built.layer.sprite;
|
|
771
|
+
if (sprite.kind === "circle") resolvedColor = sprite.colors?.[ei.colorIndex] ?? sprite.color;
|
|
772
|
+
else resolvedColor = "#e6e8ef";
|
|
773
|
+
}
|
|
774
|
+
ctx.globalAlpha = links.alpha ?? alphaAt(ei, t);
|
|
775
|
+
ctx.strokeStyle = resolvedColor;
|
|
776
|
+
let dx = pj.x - pi.x;
|
|
777
|
+
let dy = pj.y - pi.y;
|
|
778
|
+
if (wrap2) {
|
|
779
|
+
if (Math.abs(dx) > this.w / 2) dx = dx > 0 ? dx - this.w : dx + this.w;
|
|
780
|
+
if (Math.abs(dy) > this.h / 2) dy = dy > 0 ? dy - this.h : dy + this.h;
|
|
781
|
+
}
|
|
782
|
+
ctx.beginPath();
|
|
783
|
+
ctx.moveTo(pi.x, pi.y);
|
|
784
|
+
ctx.lineTo(pi.x + dx, pi.y + dy);
|
|
785
|
+
ctx.stroke();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
443
788
|
/** Deterministic, frame-addressable render (shared by the rAF loop). */
|
|
444
789
|
renderFrame(t, _seed) {
|
|
790
|
+
this.stepTransition(t);
|
|
445
791
|
const { ctx } = this;
|
|
446
792
|
ctx.globalAlpha = 1;
|
|
447
793
|
ctx.globalCompositeOperation = "source-over";
|
|
448
794
|
this.drawBackground();
|
|
449
795
|
for (const built of this.layers) {
|
|
450
796
|
ctx.globalCompositeOperation = built.layer.blend ?? "source-over";
|
|
797
|
+
this.drawLinks(built, t);
|
|
451
798
|
for (const e of built.entities) this.drawEntity(built, e, t);
|
|
452
799
|
}
|
|
453
800
|
ctx.globalAlpha = 1;
|
|
@@ -483,7 +830,77 @@ function compileSaver(spec) {
|
|
|
483
830
|
};
|
|
484
831
|
}
|
|
485
832
|
|
|
486
|
-
// src/
|
|
833
|
+
// src/advise.ts
|
|
834
|
+
import { createRng as createRng2 } from "@idle-screens/core";
|
|
835
|
+
function adviseSpec(spec, viewport = { width: 1920, height: 1080 }) {
|
|
836
|
+
const warnings = [];
|
|
837
|
+
const w = viewport.width;
|
|
838
|
+
const h = viewport.height;
|
|
839
|
+
const scale = spec.units === "viewport" ? Math.min(w, h) : 1;
|
|
840
|
+
const rng = createRng2(spec.seed ?? 42);
|
|
841
|
+
const allEntities = spec.layers.map((l) => buildEntities(l, rng, w, h, scale));
|
|
842
|
+
let totalEntities = 0;
|
|
843
|
+
let textLayerCount = 0;
|
|
844
|
+
let motionLayerCount = 0;
|
|
845
|
+
for (let li = 0; li < spec.layers.length; li++) {
|
|
846
|
+
const layer = spec.layers[li];
|
|
847
|
+
const entities = allEntities[li];
|
|
848
|
+
totalEntities += entities.length;
|
|
849
|
+
const isText = layer.sprite.kind === "text";
|
|
850
|
+
const isEmoji = layer.sprite.kind === "emoji";
|
|
851
|
+
const isStaticText = isText && layer.motion.type === "static";
|
|
852
|
+
if (isStaticText) textLayerCount++;
|
|
853
|
+
if (layer.motion.type !== "static") motionLayerCount++;
|
|
854
|
+
if (layer.sprite.kind === "circle") {
|
|
855
|
+
const maxR = entities.reduce((m, e) => Math.max(m, e.size / 2), 0);
|
|
856
|
+
const maxAlpha = entities.reduce((m, e) => Math.max(m, e.alpha), 0);
|
|
857
|
+
if (maxR * maxAlpha < 0.05) {
|
|
858
|
+
warnings.push({
|
|
859
|
+
path: `layers[${li}]`,
|
|
860
|
+
code: "invisible-layer",
|
|
861
|
+
message: `layer's max visible radius (${maxR.toFixed(1)}px \xD7 ${maxAlpha.toFixed(2)} alpha) is below perceptual floor`
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
let totalCoverage = 0;
|
|
867
|
+
for (let li = 0; li < spec.layers.length; li++) {
|
|
868
|
+
const layer = spec.layers[li];
|
|
869
|
+
const entities = allEntities[li];
|
|
870
|
+
for (const e of entities) {
|
|
871
|
+
const r = e.size / 2;
|
|
872
|
+
const pixArea = layer.sprite.kind === "circle" ? Math.PI * r * r : e.size * e.size;
|
|
873
|
+
totalCoverage += pixArea * e.alpha / (w * h);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (totalCoverage < 5e-4 && spec.layers.length > 0) {
|
|
877
|
+
warnings.push({
|
|
878
|
+
path: "layers",
|
|
879
|
+
code: "sparse-scene",
|
|
880
|
+
message: `alpha-weighted coverage is ${(totalCoverage * 100).toFixed(4)}% \u2014 scene will look empty on most displays`
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (totalEntities > 500) {
|
|
884
|
+
warnings.push({
|
|
885
|
+
path: "layers",
|
|
886
|
+
code: "dense-scene",
|
|
887
|
+
message: `${totalEntities} entities \u2014 scene may feel crowded and hurt performance on low-end devices`
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
if (spec.layers.length > 0 && textLayerCount > 0 && motionLayerCount === 0) {
|
|
891
|
+
const textRatio = textLayerCount / spec.layers.length;
|
|
892
|
+
if (textRatio >= 0.8) {
|
|
893
|
+
warnings.push({
|
|
894
|
+
path: "layers",
|
|
895
|
+
code: "text-heavy",
|
|
896
|
+
message: "most layers are static text \u2014 reads as a document, not a screensaver"
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return warnings;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/examples/aquarium.ts
|
|
487
904
|
var AQUARIUM_SPEC = {
|
|
488
905
|
schemaVersion: 1,
|
|
489
906
|
id: "aquarium",
|
|
@@ -514,185 +931,53 @@ var AQUARIUM_SPEC = {
|
|
|
514
931
|
}
|
|
515
932
|
]
|
|
516
933
|
};
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
label: "Rain",
|
|
521
|
-
seed: 7,
|
|
522
|
-
motionIntensity: "moderate",
|
|
523
|
-
background: { type: "solid", color: "#05070a" },
|
|
524
|
-
layers: [
|
|
525
|
-
{
|
|
526
|
-
count: 140,
|
|
527
|
-
sprite: { kind: "text", strings: ["\u2502"], color: "#9fd0ff" },
|
|
528
|
-
size: [12, 26],
|
|
529
|
-
motion: { type: "drift", angle: 90, speed: [520, 900] }
|
|
530
|
-
}
|
|
531
|
-
]
|
|
532
|
-
};
|
|
533
|
-
var SNOWFALL_SPEC = {
|
|
534
|
-
schemaVersion: 1,
|
|
535
|
-
id: "snowfall",
|
|
536
|
-
label: "Snowfall",
|
|
537
|
-
seed: 12,
|
|
538
|
-
motionIntensity: "calm",
|
|
539
|
-
background: {
|
|
540
|
-
type: "gradient",
|
|
541
|
-
stops: [
|
|
542
|
-
{ at: 0, color: "#0a1628" },
|
|
543
|
-
{ at: 0.7, color: "#0d1f3c" },
|
|
544
|
-
{ at: 1, color: "#121212" }
|
|
545
|
-
]
|
|
546
|
-
},
|
|
547
|
-
layers: [
|
|
548
|
-
{
|
|
549
|
-
count: 50,
|
|
550
|
-
sprite: { kind: "circle", radius: [1, 2.5], color: "#8899aa" },
|
|
551
|
-
motion: { type: "drift", angle: 90, speed: [15, 40], bob: 3 }
|
|
552
|
-
},
|
|
553
|
-
{
|
|
554
|
-
count: 35,
|
|
555
|
-
sprite: { kind: "circle", radius: [2, 4.5], color: "#c0cdd8" },
|
|
556
|
-
motion: { type: "drift", angle: 85, speed: [35, 80], bob: 6 }
|
|
557
|
-
},
|
|
558
|
-
{
|
|
559
|
-
count: 15,
|
|
560
|
-
sprite: { kind: "circle", radius: [3.5, 7], color: "#e8eff5" },
|
|
561
|
-
motion: { type: "drift", angle: 82, speed: [55, 120], bob: 10 }
|
|
562
|
-
}
|
|
563
|
-
]
|
|
564
|
-
};
|
|
565
|
-
var LANTERNS_SPEC = {
|
|
934
|
+
|
|
935
|
+
// src/examples/constellation.ts
|
|
936
|
+
var CONSTELLATION_SPEC = {
|
|
566
937
|
schemaVersion: 1,
|
|
567
|
-
id: "
|
|
568
|
-
label: "
|
|
569
|
-
seed:
|
|
938
|
+
id: "constellation",
|
|
939
|
+
label: "Constellation",
|
|
940
|
+
seed: 73,
|
|
570
941
|
motionIntensity: "calm",
|
|
571
942
|
background: {
|
|
572
943
|
type: "gradient",
|
|
573
944
|
stops: [
|
|
574
|
-
{ at: 0, color: "#
|
|
575
|
-
{ at: 0.
|
|
576
|
-
{ at:
|
|
577
|
-
{ at: 1, color: "#472518" }
|
|
945
|
+
{ at: 0, color: "#020212" },
|
|
946
|
+
{ at: 0.5, color: "#050520" },
|
|
947
|
+
{ at: 1, color: "#0a0830" }
|
|
578
948
|
]
|
|
579
949
|
},
|
|
580
950
|
layers: [
|
|
951
|
+
// Distant static stars (no links, just atmosphere)
|
|
581
952
|
{
|
|
582
|
-
count:
|
|
583
|
-
sprite: { kind: "circle", radius: [
|
|
584
|
-
alpha: [0.
|
|
585
|
-
|
|
586
|
-
motion: { type: "
|
|
953
|
+
count: 100,
|
|
954
|
+
sprite: { kind: "circle", radius: [1, 3], color: "#667799", colors: ["#8899cc", "#99aadd", "#aabbee", "#7788aa"] },
|
|
955
|
+
alpha: [0.2, 0.7],
|
|
956
|
+
pulse: { amp: 0.2, period: 3e3 },
|
|
957
|
+
motion: { type: "static" }
|
|
587
958
|
},
|
|
959
|
+
// Connected constellation nodes — slow drift, linked
|
|
588
960
|
{
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
radius: [1.5, 3],
|
|
593
|
-
color: "#b06a2a",
|
|
594
|
-
soft: true
|
|
595
|
-
},
|
|
961
|
+
key: "nodes",
|
|
962
|
+
count: 40,
|
|
963
|
+
sprite: { kind: "circle", radius: [2, 5], color: "#88aaff", colors: ["#88aaff", "#aaccff", "#ffcc88", "#ff8866"], soft: true },
|
|
596
964
|
alpha: [0.5, 0.9],
|
|
597
965
|
blend: "lighter",
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
},
|
|
601
|
-
{
|
|
602
|
-
count: 14,
|
|
603
|
-
sprite: {
|
|
604
|
-
kind: "circle",
|
|
605
|
-
radius: [3.5, 6.5],
|
|
606
|
-
color: "#e08a34",
|
|
607
|
-
soft: true
|
|
608
|
-
},
|
|
609
|
-
alpha: [0.6, 1],
|
|
610
|
-
blend: "lighter",
|
|
611
|
-
pulse: { amp: 0.22, period: 3400 },
|
|
612
|
-
motion: { type: "rise", speed: [16, 28], sway: 7 }
|
|
613
|
-
},
|
|
614
|
-
{
|
|
615
|
-
count: 8,
|
|
616
|
-
sprite: { kind: "emoji", glyphs: ["\u{1F3EE}"] },
|
|
617
|
-
size: [26, 44],
|
|
618
|
-
alpha: [0.85, 1],
|
|
619
|
-
motion: { type: "rise", speed: [30, 48], sway: 10 }
|
|
966
|
+
motion: { type: "drift", speed: [2, 8], bidirectional: true, bob: 3 },
|
|
967
|
+
links: { k: 3, maxDist: 200, alpha: 0.15, width: 0.5 }
|
|
620
968
|
},
|
|
969
|
+
// Cycling asterism markers
|
|
621
970
|
{
|
|
622
|
-
count:
|
|
623
|
-
sprite: { kind: "emoji", glyphs: ["\
|
|
624
|
-
size: [
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
{
|
|
628
|
-
count: 12,
|
|
629
|
-
sprite: { kind: "circle", radius: [2, 5], color: "#e08a34", soft: true },
|
|
630
|
-
alpha: [0.12, 0.35],
|
|
631
|
-
blend: "lighter",
|
|
632
|
-
region: { y: [0.93, 1] },
|
|
633
|
-
pulse: { amp: 0.1, period: 2200 },
|
|
634
|
-
motion: { type: "drift", speed: [2, 6], bob: 2 }
|
|
635
|
-
}
|
|
636
|
-
]
|
|
637
|
-
};
|
|
638
|
-
var SAKURA_SPEC = {
|
|
639
|
-
schemaVersion: 1,
|
|
640
|
-
id: "sakura",
|
|
641
|
-
label: "Sakura Drift",
|
|
642
|
-
seed: 314,
|
|
643
|
-
motionIntensity: "calm",
|
|
644
|
-
background: {
|
|
645
|
-
type: "gradient",
|
|
646
|
-
stops: [
|
|
647
|
-
{ at: 0, color: "#1b1d38" },
|
|
648
|
-
{ at: 0.45, color: "#3a2645" },
|
|
649
|
-
{ at: 0.8, color: "#7d3d4e" },
|
|
650
|
-
{ at: 1, color: "#0f140d" }
|
|
651
|
-
],
|
|
652
|
-
band: { color: "#0c140d", height: 44 }
|
|
653
|
-
},
|
|
654
|
-
layers: [
|
|
655
|
-
{
|
|
656
|
-
count: 60,
|
|
657
|
-
sprite: { kind: "circle", radius: [0.6, 1.6], color: "#ffe3ec", soft: true },
|
|
658
|
-
alpha: [0.3, 0.9],
|
|
659
|
-
blend: "lighter",
|
|
660
|
-
region: { y: [0, 0.72] },
|
|
661
|
-
pulse: { amp: 0.22, period: 3e3 },
|
|
662
|
-
motion: { type: "drift", speed: [0.3, 1.2], bob: 1 }
|
|
663
|
-
},
|
|
664
|
-
{
|
|
665
|
-
count: 28,
|
|
666
|
-
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
667
|
-
size: [13, 20],
|
|
668
|
-
alpha: [0.5, 0.8],
|
|
669
|
-
motion: { type: "drift", speed: [12, 26], angle: 100, bob: 3 }
|
|
670
|
-
},
|
|
671
|
-
{
|
|
672
|
-
count: 18,
|
|
673
|
-
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
674
|
-
size: [20, 32],
|
|
675
|
-
alpha: [0.7, 0.95],
|
|
676
|
-
motion: { type: "drift", speed: [24, 44], angle: 100, bob: 6 }
|
|
677
|
-
},
|
|
678
|
-
{
|
|
679
|
-
count: 10,
|
|
680
|
-
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
681
|
-
size: [34, 54],
|
|
682
|
-
alpha: [0.85, 1],
|
|
683
|
-
motion: { type: "drift", speed: [42, 68], angle: 100, bob: 11 }
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
count: 16,
|
|
687
|
-
sprite: { kind: "circle", radius: [1.5, 4], color: "#ff9bb5", soft: true },
|
|
688
|
-
alpha: [0.1, 0.3],
|
|
689
|
-
blend: "lighter",
|
|
690
|
-
region: { y: [0.9, 1] },
|
|
691
|
-
pulse: { amp: 0.15, period: 2600 },
|
|
692
|
-
motion: { type: "drift", speed: [0.3, 1.5], angle: 180, bob: 1 }
|
|
971
|
+
count: 6,
|
|
972
|
+
sprite: { kind: "emoji", glyphs: ["\u2726", "\u2727", "\u22C6", "\u2217"], cycle: { period: 2e3 } },
|
|
973
|
+
size: [14, 22],
|
|
974
|
+
alpha: [0.4, 0.7],
|
|
975
|
+
motion: { type: "drift", speed: [1, 4], bidirectional: true }
|
|
693
976
|
}
|
|
694
977
|
]
|
|
695
978
|
};
|
|
979
|
+
|
|
980
|
+
// src/examples/dev-dashboard.ts
|
|
696
981
|
var DASHBOARD_SPEC = {
|
|
697
982
|
schemaVersion: 1,
|
|
698
983
|
id: "dev-dashboard",
|
|
@@ -1154,7 +1439,7 @@ var DASHBOARD_SPEC = {
|
|
|
1154
1439
|
position: { x: 0.06, y: 0.92 },
|
|
1155
1440
|
sprite: {
|
|
1156
1441
|
kind: "text",
|
|
1157
|
-
strings: ["14:03 Edit examples.ts +84 -1"],
|
|
1442
|
+
strings: ["14:03 Edit examples/dev-dashboard.ts +84 -1"],
|
|
1158
1443
|
color: "#445544",
|
|
1159
1444
|
font: "10px monospace",
|
|
1160
1445
|
align: "left",
|
|
@@ -1180,31 +1465,324 @@ var DASHBOARD_SPEC = {
|
|
|
1180
1465
|
}
|
|
1181
1466
|
]
|
|
1182
1467
|
};
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1468
|
+
|
|
1469
|
+
// src/examples/lanterns.ts
|
|
1470
|
+
var LANTERNS_SPEC = {
|
|
1471
|
+
schemaVersion: 1,
|
|
1472
|
+
id: "lanterns",
|
|
1473
|
+
label: "Night Lanterns",
|
|
1474
|
+
seed: 88,
|
|
1475
|
+
motionIntensity: "calm",
|
|
1476
|
+
background: {
|
|
1477
|
+
type: "gradient",
|
|
1478
|
+
stops: [
|
|
1479
|
+
{ at: 0, color: "#04060f" },
|
|
1480
|
+
{ at: 0.55, color: "#0b1026" },
|
|
1481
|
+
{ at: 0.85, color: "#251731" },
|
|
1482
|
+
{ at: 1, color: "#472518" }
|
|
1483
|
+
]
|
|
1484
|
+
},
|
|
1485
|
+
layers: [
|
|
1486
|
+
{
|
|
1487
|
+
count: 60,
|
|
1488
|
+
sprite: { kind: "circle", radius: [0.5, 1.4], color: "#8fa0c8" },
|
|
1489
|
+
alpha: [0.35, 1],
|
|
1490
|
+
region: { y: [0, 0.62] },
|
|
1491
|
+
motion: { type: "drift", speed: [0.5, 2], bob: 1 }
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
count: 36,
|
|
1495
|
+
sprite: {
|
|
1496
|
+
kind: "circle",
|
|
1497
|
+
radius: [1.5, 3],
|
|
1498
|
+
color: "#b06a2a",
|
|
1499
|
+
soft: true
|
|
1500
|
+
},
|
|
1501
|
+
alpha: [0.5, 0.9],
|
|
1502
|
+
blend: "lighter",
|
|
1503
|
+
pulse: { amp: 0.18, period: 2800 },
|
|
1504
|
+
motion: { type: "rise", speed: [6, 14], sway: 4 }
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
count: 14,
|
|
1508
|
+
sprite: {
|
|
1509
|
+
kind: "circle",
|
|
1510
|
+
radius: [3.5, 6.5],
|
|
1511
|
+
color: "#e08a34",
|
|
1512
|
+
soft: true
|
|
1513
|
+
},
|
|
1514
|
+
alpha: [0.6, 1],
|
|
1515
|
+
blend: "lighter",
|
|
1516
|
+
pulse: { amp: 0.22, period: 3400 },
|
|
1517
|
+
motion: { type: "rise", speed: [16, 28], sway: 7 }
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
count: 8,
|
|
1521
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F3EE}"] },
|
|
1522
|
+
size: [26, 44],
|
|
1523
|
+
alpha: [0.85, 1],
|
|
1524
|
+
motion: { type: "rise", speed: [30, 48], sway: 10 }
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
count: 4,
|
|
1528
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F3EE}"] },
|
|
1529
|
+
size: [58, 84],
|
|
1530
|
+
motion: { type: "rise", speed: [52, 74], sway: 14 }
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
count: 12,
|
|
1534
|
+
sprite: { kind: "circle", radius: [2, 5], color: "#e08a34", soft: true },
|
|
1535
|
+
alpha: [0.12, 0.35],
|
|
1536
|
+
blend: "lighter",
|
|
1537
|
+
region: { y: [0.93, 1] },
|
|
1538
|
+
pulse: { amp: 0.1, period: 2200 },
|
|
1539
|
+
motion: { type: "drift", speed: [2, 6], bob: 2 }
|
|
1540
|
+
}
|
|
1541
|
+
]
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1544
|
+
// src/examples/orrery.ts
|
|
1545
|
+
var ORRERY_SPEC = {
|
|
1546
|
+
schemaVersion: 1,
|
|
1547
|
+
id: "orrery",
|
|
1548
|
+
label: "Orrery",
|
|
1549
|
+
seed: 7,
|
|
1550
|
+
motionIntensity: "calm",
|
|
1551
|
+
background: {
|
|
1552
|
+
type: "gradient",
|
|
1553
|
+
stops: [
|
|
1554
|
+
{ at: 0, color: "#020210" },
|
|
1555
|
+
{ at: 0.5, color: "#06051a" },
|
|
1556
|
+
{ at: 1, color: "#0a0820" }
|
|
1557
|
+
]
|
|
1558
|
+
},
|
|
1559
|
+
layers: [
|
|
1560
|
+
// Distant stars
|
|
1561
|
+
{
|
|
1562
|
+
count: 80,
|
|
1563
|
+
sprite: { kind: "circle", radius: [0.3, 1.2], color: "#8899cc" },
|
|
1564
|
+
alpha: [0.2, 0.8],
|
|
1565
|
+
pulse: { amp: 0.3, period: 3e3 },
|
|
1566
|
+
motion: { type: "static" }
|
|
1567
|
+
},
|
|
1568
|
+
// Sun corona (soft glow)
|
|
1569
|
+
{
|
|
1570
|
+
key: "corona",
|
|
1571
|
+
count: 1,
|
|
1572
|
+
sprite: { kind: "circle", radius: [60, 60], color: "#ffaa22", soft: true },
|
|
1573
|
+
blend: "lighter",
|
|
1574
|
+
alpha: [0.6, 0.6],
|
|
1575
|
+
grow: { amp: 0.15, period: 4e3 },
|
|
1576
|
+
motion: { type: "static" },
|
|
1577
|
+
position: { x: 0.5, y: 0.5 }
|
|
1578
|
+
},
|
|
1579
|
+
// Sun body
|
|
1580
|
+
{
|
|
1581
|
+
key: "sun",
|
|
1582
|
+
count: 1,
|
|
1583
|
+
sprite: { kind: "emoji", glyphs: ["\u2600\uFE0F"] },
|
|
1584
|
+
size: [48, 48],
|
|
1585
|
+
grow: { amp: 0.08, period: 4e3 },
|
|
1586
|
+
motion: { type: "static" },
|
|
1587
|
+
position: { x: 0.5, y: 0.5 }
|
|
1588
|
+
},
|
|
1589
|
+
// Inner ring — small fast planets
|
|
1590
|
+
{
|
|
1591
|
+
key: "inner",
|
|
1592
|
+
count: 3,
|
|
1593
|
+
sprite: { kind: "emoji", glyphs: ["\u{1FAA8}", "\u{1F534}", "\u{1F7E0}"] },
|
|
1594
|
+
size: [16, 22],
|
|
1595
|
+
spin: 30,
|
|
1596
|
+
motion: { type: "orbit", speed: [28, 42], radius: [80, 120] }
|
|
1597
|
+
},
|
|
1598
|
+
// Middle ring — gas giants
|
|
1599
|
+
{
|
|
1600
|
+
key: "middle",
|
|
1601
|
+
count: 2,
|
|
1602
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F7E4}", "\u{1FA90}"] },
|
|
1603
|
+
size: [28, 38],
|
|
1604
|
+
spin: -15,
|
|
1605
|
+
motion: { type: "orbit", speed: [12, 20], radius: [160, 200] }
|
|
1606
|
+
},
|
|
1607
|
+
// Outer ring — ice giants
|
|
1608
|
+
{
|
|
1609
|
+
key: "outer",
|
|
1610
|
+
count: 2,
|
|
1611
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F535}", "\u{1F48E}"] },
|
|
1612
|
+
size: [22, 30],
|
|
1613
|
+
spin: 10,
|
|
1614
|
+
motion: { type: "orbit", speed: [5, 10], radius: [240, 300] }
|
|
1615
|
+
},
|
|
1616
|
+
// Orbital dust ring (soft glow particles orbiting slowly)
|
|
1617
|
+
{
|
|
1618
|
+
count: 24,
|
|
1619
|
+
sprite: { kind: "circle", radius: [1, 3], color: "#ccaa66", soft: true },
|
|
1620
|
+
alpha: [0.15, 0.4],
|
|
1621
|
+
blend: "lighter",
|
|
1622
|
+
pulse: { amp: 0.2, period: 2500 },
|
|
1623
|
+
motion: { type: "orbit", speed: [2, 8], radius: [100, 320] }
|
|
1624
|
+
}
|
|
1625
|
+
]
|
|
1626
|
+
};
|
|
1627
|
+
|
|
1628
|
+
// src/examples/rain.ts
|
|
1629
|
+
var RAIN_SPEC = {
|
|
1630
|
+
schemaVersion: 1,
|
|
1631
|
+
id: "rain",
|
|
1632
|
+
label: "Rain",
|
|
1633
|
+
seed: 7,
|
|
1634
|
+
motionIntensity: "moderate",
|
|
1635
|
+
background: { type: "solid", color: "#05070a" },
|
|
1636
|
+
layers: [
|
|
1637
|
+
{
|
|
1638
|
+
count: 140,
|
|
1639
|
+
sprite: { kind: "text", strings: ["\u2502"], color: "#9fd0ff" },
|
|
1640
|
+
size: [12, 26],
|
|
1641
|
+
motion: { type: "drift", angle: 90, speed: [520, 900] }
|
|
1642
|
+
}
|
|
1643
|
+
]
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
// src/examples/sakura.ts
|
|
1647
|
+
var SAKURA_SPEC = {
|
|
1648
|
+
schemaVersion: 1,
|
|
1649
|
+
id: "sakura",
|
|
1650
|
+
label: "Sakura Drift",
|
|
1651
|
+
seed: 314,
|
|
1652
|
+
motionIntensity: "calm",
|
|
1653
|
+
background: {
|
|
1654
|
+
type: "gradient",
|
|
1655
|
+
stops: [
|
|
1656
|
+
{ at: 0, color: "#1b1d38" },
|
|
1657
|
+
{ at: 0.45, color: "#3a2645" },
|
|
1658
|
+
{ at: 0.8, color: "#7d3d4e" },
|
|
1659
|
+
{ at: 1, color: "#0f140d" }
|
|
1660
|
+
],
|
|
1661
|
+
band: { color: "#0c140d", height: 44 }
|
|
1662
|
+
},
|
|
1663
|
+
layers: [
|
|
1664
|
+
{
|
|
1665
|
+
count: 60,
|
|
1666
|
+
sprite: { kind: "circle", radius: [0.6, 1.6], color: "#ffe3ec", soft: true },
|
|
1667
|
+
alpha: [0.3, 0.9],
|
|
1668
|
+
blend: "lighter",
|
|
1669
|
+
region: { y: [0, 0.72] },
|
|
1670
|
+
pulse: { amp: 0.22, period: 3e3 },
|
|
1671
|
+
motion: { type: "drift", speed: [0.3, 1.2], bob: 1 }
|
|
1672
|
+
},
|
|
1673
|
+
{
|
|
1674
|
+
count: 28,
|
|
1675
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
1676
|
+
size: [13, 20],
|
|
1677
|
+
alpha: [0.5, 0.8],
|
|
1678
|
+
motion: { type: "drift", speed: [12, 26], angle: 100, bob: 3 }
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
count: 18,
|
|
1682
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
1683
|
+
size: [20, 32],
|
|
1684
|
+
alpha: [0.7, 0.95],
|
|
1685
|
+
motion: { type: "drift", speed: [24, 44], angle: 100, bob: 6 }
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
count: 10,
|
|
1689
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
1690
|
+
size: [34, 54],
|
|
1691
|
+
alpha: [0.85, 1],
|
|
1692
|
+
motion: { type: "drift", speed: [42, 68], angle: 100, bob: 11 }
|
|
1693
|
+
},
|
|
1694
|
+
{
|
|
1695
|
+
count: 16,
|
|
1696
|
+
sprite: { kind: "circle", radius: [1.5, 4], color: "#ff9bb5", soft: true },
|
|
1697
|
+
alpha: [0.1, 0.3],
|
|
1698
|
+
blend: "lighter",
|
|
1699
|
+
region: { y: [0.9, 1] },
|
|
1700
|
+
pulse: { amp: 0.15, period: 2600 },
|
|
1701
|
+
motion: { type: "drift", speed: [0.3, 1.5], angle: 180, bob: 1 }
|
|
1702
|
+
}
|
|
1703
|
+
]
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
// src/examples/snowfall.ts
|
|
1707
|
+
var SNOWFALL_SPEC = {
|
|
1708
|
+
schemaVersion: 1,
|
|
1709
|
+
id: "snowfall",
|
|
1710
|
+
label: "Snowfall",
|
|
1711
|
+
seed: 12,
|
|
1712
|
+
motionIntensity: "calm",
|
|
1713
|
+
background: {
|
|
1714
|
+
type: "gradient",
|
|
1715
|
+
stops: [
|
|
1716
|
+
{ at: 0, color: "#0a1628" },
|
|
1717
|
+
{ at: 0.7, color: "#0d1f3c" },
|
|
1718
|
+
{ at: 1, color: "#121212" }
|
|
1719
|
+
]
|
|
1720
|
+
},
|
|
1721
|
+
layers: [
|
|
1722
|
+
{
|
|
1723
|
+
count: 50,
|
|
1724
|
+
sprite: { kind: "circle", radius: [1, 2.5], color: "#8899aa" },
|
|
1725
|
+
motion: { type: "drift", angle: 90, speed: [15, 40], bob: 3 }
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
count: 35,
|
|
1729
|
+
sprite: { kind: "circle", radius: [2, 4.5], color: "#c0cdd8" },
|
|
1730
|
+
motion: { type: "drift", angle: 85, speed: [35, 80], bob: 6 }
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
count: 15,
|
|
1734
|
+
sprite: { kind: "circle", radius: [3.5, 7], color: "#e8eff5" },
|
|
1735
|
+
motion: { type: "drift", angle: 82, speed: [55, 120], bob: 10 }
|
|
1736
|
+
}
|
|
1737
|
+
]
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1740
|
+
// src/examples/index.ts
|
|
1741
|
+
var SCHEMA_EXAMPLES = [
|
|
1742
|
+
{ id: "aquarium", label: "Aquarium", spec: AQUARIUM_SPEC },
|
|
1743
|
+
{ id: "rain", label: "Rain", spec: RAIN_SPEC },
|
|
1744
|
+
{ id: "snowfall", label: "Snowfall", spec: SNOWFALL_SPEC },
|
|
1745
|
+
{ id: "lanterns", label: "Night Lanterns", spec: LANTERNS_SPEC },
|
|
1746
|
+
{ id: "sakura", label: "Sakura Drift", spec: SAKURA_SPEC },
|
|
1747
|
+
{ id: "dev-dashboard", label: "Dev Dashboard", spec: DASHBOARD_SPEC },
|
|
1748
|
+
{ id: "orrery", label: "Orrery", spec: ORRERY_SPEC },
|
|
1749
|
+
{ id: "constellation", label: "Constellation", spec: CONSTELLATION_SPEC }
|
|
1190
1750
|
];
|
|
1751
|
+
var EXAMPLE_SPECS = SCHEMA_EXAMPLES.map((e) => e.spec);
|
|
1752
|
+
var EXAMPLE_BY_ID = Object.fromEntries(
|
|
1753
|
+
SCHEMA_EXAMPLES.map((e) => [e.id, e.spec])
|
|
1754
|
+
);
|
|
1191
1755
|
export {
|
|
1192
1756
|
AQUARIUM_SPEC,
|
|
1757
|
+
CONSTELLATION_SPEC,
|
|
1193
1758
|
DASHBOARD_SPEC,
|
|
1759
|
+
EXAMPLE_BY_ID,
|
|
1194
1760
|
EXAMPLE_SPECS,
|
|
1195
1761
|
LANTERNS_SPEC,
|
|
1196
1762
|
LIMITS,
|
|
1763
|
+
ORRERY_SPEC,
|
|
1197
1764
|
RAIN_SPEC,
|
|
1198
1765
|
SAKURA_SPEC,
|
|
1766
|
+
SCHEMA_EXAMPLES,
|
|
1199
1767
|
SCHEMA_VERSION,
|
|
1200
1768
|
SNOWFALL_SPEC,
|
|
1769
|
+
adviseSpec,
|
|
1201
1770
|
alphaAt,
|
|
1771
|
+
applyDeltasToSpec,
|
|
1202
1772
|
assertValidSpec,
|
|
1203
1773
|
buildEntities,
|
|
1204
1774
|
compileSaver,
|
|
1775
|
+
easeSmooth,
|
|
1776
|
+
lerpSpec,
|
|
1777
|
+
linkPairs,
|
|
1205
1778
|
manifestFor,
|
|
1206
1779
|
positionAt,
|
|
1780
|
+
resolveSpecPath,
|
|
1781
|
+
rotationAt,
|
|
1782
|
+
sizeAt,
|
|
1783
|
+
spriteIndexAt,
|
|
1207
1784
|
spriteVariants,
|
|
1785
|
+
structuralSignature,
|
|
1208
1786
|
validateSpec
|
|
1209
1787
|
};
|
|
1210
1788
|
//# sourceMappingURL=index.js.map
|