@idle-screens/schema 0.1.0 → 2.0.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 +143 -4
- package/dist/index.js +967 -26
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
- package/saver-spec.schema.json +224 -0
package/dist/index.js
CHANGED
|
@@ -3,9 +3,13 @@ var SCHEMA_VERSION = 1;
|
|
|
3
3
|
var LIMITS = {
|
|
4
4
|
maxPerLayer: 400,
|
|
5
5
|
maxTotal: 800,
|
|
6
|
-
maxLayers:
|
|
7
|
-
maxSpeed: 4e3
|
|
6
|
+
maxLayers: 36,
|
|
7
|
+
maxSpeed: 4e3,
|
|
8
8
|
// px/sec — bounds motion so nothing teleports
|
|
9
|
+
maxPulseAmp: 0.5,
|
|
10
|
+
// opacity breathing amplitude cap
|
|
11
|
+
minPulsePeriod: 500
|
|
12
|
+
// ms — caps pulse at 2 Hz (WCAG flash threshold is 3 Hz)
|
|
9
13
|
};
|
|
10
14
|
|
|
11
15
|
// src/validate.ts
|
|
@@ -78,6 +82,44 @@ function validateLayer(layer, path, err) {
|
|
|
78
82
|
}
|
|
79
83
|
if (layer.wrap !== void 0 && typeof layer.wrap !== "boolean") err(`${path}.wrap`, "must be a boolean");
|
|
80
84
|
if (layer.flip !== void 0 && typeof layer.flip !== "boolean") err(`${path}.flip`, "must be a boolean");
|
|
85
|
+
if (layer.alpha !== void 0 && (!isRange(layer.alpha) || layer.alpha[0] < 0 || layer.alpha[1] > 1)) {
|
|
86
|
+
err(`${path}.alpha`, "must be a [min,max] range within 0..1");
|
|
87
|
+
}
|
|
88
|
+
if (layer.blend !== void 0 && layer.blend !== "lighter") err(`${path}.blend`, "must be 'lighter' when set");
|
|
89
|
+
if (layer.key !== void 0 && (!isStr(layer.key) || layer.key.trim() === "")) {
|
|
90
|
+
err(`${path}.key`, "must be a non-empty string");
|
|
91
|
+
}
|
|
92
|
+
if (layer.position !== void 0) {
|
|
93
|
+
if (!isObj(layer.position) || !isNum(layer.position.x) || !isNum(layer.position.y)) {
|
|
94
|
+
err(`${path}.position`, "must be {x, y} with numbers 0..1");
|
|
95
|
+
} else {
|
|
96
|
+
if (layer.position.x < 0 || layer.position.x > 1) err(`${path}.position.x`, "must be 0..1");
|
|
97
|
+
if (layer.position.y < 0 || layer.position.y > 1) err(`${path}.position.y`, "must be 0..1");
|
|
98
|
+
if (isNum(layer.count) && layer.count !== 1) err(`${path}.position`, "position requires count: 1");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (layer.region !== void 0) {
|
|
102
|
+
if (!isObj(layer.region)) err(`${path}.region`, "must be an object");
|
|
103
|
+
else {
|
|
104
|
+
for (const axis of ["x", "y"]) {
|
|
105
|
+
const r = layer.region[axis];
|
|
106
|
+
if (r !== void 0 && (!isRange(r) || r[0] < 0 || r[1] > 1)) {
|
|
107
|
+
err(`${path}.region.${axis}`, "must be a [min,max] range within 0..1");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (layer.pulse !== void 0) {
|
|
113
|
+
if (!isObj(layer.pulse)) err(`${path}.pulse`, "must be an object");
|
|
114
|
+
else {
|
|
115
|
+
if (!isNum(layer.pulse.amp) || layer.pulse.amp <= 0 || layer.pulse.amp > LIMITS.maxPulseAmp) {
|
|
116
|
+
err(`${path}.pulse.amp`, `must be within 0..${LIMITS.maxPulseAmp}`);
|
|
117
|
+
}
|
|
118
|
+
if (!isNum(layer.pulse.period) || layer.pulse.period < LIMITS.minPulsePeriod) {
|
|
119
|
+
err(`${path}.pulse.period`, `must be >= ${LIMITS.minPulsePeriod} ms (flash-safety floor)`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
81
123
|
validateSprite(layer.sprite, `${path}.sprite`, err);
|
|
82
124
|
validateMotion(layer.motion, `${path}.motion`, err);
|
|
83
125
|
}
|
|
@@ -92,9 +134,19 @@ function validateSprite(sprite, path, err) {
|
|
|
92
134
|
err(`${path}.strings`, "must be a non-empty array of strings");
|
|
93
135
|
}
|
|
94
136
|
if (sprite.color !== void 0) color(sprite.color, `${path}.color`, err);
|
|
137
|
+
if (sprite.align !== void 0 && !["left", "center", "right"].includes(sprite.align)) {
|
|
138
|
+
err(`${path}.align`, "must be left | center | right");
|
|
139
|
+
}
|
|
140
|
+
if (sprite.baseline !== void 0 && !["top", "middle", "bottom"].includes(sprite.baseline)) {
|
|
141
|
+
err(`${path}.baseline`, "must be top | middle | bottom");
|
|
142
|
+
}
|
|
143
|
+
if (sprite.maxWidth !== void 0 && (!isNum(sprite.maxWidth) || sprite.maxWidth <= 0)) {
|
|
144
|
+
err(`${path}.maxWidth`, "must be a positive number");
|
|
145
|
+
}
|
|
95
146
|
} else if (sprite.kind === "circle") {
|
|
96
147
|
if (!isRange(sprite.radius) || sprite.radius[0] <= 0) err(`${path}.radius`, "must be a [min,max] range of positive px");
|
|
97
148
|
color(sprite.color, `${path}.color`, err);
|
|
149
|
+
if (sprite.soft !== void 0 && typeof sprite.soft !== "boolean") err(`${path}.soft`, "must be a boolean");
|
|
98
150
|
} else {
|
|
99
151
|
err(`${path}.kind`, "must be emoji | text | circle");
|
|
100
152
|
}
|
|
@@ -115,8 +167,9 @@ function validateMotion(motion, path, err) {
|
|
|
115
167
|
if (motion.sway !== void 0 && !isNum(motion.sway)) err(`${path}.sway`, "must be a number");
|
|
116
168
|
} else if (motion.type === "bounce") {
|
|
117
169
|
speedOk(motion.speed, `${path}.speed`);
|
|
170
|
+
} else if (motion.type === "static") {
|
|
118
171
|
} else {
|
|
119
|
-
err(`${path}.type`, "must be drift | rise | bounce");
|
|
172
|
+
err(`${path}.type`, "must be drift | rise | bounce | static");
|
|
120
173
|
}
|
|
121
174
|
}
|
|
122
175
|
function assertValidSpec(spec) {
|
|
@@ -145,7 +198,9 @@ function buildEntities(layer, rng, w, h) {
|
|
|
145
198
|
let bob = 0;
|
|
146
199
|
let motion = "drift";
|
|
147
200
|
const m = layer.motion;
|
|
148
|
-
if (m.type === "
|
|
201
|
+
if (m.type === "static") {
|
|
202
|
+
motion = "static";
|
|
203
|
+
} else if (m.type === "drift") {
|
|
149
204
|
const s = rng.range(m.speed[0], m.speed[1]);
|
|
150
205
|
let angle = (m.angle ?? 0) * Math.PI / 180;
|
|
151
206
|
if (m.bidirectional && rng.next() < 0.5) angle = Math.PI - angle;
|
|
@@ -163,9 +218,20 @@ function buildEntities(layer, rng, w, h) {
|
|
|
163
218
|
vx = s * Math.cos(a);
|
|
164
219
|
vy = s * Math.sin(a);
|
|
165
220
|
}
|
|
221
|
+
let x0;
|
|
222
|
+
let y0;
|
|
223
|
+
if (layer.position && layer.count === 1) {
|
|
224
|
+
x0 = layer.position.x * w;
|
|
225
|
+
y0 = layer.position.y * h;
|
|
226
|
+
} else {
|
|
227
|
+
const [rx0, rx1] = layer.region?.x ?? [0, 1];
|
|
228
|
+
const [ry0, ry1] = layer.region?.y ?? [0, 1];
|
|
229
|
+
x0 = rng.range(rx0 * w, rx1 * w);
|
|
230
|
+
y0 = rng.range(ry0 * h, ry1 * h);
|
|
231
|
+
}
|
|
166
232
|
out.push({
|
|
167
|
-
x0
|
|
168
|
-
y0
|
|
233
|
+
x0,
|
|
234
|
+
y0,
|
|
169
235
|
size,
|
|
170
236
|
spriteIndex: variants > 1 ? rng.int(0, variants - 1) : 0,
|
|
171
237
|
phase: rng.range(0, Math.PI * 2),
|
|
@@ -173,11 +239,20 @@ function buildEntities(layer, rng, w, h) {
|
|
|
173
239
|
vy,
|
|
174
240
|
bob,
|
|
175
241
|
motion,
|
|
176
|
-
headingLeft: vx < 0
|
|
242
|
+
headingLeft: vx < 0,
|
|
243
|
+
alpha: layer.alpha ? rng.range(layer.alpha[0], layer.alpha[1]) : 1,
|
|
244
|
+
pulseAmp: layer.pulse?.amp ?? 0,
|
|
245
|
+
pulsePeriod: layer.pulse?.period ?? 1e3,
|
|
246
|
+
pulsePhase: layer.pulse ? rng.range(0, Math.PI * 2) : 0
|
|
177
247
|
});
|
|
178
248
|
}
|
|
179
249
|
return out;
|
|
180
250
|
}
|
|
251
|
+
function alphaAt(e, t) {
|
|
252
|
+
if (!e.pulseAmp) return e.alpha;
|
|
253
|
+
const a = e.alpha + e.pulseAmp * Math.sin(t * 2 * Math.PI / e.pulsePeriod + e.pulsePhase);
|
|
254
|
+
return a < 0 ? 0 : a > 1 ? 1 : a;
|
|
255
|
+
}
|
|
181
256
|
function wrap(v, min, max) {
|
|
182
257
|
const range = max - min;
|
|
183
258
|
return ((v - min) % range + range) % range + min;
|
|
@@ -190,6 +265,7 @@ function reflect(v, min, max) {
|
|
|
190
265
|
return min + (p < range ? p : period - p);
|
|
191
266
|
}
|
|
192
267
|
function positionAt(e, t, w, h) {
|
|
268
|
+
if (e.motion === "static") return { x: e.x0, y: e.y0, flip: false };
|
|
193
269
|
const dt = t / 1e3;
|
|
194
270
|
const m = e.size;
|
|
195
271
|
if (e.motion === "bounce") {
|
|
@@ -216,6 +292,104 @@ function positionAt(e, t, w, h) {
|
|
|
216
292
|
import {
|
|
217
293
|
createRng
|
|
218
294
|
} from "@idle-screens/core";
|
|
295
|
+
|
|
296
|
+
// src/steer.ts
|
|
297
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
298
|
+
function resolveSpecPath(spec, path) {
|
|
299
|
+
if (!spec || typeof spec !== "object" || !path) return null;
|
|
300
|
+
const parts = path.split(".");
|
|
301
|
+
if (parts.some((p) => UNSAFE_KEYS.has(p))) return null;
|
|
302
|
+
const s = spec;
|
|
303
|
+
if (parts[0] !== "layers" && parts[0] !== "background" && Array.isArray(s.layers)) {
|
|
304
|
+
const idx = s.layers.findIndex((l) => l && l.key === parts[0]);
|
|
305
|
+
if (idx === -1) return null;
|
|
306
|
+
parts.splice(0, 1, "layers", String(idx));
|
|
307
|
+
}
|
|
308
|
+
let node = spec;
|
|
309
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
310
|
+
if (node === null || typeof node !== "object") return null;
|
|
311
|
+
node = node[parts[i]];
|
|
312
|
+
}
|
|
313
|
+
if (node === null || typeof node !== "object") return null;
|
|
314
|
+
const last = parts[parts.length - 1];
|
|
315
|
+
const key = Array.isArray(node) ? Number(last) : last;
|
|
316
|
+
const exists = Array.isArray(node) ? Number.isInteger(key) && key >= 0 && key < node.length : last in node;
|
|
317
|
+
return exists ? { parent: node, key } : null;
|
|
318
|
+
}
|
|
319
|
+
function applyDeltasToSpec(spec, deltas) {
|
|
320
|
+
const copy = JSON.parse(JSON.stringify(spec));
|
|
321
|
+
for (const d of deltas) {
|
|
322
|
+
const loc = resolveSpecPath(copy, d.path);
|
|
323
|
+
if (loc) loc.parent[loc.key] = d.value;
|
|
324
|
+
}
|
|
325
|
+
return copy;
|
|
326
|
+
}
|
|
327
|
+
var HEX2 = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
328
|
+
function hexToRgb(hex) {
|
|
329
|
+
const h = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
330
|
+
const n = parseInt(h.slice(1), 16);
|
|
331
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
332
|
+
}
|
|
333
|
+
function lerpHex(a, b, k) {
|
|
334
|
+
const ca = hexToRgb(a);
|
|
335
|
+
const cb = hexToRgb(b);
|
|
336
|
+
const mix = ca.map((v, i) => Math.round(v + (cb[i] - v) * k));
|
|
337
|
+
return `#${mix.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
338
|
+
}
|
|
339
|
+
function lerpSpec(from, to, k) {
|
|
340
|
+
const kk = Math.max(0, Math.min(1, k));
|
|
341
|
+
const walk = (a, b, key) => {
|
|
342
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
343
|
+
const v = a + (b - a) * kk;
|
|
344
|
+
return key === "count" ? Math.max(1, Math.round(v)) : v;
|
|
345
|
+
}
|
|
346
|
+
if (typeof a === "string" && typeof b === "string" && HEX2.test(a) && HEX2.test(b)) {
|
|
347
|
+
return lerpHex(a, b, kk);
|
|
348
|
+
}
|
|
349
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
350
|
+
return b.map((bv, i) => walk(a[i], bv, i));
|
|
351
|
+
}
|
|
352
|
+
if (a && b && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
|
|
353
|
+
const out = {};
|
|
354
|
+
for (const kName of Object.keys(b)) {
|
|
355
|
+
out[kName] = walk(a[kName], b[kName], kName);
|
|
356
|
+
}
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
359
|
+
return kk > 0 ? b : a;
|
|
360
|
+
};
|
|
361
|
+
return walk(from, to);
|
|
362
|
+
}
|
|
363
|
+
function easeSmooth(k) {
|
|
364
|
+
const c = Math.max(0, Math.min(1, k));
|
|
365
|
+
return c * c * (3 - 2 * c);
|
|
366
|
+
}
|
|
367
|
+
function structuralSignature(spec) {
|
|
368
|
+
return JSON.stringify(
|
|
369
|
+
spec.layers.map((l) => [
|
|
370
|
+
l.count,
|
|
371
|
+
l.size,
|
|
372
|
+
l.region,
|
|
373
|
+
l.position,
|
|
374
|
+
l.motion,
|
|
375
|
+
l.wrap,
|
|
376
|
+
l.flip,
|
|
377
|
+
l.alpha,
|
|
378
|
+
l.pulse,
|
|
379
|
+
l.sprite.kind,
|
|
380
|
+
l.sprite.kind === "circle" ? l.sprite.radius : void 0,
|
|
381
|
+
l.sprite.kind === "emoji" ? l.sprite.glyphs.length : l.sprite.kind === "text" ? l.sprite.strings.length : void 0
|
|
382
|
+
])
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/compile.ts
|
|
387
|
+
var DEFAULT_STEER_DUR = 1e3;
|
|
388
|
+
function hexToRgba(hex, alpha) {
|
|
389
|
+
const h = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
390
|
+
const n = parseInt(h.slice(1), 16);
|
|
391
|
+
return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${alpha})`;
|
|
392
|
+
}
|
|
219
393
|
function manifestFor(spec) {
|
|
220
394
|
const total = spec.layers.reduce((n, l) => n + l.count, 0);
|
|
221
395
|
const costTier = total < 30 ? "idle" : total < 150 ? "low" : total < 400 ? "medium" : "high";
|
|
@@ -229,17 +403,26 @@ function manifestFor(spec) {
|
|
|
229
403
|
reducedMotionFallback: "static",
|
|
230
404
|
// Flash-safe by construction: static background + bounded sprites, no strobe
|
|
231
405
|
// primitive. Proven by sampling a compiled spec through @idle-screens/validator.
|
|
232
|
-
a11y: { flashSafe: true }
|
|
406
|
+
a11y: { flashSafe: true },
|
|
407
|
+
workerReady: true
|
|
233
408
|
};
|
|
234
409
|
}
|
|
235
410
|
var SpecInstance = class {
|
|
236
411
|
constructor(spec, ctx) {
|
|
237
412
|
this.spec = spec;
|
|
413
|
+
this.effSpec = spec;
|
|
414
|
+
this.saverCtx = ctx;
|
|
238
415
|
this.seed = (spec.seed ?? ctx.seed) >>> 0 || 1;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
416
|
+
let canvas;
|
|
417
|
+
if (ctx.surface) {
|
|
418
|
+
canvas = ctx.surface;
|
|
419
|
+
} else {
|
|
420
|
+
const el = document.createElement("canvas");
|
|
421
|
+
el.style.cssText = "display:block;width:100%;height:100%";
|
|
422
|
+
el.setAttribute("aria-hidden", "true");
|
|
423
|
+
ctx.host.appendChild(el);
|
|
424
|
+
canvas = el;
|
|
425
|
+
}
|
|
243
426
|
this.canvas = canvas;
|
|
244
427
|
const c2d = canvas.getContext("2d", { alpha: false });
|
|
245
428
|
if (!c2d) throw new Error("schema saver: no 2d context");
|
|
@@ -255,6 +438,7 @@ var SpecInstance = class {
|
|
|
255
438
|
spec;
|
|
256
439
|
canvas;
|
|
257
440
|
ctx;
|
|
441
|
+
saverCtx;
|
|
258
442
|
seed;
|
|
259
443
|
w;
|
|
260
444
|
h;
|
|
@@ -262,8 +446,16 @@ var SpecInstance = class {
|
|
|
262
446
|
frameId = null;
|
|
263
447
|
paused = false;
|
|
264
448
|
startT = 0;
|
|
449
|
+
baseT = 0;
|
|
450
|
+
// elapsed logical time carried across pause/resume
|
|
451
|
+
lastT = 0;
|
|
452
|
+
/** The spec currently being rendered (base spec + any applied steering). */
|
|
453
|
+
effSpec;
|
|
454
|
+
/** Active glide between two resolved specs (live setParam/applyTrack). */
|
|
455
|
+
transition = null;
|
|
456
|
+
lastStructural = "";
|
|
265
457
|
sizeCanvas() {
|
|
266
|
-
const dpr = Math.min(
|
|
458
|
+
const dpr = Math.min(this.saverCtx.dpr, 2);
|
|
267
459
|
this.canvas.width = Math.max(1, Math.round(this.w * dpr));
|
|
268
460
|
this.canvas.height = Math.max(1, Math.round(this.h * dpr));
|
|
269
461
|
this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
@@ -271,7 +463,8 @@ var SpecInstance = class {
|
|
|
271
463
|
/** (Re)seed and place all entities — deterministic for the seed + size. */
|
|
272
464
|
rebuild() {
|
|
273
465
|
const rng = createRng(this.seed);
|
|
274
|
-
this.layers = this.
|
|
466
|
+
this.layers = this.effSpec.layers.map((layer) => ({ layer, entities: buildEntities(layer, rng, this.w, this.h) }));
|
|
467
|
+
this.lastStructural = structuralSignature(this.effSpec);
|
|
275
468
|
}
|
|
276
469
|
start() {
|
|
277
470
|
if (this.frameId !== null || typeof requestAnimationFrame === "undefined") return;
|
|
@@ -283,15 +476,17 @@ var SpecInstance = class {
|
|
|
283
476
|
cancelAnimationFrame(this.frameId);
|
|
284
477
|
this.frameId = null;
|
|
285
478
|
}
|
|
479
|
+
this.baseT = this.lastT;
|
|
286
480
|
}
|
|
287
481
|
loop(now) {
|
|
288
482
|
this.frameId = requestAnimationFrame((n) => this.loop(n));
|
|
289
483
|
if (this.startT === 0) this.startT = now;
|
|
290
|
-
this.
|
|
484
|
+
this.lastT = now - this.startT + this.baseT;
|
|
485
|
+
this.renderFrame(this.lastT, this.seed);
|
|
291
486
|
}
|
|
292
487
|
drawBackground() {
|
|
293
488
|
const { ctx, w, h } = this;
|
|
294
|
-
const bg = this.
|
|
489
|
+
const bg = this.effSpec.background;
|
|
295
490
|
if (!bg || bg.type === "solid") {
|
|
296
491
|
ctx.fillStyle = bg?.color ?? "#05050a";
|
|
297
492
|
ctx.fillRect(0, 0, w, h);
|
|
@@ -310,10 +505,20 @@ var SpecInstance = class {
|
|
|
310
505
|
const { ctx } = this;
|
|
311
506
|
const p = positionAt(e, t, this.w, this.h);
|
|
312
507
|
const sprite = built.layer.sprite;
|
|
508
|
+
ctx.globalAlpha = alphaAt(e, t);
|
|
313
509
|
if (sprite.kind === "circle") {
|
|
314
|
-
|
|
510
|
+
const r = e.size / 2;
|
|
511
|
+
if (sprite.soft) {
|
|
512
|
+
const g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r);
|
|
513
|
+
g.addColorStop(0, sprite.color);
|
|
514
|
+
g.addColorStop(0.35, hexToRgba(sprite.color, 0.75));
|
|
515
|
+
g.addColorStop(1, hexToRgba(sprite.color, 0));
|
|
516
|
+
ctx.fillStyle = g;
|
|
517
|
+
} else {
|
|
518
|
+
ctx.fillStyle = sprite.color;
|
|
519
|
+
}
|
|
315
520
|
ctx.beginPath();
|
|
316
|
-
ctx.arc(p.x, p.y,
|
|
521
|
+
ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
|
|
317
522
|
ctx.fill();
|
|
318
523
|
return;
|
|
319
524
|
}
|
|
@@ -326,18 +531,67 @@ var SpecInstance = class {
|
|
|
326
531
|
ctx.font = `${e.size}px serif`;
|
|
327
532
|
ctx.fillText(sprite.glyphs[e.spriteIndex] ?? sprite.glyphs[0], 0, 0);
|
|
328
533
|
} else {
|
|
534
|
+
ctx.textAlign = sprite.align ?? "center";
|
|
535
|
+
ctx.textBaseline = sprite.baseline ?? "middle";
|
|
329
536
|
ctx.font = sprite.font ?? `${e.size}px system-ui, sans-serif`;
|
|
330
537
|
ctx.fillStyle = sprite.color ?? "#e6e8ef";
|
|
331
|
-
|
|
538
|
+
const text = sprite.strings[e.spriteIndex] ?? sprite.strings[0];
|
|
539
|
+
if (sprite.maxWidth) ctx.fillText(text, 0, 0, sprite.maxWidth);
|
|
540
|
+
else ctx.fillText(text, 0, 0);
|
|
332
541
|
}
|
|
333
542
|
ctx.restore();
|
|
334
543
|
}
|
|
544
|
+
/** Advance any live steering glide; rebuild entities on structural change. */
|
|
545
|
+
stepTransition(t) {
|
|
546
|
+
const tr = this.transition;
|
|
547
|
+
if (!tr) return;
|
|
548
|
+
const k = tr.dur <= 0 ? 1 : (t - tr.startT) / tr.dur;
|
|
549
|
+
this.effSpec = k >= 1 ? tr.to : lerpSpec(tr.from, tr.to, easeSmooth(k));
|
|
550
|
+
if (k >= 1) this.transition = null;
|
|
551
|
+
const sig = structuralSignature(this.effSpec);
|
|
552
|
+
if (sig !== this.lastStructural) this.rebuild();
|
|
553
|
+
else this.layers.forEach((b, i) => {
|
|
554
|
+
b.layer = this.effSpec.layers[i] ?? b.layer;
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Live steering: glide from what's currently rendered to the track's target
|
|
559
|
+
* state (deltas applied last-wins). Duration = the longest delta `dur`.
|
|
560
|
+
*/
|
|
561
|
+
applyTrack(track) {
|
|
562
|
+
const deltas = track?.deltas ?? [];
|
|
563
|
+
const target = applyDeltasToSpec(this.effSpec, deltas);
|
|
564
|
+
if (!validateSpec(target).valid) return;
|
|
565
|
+
const dur = deltas.length ? deltas.reduce((m, d) => Math.max(m, d.dur ?? DEFAULT_STEER_DUR), 0) : DEFAULT_STEER_DUR;
|
|
566
|
+
this.transition = {
|
|
567
|
+
from: JSON.parse(JSON.stringify(this.effSpec)),
|
|
568
|
+
to: target,
|
|
569
|
+
startT: this.lastT,
|
|
570
|
+
dur
|
|
571
|
+
};
|
|
572
|
+
if (this.paused) {
|
|
573
|
+
this.transition = null;
|
|
574
|
+
this.effSpec = target;
|
|
575
|
+
if (structuralSignature(this.effSpec) !== this.lastStructural) this.rebuild();
|
|
576
|
+
else this.layers.forEach((b, i) => {
|
|
577
|
+
b.layer = this.effSpec.layers[i] ?? b.layer;
|
|
578
|
+
});
|
|
579
|
+
this.renderFrame(this.lastT, this.seed);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
335
582
|
/** Deterministic, frame-addressable render (shared by the rAF loop). */
|
|
336
583
|
renderFrame(t, _seed) {
|
|
584
|
+
this.stepTransition(t);
|
|
585
|
+
const { ctx } = this;
|
|
586
|
+
ctx.globalAlpha = 1;
|
|
587
|
+
ctx.globalCompositeOperation = "source-over";
|
|
337
588
|
this.drawBackground();
|
|
338
589
|
for (const built of this.layers) {
|
|
590
|
+
ctx.globalCompositeOperation = built.layer.blend ?? "source-over";
|
|
339
591
|
for (const e of built.entities) this.drawEntity(built, e, t);
|
|
340
592
|
}
|
|
593
|
+
ctx.globalAlpha = 1;
|
|
594
|
+
ctx.globalCompositeOperation = "source-over";
|
|
341
595
|
}
|
|
342
596
|
setPaused(paused) {
|
|
343
597
|
this.paused = paused;
|
|
@@ -347,31 +601,33 @@ var SpecInstance = class {
|
|
|
347
601
|
this.start();
|
|
348
602
|
}
|
|
349
603
|
}
|
|
350
|
-
resize(width, height) {
|
|
604
|
+
resize(width, height, dpr) {
|
|
351
605
|
this.w = width;
|
|
352
606
|
this.h = height;
|
|
607
|
+
if (dpr !== void 0) this.saverCtx.dpr = dpr;
|
|
353
608
|
this.sizeCanvas();
|
|
354
609
|
this.rebuild();
|
|
355
610
|
if (this.paused) this.renderFrame(0, this.seed);
|
|
356
611
|
}
|
|
357
612
|
dispose() {
|
|
358
613
|
this.stop();
|
|
359
|
-
this.canvas.remove();
|
|
614
|
+
if (typeof HTMLCanvasElement !== "undefined" && this.canvas instanceof HTMLCanvasElement) this.canvas.remove();
|
|
360
615
|
}
|
|
361
616
|
};
|
|
362
617
|
function compileSaver(spec) {
|
|
363
618
|
const valid = assertValidSpec(spec);
|
|
364
619
|
return {
|
|
365
620
|
manifest: manifestFor(valid),
|
|
366
|
-
mount: (ctx) => new SpecInstance(valid, ctx)
|
|
621
|
+
mount: (ctx) => new SpecInstance(valid, ctx),
|
|
622
|
+
spec: valid
|
|
367
623
|
};
|
|
368
624
|
}
|
|
369
625
|
|
|
370
|
-
// src/examples.ts
|
|
626
|
+
// src/examples/aquarium.ts
|
|
371
627
|
var AQUARIUM_SPEC = {
|
|
372
628
|
schemaVersion: 1,
|
|
373
629
|
id: "aquarium",
|
|
374
|
-
label: "Aquarium
|
|
630
|
+
label: "Aquarium",
|
|
375
631
|
seed: 42,
|
|
376
632
|
motionIntensity: "calm",
|
|
377
633
|
background: {
|
|
@@ -398,10 +654,576 @@ var AQUARIUM_SPEC = {
|
|
|
398
654
|
}
|
|
399
655
|
]
|
|
400
656
|
};
|
|
657
|
+
|
|
658
|
+
// src/examples/dev-dashboard.ts
|
|
659
|
+
var DASHBOARD_SPEC = {
|
|
660
|
+
schemaVersion: 1,
|
|
661
|
+
id: "dev-dashboard",
|
|
662
|
+
label: "Dev Dashboard",
|
|
663
|
+
seed: 256,
|
|
664
|
+
motionIntensity: "calm",
|
|
665
|
+
background: {
|
|
666
|
+
type: "gradient",
|
|
667
|
+
stops: [
|
|
668
|
+
{ at: 0, color: "#06060c" },
|
|
669
|
+
{ at: 0.5, color: "#0a0a14" },
|
|
670
|
+
{ at: 1, color: "#0c0c18" }
|
|
671
|
+
]
|
|
672
|
+
},
|
|
673
|
+
layers: [
|
|
674
|
+
// --- ambient depth ---
|
|
675
|
+
{
|
|
676
|
+
count: 50,
|
|
677
|
+
sprite: { kind: "circle", radius: [0.2, 0.8], color: "#1a2538" },
|
|
678
|
+
alpha: [0.1, 0.25],
|
|
679
|
+
motion: { type: "drift", speed: [0.3, 1.2], bob: 0.3 }
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
count: 12,
|
|
683
|
+
sprite: { kind: "circle", radius: [1, 2.5], color: "#1a3050", soft: true },
|
|
684
|
+
alpha: [0.08, 0.18],
|
|
685
|
+
blend: "lighter",
|
|
686
|
+
pulse: { amp: 0.08, period: 4e3 },
|
|
687
|
+
motion: { type: "drift", speed: [0.5, 1.5], bob: 0.5 }
|
|
688
|
+
},
|
|
689
|
+
// --- header ---
|
|
690
|
+
{
|
|
691
|
+
key: "title",
|
|
692
|
+
count: 1,
|
|
693
|
+
position: { x: 0.5, y: 0.04 },
|
|
694
|
+
sprite: {
|
|
695
|
+
kind: "text",
|
|
696
|
+
strings: ["DEV PULSE"],
|
|
697
|
+
color: "#556677",
|
|
698
|
+
font: "600 13px 'SF Mono', monospace",
|
|
699
|
+
align: "center"
|
|
700
|
+
},
|
|
701
|
+
motion: { type: "static" }
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
key: "status",
|
|
705
|
+
count: 1,
|
|
706
|
+
position: { x: 0.92, y: 0.04 },
|
|
707
|
+
sprite: {
|
|
708
|
+
kind: "text",
|
|
709
|
+
strings: ["\u25CF ACTIVE"],
|
|
710
|
+
color: "#44cc66",
|
|
711
|
+
font: "bold 10px monospace",
|
|
712
|
+
align: "right"
|
|
713
|
+
},
|
|
714
|
+
motion: { type: "static" }
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
key: "uptime",
|
|
718
|
+
count: 1,
|
|
719
|
+
position: { x: 0.08, y: 0.04 },
|
|
720
|
+
sprite: {
|
|
721
|
+
kind: "text",
|
|
722
|
+
strings: ["up 2h 14m"],
|
|
723
|
+
color: "#445566",
|
|
724
|
+
font: "10px monospace",
|
|
725
|
+
align: "left"
|
|
726
|
+
},
|
|
727
|
+
motion: { type: "static" }
|
|
728
|
+
},
|
|
729
|
+
// --- left column: metrics ---
|
|
730
|
+
{
|
|
731
|
+
key: "tools-label",
|
|
732
|
+
count: 1,
|
|
733
|
+
position: { x: 0.06, y: 0.12 },
|
|
734
|
+
sprite: {
|
|
735
|
+
kind: "text",
|
|
736
|
+
strings: ["TOOL CALLS"],
|
|
737
|
+
color: "#445566",
|
|
738
|
+
font: "9px monospace",
|
|
739
|
+
align: "left",
|
|
740
|
+
baseline: "top"
|
|
741
|
+
},
|
|
742
|
+
motion: { type: "static" }
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
key: "tools-value",
|
|
746
|
+
count: 1,
|
|
747
|
+
position: { x: 0.06, y: 0.16 },
|
|
748
|
+
sprite: {
|
|
749
|
+
kind: "text",
|
|
750
|
+
strings: ["147"],
|
|
751
|
+
color: "#44aaff",
|
|
752
|
+
font: "bold 28px monospace",
|
|
753
|
+
align: "left",
|
|
754
|
+
baseline: "top"
|
|
755
|
+
},
|
|
756
|
+
motion: { type: "static" }
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
key: "reads-label",
|
|
760
|
+
count: 1,
|
|
761
|
+
position: { x: 0.06, y: 0.26 },
|
|
762
|
+
sprite: {
|
|
763
|
+
kind: "text",
|
|
764
|
+
strings: ["READS"],
|
|
765
|
+
color: "#445566",
|
|
766
|
+
font: "9px monospace",
|
|
767
|
+
align: "left",
|
|
768
|
+
baseline: "top"
|
|
769
|
+
},
|
|
770
|
+
motion: { type: "static" }
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
key: "reads-value",
|
|
774
|
+
count: 1,
|
|
775
|
+
position: { x: 0.06, y: 0.3 },
|
|
776
|
+
sprite: {
|
|
777
|
+
kind: "text",
|
|
778
|
+
strings: ["89"],
|
|
779
|
+
color: "#44ddcc",
|
|
780
|
+
font: "bold 28px monospace",
|
|
781
|
+
align: "left",
|
|
782
|
+
baseline: "top"
|
|
783
|
+
},
|
|
784
|
+
motion: { type: "static" }
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
key: "edits-label",
|
|
788
|
+
count: 1,
|
|
789
|
+
position: { x: 0.06, y: 0.4 },
|
|
790
|
+
sprite: {
|
|
791
|
+
kind: "text",
|
|
792
|
+
strings: ["EDITS"],
|
|
793
|
+
color: "#445566",
|
|
794
|
+
font: "9px monospace",
|
|
795
|
+
align: "left",
|
|
796
|
+
baseline: "top"
|
|
797
|
+
},
|
|
798
|
+
motion: { type: "static" }
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
key: "edits-value",
|
|
802
|
+
count: 1,
|
|
803
|
+
position: { x: 0.06, y: 0.44 },
|
|
804
|
+
sprite: {
|
|
805
|
+
kind: "text",
|
|
806
|
+
strings: ["34"],
|
|
807
|
+
color: "#ff8844",
|
|
808
|
+
font: "bold 28px monospace",
|
|
809
|
+
align: "left",
|
|
810
|
+
baseline: "top"
|
|
811
|
+
},
|
|
812
|
+
motion: { type: "static" }
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
key: "errors-label",
|
|
816
|
+
count: 1,
|
|
817
|
+
position: { x: 0.06, y: 0.54 },
|
|
818
|
+
sprite: {
|
|
819
|
+
kind: "text",
|
|
820
|
+
strings: ["ERRORS"],
|
|
821
|
+
color: "#445566",
|
|
822
|
+
font: "9px monospace",
|
|
823
|
+
align: "left",
|
|
824
|
+
baseline: "top"
|
|
825
|
+
},
|
|
826
|
+
motion: { type: "static" }
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
key: "errors-value",
|
|
830
|
+
count: 1,
|
|
831
|
+
position: { x: 0.06, y: 0.58 },
|
|
832
|
+
sprite: {
|
|
833
|
+
kind: "text",
|
|
834
|
+
strings: ["2"],
|
|
835
|
+
color: "#ff4455",
|
|
836
|
+
font: "bold 28px monospace",
|
|
837
|
+
align: "left",
|
|
838
|
+
baseline: "top"
|
|
839
|
+
},
|
|
840
|
+
motion: { type: "static" }
|
|
841
|
+
},
|
|
842
|
+
// --- center column: system gauges ---
|
|
843
|
+
{
|
|
844
|
+
key: "cpu-label",
|
|
845
|
+
count: 1,
|
|
846
|
+
position: { x: 0.38, y: 0.12 },
|
|
847
|
+
sprite: {
|
|
848
|
+
kind: "text",
|
|
849
|
+
strings: ["CPU"],
|
|
850
|
+
color: "#445566",
|
|
851
|
+
font: "9px monospace",
|
|
852
|
+
align: "center"
|
|
853
|
+
},
|
|
854
|
+
motion: { type: "static" }
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
key: "cpu-value",
|
|
858
|
+
count: 1,
|
|
859
|
+
position: { x: 0.38, y: 0.22 },
|
|
860
|
+
sprite: {
|
|
861
|
+
kind: "text",
|
|
862
|
+
strings: ["73%"],
|
|
863
|
+
color: "#44ff88",
|
|
864
|
+
font: "bold 36px monospace",
|
|
865
|
+
align: "center"
|
|
866
|
+
},
|
|
867
|
+
motion: { type: "static" }
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
key: "mem-label",
|
|
871
|
+
count: 1,
|
|
872
|
+
position: { x: 0.38, y: 0.36 },
|
|
873
|
+
sprite: {
|
|
874
|
+
kind: "text",
|
|
875
|
+
strings: ["MEMORY"],
|
|
876
|
+
color: "#445566",
|
|
877
|
+
font: "9px monospace",
|
|
878
|
+
align: "center"
|
|
879
|
+
},
|
|
880
|
+
motion: { type: "static" }
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
key: "mem-value",
|
|
884
|
+
count: 1,
|
|
885
|
+
position: { x: 0.38, y: 0.46 },
|
|
886
|
+
sprite: {
|
|
887
|
+
kind: "text",
|
|
888
|
+
strings: ["4.2 GB"],
|
|
889
|
+
color: "#ddcc44",
|
|
890
|
+
font: "bold 36px monospace",
|
|
891
|
+
align: "center"
|
|
892
|
+
},
|
|
893
|
+
motion: { type: "static" }
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
key: "tokens-label",
|
|
897
|
+
count: 1,
|
|
898
|
+
position: { x: 0.38, y: 0.58 },
|
|
899
|
+
sprite: {
|
|
900
|
+
kind: "text",
|
|
901
|
+
strings: ["TOKENS"],
|
|
902
|
+
color: "#445566",
|
|
903
|
+
font: "9px monospace",
|
|
904
|
+
align: "center"
|
|
905
|
+
},
|
|
906
|
+
motion: { type: "static" }
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
key: "tokens-value",
|
|
910
|
+
count: 1,
|
|
911
|
+
position: { x: 0.38, y: 0.66 },
|
|
912
|
+
sprite: {
|
|
913
|
+
kind: "text",
|
|
914
|
+
strings: ["128k"],
|
|
915
|
+
color: "#aa88ff",
|
|
916
|
+
font: "bold 28px monospace",
|
|
917
|
+
align: "center"
|
|
918
|
+
},
|
|
919
|
+
motion: { type: "static" }
|
|
920
|
+
},
|
|
921
|
+
// --- right column: recent files ---
|
|
922
|
+
{
|
|
923
|
+
key: "files-label",
|
|
924
|
+
count: 1,
|
|
925
|
+
position: { x: 0.62, y: 0.12 },
|
|
926
|
+
sprite: {
|
|
927
|
+
kind: "text",
|
|
928
|
+
strings: ["RECENT FILES"],
|
|
929
|
+
color: "#445566",
|
|
930
|
+
font: "9px monospace",
|
|
931
|
+
align: "left",
|
|
932
|
+
baseline: "top"
|
|
933
|
+
},
|
|
934
|
+
motion: { type: "static" }
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
key: "file-1",
|
|
938
|
+
count: 1,
|
|
939
|
+
position: { x: 0.62, y: 0.17 },
|
|
940
|
+
sprite: {
|
|
941
|
+
kind: "text",
|
|
942
|
+
strings: ["src/worker.ts"],
|
|
943
|
+
color: "#8899aa",
|
|
944
|
+
font: "11px monospace",
|
|
945
|
+
align: "left",
|
|
946
|
+
baseline: "top",
|
|
947
|
+
maxWidth: 280
|
|
948
|
+
},
|
|
949
|
+
motion: { type: "static" }
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
key: "file-2",
|
|
953
|
+
count: 1,
|
|
954
|
+
position: { x: 0.62, y: 0.21 },
|
|
955
|
+
sprite: {
|
|
956
|
+
kind: "text",
|
|
957
|
+
strings: ["site/src/App.tsx"],
|
|
958
|
+
color: "#778899",
|
|
959
|
+
font: "11px monospace",
|
|
960
|
+
align: "left",
|
|
961
|
+
baseline: "top",
|
|
962
|
+
maxWidth: 280
|
|
963
|
+
},
|
|
964
|
+
motion: { type: "static" }
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
key: "file-3",
|
|
968
|
+
count: 1,
|
|
969
|
+
position: { x: 0.62, y: 0.25 },
|
|
970
|
+
sprite: {
|
|
971
|
+
kind: "text",
|
|
972
|
+
strings: ["packages/schema/compile.ts"],
|
|
973
|
+
color: "#667788",
|
|
974
|
+
font: "11px monospace",
|
|
975
|
+
align: "left",
|
|
976
|
+
baseline: "top",
|
|
977
|
+
maxWidth: 280
|
|
978
|
+
},
|
|
979
|
+
motion: { type: "static" }
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
key: "file-4",
|
|
983
|
+
count: 1,
|
|
984
|
+
position: { x: 0.62, y: 0.29 },
|
|
985
|
+
sprite: {
|
|
986
|
+
kind: "text",
|
|
987
|
+
strings: ["docs/dashboard-spec.md"],
|
|
988
|
+
color: "#556677",
|
|
989
|
+
font: "11px monospace",
|
|
990
|
+
align: "left",
|
|
991
|
+
baseline: "top",
|
|
992
|
+
maxWidth: 280
|
|
993
|
+
},
|
|
994
|
+
motion: { type: "static" }
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
key: "file-5",
|
|
998
|
+
count: 1,
|
|
999
|
+
position: { x: 0.62, y: 0.33 },
|
|
1000
|
+
sprite: {
|
|
1001
|
+
kind: "text",
|
|
1002
|
+
strings: ["packages/core/types.ts"],
|
|
1003
|
+
color: "#445566",
|
|
1004
|
+
font: "11px monospace",
|
|
1005
|
+
align: "left",
|
|
1006
|
+
baseline: "top",
|
|
1007
|
+
maxWidth: 280
|
|
1008
|
+
},
|
|
1009
|
+
motion: { type: "static" }
|
|
1010
|
+
},
|
|
1011
|
+
// --- right column: session info ---
|
|
1012
|
+
{
|
|
1013
|
+
key: "session-label",
|
|
1014
|
+
count: 1,
|
|
1015
|
+
position: { x: 0.62, y: 0.42 },
|
|
1016
|
+
sprite: {
|
|
1017
|
+
kind: "text",
|
|
1018
|
+
strings: ["SESSION"],
|
|
1019
|
+
color: "#445566",
|
|
1020
|
+
font: "9px monospace",
|
|
1021
|
+
align: "left",
|
|
1022
|
+
baseline: "top"
|
|
1023
|
+
},
|
|
1024
|
+
motion: { type: "static" }
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
key: "session-model",
|
|
1028
|
+
count: 1,
|
|
1029
|
+
position: { x: 0.62, y: 0.47 },
|
|
1030
|
+
sprite: {
|
|
1031
|
+
kind: "text",
|
|
1032
|
+
strings: ["claude-opus-4-6"],
|
|
1033
|
+
color: "#8888aa",
|
|
1034
|
+
font: "11px monospace",
|
|
1035
|
+
align: "left",
|
|
1036
|
+
baseline: "top"
|
|
1037
|
+
},
|
|
1038
|
+
motion: { type: "static" }
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
key: "session-cost",
|
|
1042
|
+
count: 1,
|
|
1043
|
+
position: { x: 0.62, y: 0.51 },
|
|
1044
|
+
sprite: {
|
|
1045
|
+
kind: "text",
|
|
1046
|
+
strings: ["$2.47 this session"],
|
|
1047
|
+
color: "#667788",
|
|
1048
|
+
font: "11px monospace",
|
|
1049
|
+
align: "left",
|
|
1050
|
+
baseline: "top"
|
|
1051
|
+
},
|
|
1052
|
+
motion: { type: "static" }
|
|
1053
|
+
},
|
|
1054
|
+
// --- bottom: event log ---
|
|
1055
|
+
{
|
|
1056
|
+
key: "log-label",
|
|
1057
|
+
count: 1,
|
|
1058
|
+
position: { x: 0.06, y: 0.76 },
|
|
1059
|
+
sprite: {
|
|
1060
|
+
kind: "text",
|
|
1061
|
+
strings: ["EVENT LOG"],
|
|
1062
|
+
color: "#445566",
|
|
1063
|
+
font: "9px monospace",
|
|
1064
|
+
align: "left",
|
|
1065
|
+
baseline: "top"
|
|
1066
|
+
},
|
|
1067
|
+
motion: { type: "static" }
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
key: "log-1",
|
|
1071
|
+
count: 1,
|
|
1072
|
+
position: { x: 0.06, y: 0.8 },
|
|
1073
|
+
sprite: {
|
|
1074
|
+
kind: "text",
|
|
1075
|
+
strings: ["14:07 Edit compile.ts +18 -3"],
|
|
1076
|
+
color: "#667766",
|
|
1077
|
+
font: "10px monospace",
|
|
1078
|
+
align: "left",
|
|
1079
|
+
baseline: "top",
|
|
1080
|
+
maxWidth: 600
|
|
1081
|
+
},
|
|
1082
|
+
motion: { type: "static" }
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
key: "log-2",
|
|
1086
|
+
count: 1,
|
|
1087
|
+
position: { x: 0.06, y: 0.84 },
|
|
1088
|
+
sprite: {
|
|
1089
|
+
kind: "text",
|
|
1090
|
+
strings: ["14:06 Bash pnpm test \u2713 125 passed"],
|
|
1091
|
+
color: "#556655",
|
|
1092
|
+
font: "10px monospace",
|
|
1093
|
+
align: "left",
|
|
1094
|
+
baseline: "top",
|
|
1095
|
+
maxWidth: 600
|
|
1096
|
+
},
|
|
1097
|
+
motion: { type: "static" }
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
key: "log-3",
|
|
1101
|
+
count: 1,
|
|
1102
|
+
position: { x: 0.06, y: 0.88 },
|
|
1103
|
+
sprite: {
|
|
1104
|
+
kind: "text",
|
|
1105
|
+
strings: ["14:05 Read types.ts (121 lines)"],
|
|
1106
|
+
color: "#4d5e4d",
|
|
1107
|
+
font: "10px monospace",
|
|
1108
|
+
align: "left",
|
|
1109
|
+
baseline: "top",
|
|
1110
|
+
maxWidth: 600
|
|
1111
|
+
},
|
|
1112
|
+
motion: { type: "static" }
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
key: "log-4",
|
|
1116
|
+
count: 1,
|
|
1117
|
+
position: { x: 0.06, y: 0.92 },
|
|
1118
|
+
sprite: {
|
|
1119
|
+
kind: "text",
|
|
1120
|
+
strings: ["14:03 Edit examples/dev-dashboard.ts +84 -1"],
|
|
1121
|
+
color: "#445544",
|
|
1122
|
+
font: "10px monospace",
|
|
1123
|
+
align: "left",
|
|
1124
|
+
baseline: "top",
|
|
1125
|
+
maxWidth: 600
|
|
1126
|
+
},
|
|
1127
|
+
motion: { type: "static" }
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
key: "log-5",
|
|
1131
|
+
count: 1,
|
|
1132
|
+
position: { x: 0.06, y: 0.96 },
|
|
1133
|
+
sprite: {
|
|
1134
|
+
kind: "text",
|
|
1135
|
+
strings: ["14:01 Bash pnpm build \u2713 6 packages"],
|
|
1136
|
+
color: "#3d4e3d",
|
|
1137
|
+
font: "10px monospace",
|
|
1138
|
+
align: "left",
|
|
1139
|
+
baseline: "top",
|
|
1140
|
+
maxWidth: 600
|
|
1141
|
+
},
|
|
1142
|
+
motion: { type: "static" }
|
|
1143
|
+
}
|
|
1144
|
+
]
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
// src/examples/lanterns.ts
|
|
1148
|
+
var LANTERNS_SPEC = {
|
|
1149
|
+
schemaVersion: 1,
|
|
1150
|
+
id: "lanterns",
|
|
1151
|
+
label: "Night Lanterns",
|
|
1152
|
+
seed: 88,
|
|
1153
|
+
motionIntensity: "calm",
|
|
1154
|
+
background: {
|
|
1155
|
+
type: "gradient",
|
|
1156
|
+
stops: [
|
|
1157
|
+
{ at: 0, color: "#04060f" },
|
|
1158
|
+
{ at: 0.55, color: "#0b1026" },
|
|
1159
|
+
{ at: 0.85, color: "#251731" },
|
|
1160
|
+
{ at: 1, color: "#472518" }
|
|
1161
|
+
]
|
|
1162
|
+
},
|
|
1163
|
+
layers: [
|
|
1164
|
+
{
|
|
1165
|
+
count: 60,
|
|
1166
|
+
sprite: { kind: "circle", radius: [0.5, 1.4], color: "#8fa0c8" },
|
|
1167
|
+
alpha: [0.35, 1],
|
|
1168
|
+
region: { y: [0, 0.62] },
|
|
1169
|
+
motion: { type: "drift", speed: [0.5, 2], bob: 1 }
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
count: 36,
|
|
1173
|
+
sprite: {
|
|
1174
|
+
kind: "circle",
|
|
1175
|
+
radius: [1.5, 3],
|
|
1176
|
+
color: "#b06a2a",
|
|
1177
|
+
soft: true
|
|
1178
|
+
},
|
|
1179
|
+
alpha: [0.5, 0.9],
|
|
1180
|
+
blend: "lighter",
|
|
1181
|
+
pulse: { amp: 0.18, period: 2800 },
|
|
1182
|
+
motion: { type: "rise", speed: [6, 14], sway: 4 }
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
count: 14,
|
|
1186
|
+
sprite: {
|
|
1187
|
+
kind: "circle",
|
|
1188
|
+
radius: [3.5, 6.5],
|
|
1189
|
+
color: "#e08a34",
|
|
1190
|
+
soft: true
|
|
1191
|
+
},
|
|
1192
|
+
alpha: [0.6, 1],
|
|
1193
|
+
blend: "lighter",
|
|
1194
|
+
pulse: { amp: 0.22, period: 3400 },
|
|
1195
|
+
motion: { type: "rise", speed: [16, 28], sway: 7 }
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
count: 8,
|
|
1199
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F3EE}"] },
|
|
1200
|
+
size: [26, 44],
|
|
1201
|
+
alpha: [0.85, 1],
|
|
1202
|
+
motion: { type: "rise", speed: [30, 48], sway: 10 }
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
count: 4,
|
|
1206
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F3EE}"] },
|
|
1207
|
+
size: [58, 84],
|
|
1208
|
+
motion: { type: "rise", speed: [52, 74], sway: 14 }
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
count: 12,
|
|
1212
|
+
sprite: { kind: "circle", radius: [2, 5], color: "#e08a34", soft: true },
|
|
1213
|
+
alpha: [0.12, 0.35],
|
|
1214
|
+
blend: "lighter",
|
|
1215
|
+
region: { y: [0.93, 1] },
|
|
1216
|
+
pulse: { amp: 0.1, period: 2200 },
|
|
1217
|
+
motion: { type: "drift", speed: [2, 6], bob: 2 }
|
|
1218
|
+
}
|
|
1219
|
+
]
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
// src/examples/rain.ts
|
|
401
1223
|
var RAIN_SPEC = {
|
|
402
1224
|
schemaVersion: 1,
|
|
403
1225
|
id: "rain",
|
|
404
|
-
label: "Rain
|
|
1226
|
+
label: "Rain",
|
|
405
1227
|
seed: 7,
|
|
406
1228
|
motionIntensity: "moderate",
|
|
407
1229
|
background: { type: "solid", color: "#05070a" },
|
|
@@ -414,19 +1236,138 @@ var RAIN_SPEC = {
|
|
|
414
1236
|
}
|
|
415
1237
|
]
|
|
416
1238
|
};
|
|
417
|
-
|
|
1239
|
+
|
|
1240
|
+
// src/examples/sakura.ts
|
|
1241
|
+
var SAKURA_SPEC = {
|
|
1242
|
+
schemaVersion: 1,
|
|
1243
|
+
id: "sakura",
|
|
1244
|
+
label: "Sakura Drift",
|
|
1245
|
+
seed: 314,
|
|
1246
|
+
motionIntensity: "calm",
|
|
1247
|
+
background: {
|
|
1248
|
+
type: "gradient",
|
|
1249
|
+
stops: [
|
|
1250
|
+
{ at: 0, color: "#1b1d38" },
|
|
1251
|
+
{ at: 0.45, color: "#3a2645" },
|
|
1252
|
+
{ at: 0.8, color: "#7d3d4e" },
|
|
1253
|
+
{ at: 1, color: "#0f140d" }
|
|
1254
|
+
],
|
|
1255
|
+
band: { color: "#0c140d", height: 44 }
|
|
1256
|
+
},
|
|
1257
|
+
layers: [
|
|
1258
|
+
{
|
|
1259
|
+
count: 60,
|
|
1260
|
+
sprite: { kind: "circle", radius: [0.6, 1.6], color: "#ffe3ec", soft: true },
|
|
1261
|
+
alpha: [0.3, 0.9],
|
|
1262
|
+
blend: "lighter",
|
|
1263
|
+
region: { y: [0, 0.72] },
|
|
1264
|
+
pulse: { amp: 0.22, period: 3e3 },
|
|
1265
|
+
motion: { type: "drift", speed: [0.3, 1.2], bob: 1 }
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
count: 28,
|
|
1269
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
1270
|
+
size: [13, 20],
|
|
1271
|
+
alpha: [0.5, 0.8],
|
|
1272
|
+
motion: { type: "drift", speed: [12, 26], angle: 100, bob: 3 }
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
count: 18,
|
|
1276
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
1277
|
+
size: [20, 32],
|
|
1278
|
+
alpha: [0.7, 0.95],
|
|
1279
|
+
motion: { type: "drift", speed: [24, 44], angle: 100, bob: 6 }
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
count: 10,
|
|
1283
|
+
sprite: { kind: "emoji", glyphs: ["\u{1F338}"] },
|
|
1284
|
+
size: [34, 54],
|
|
1285
|
+
alpha: [0.85, 1],
|
|
1286
|
+
motion: { type: "drift", speed: [42, 68], angle: 100, bob: 11 }
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
count: 16,
|
|
1290
|
+
sprite: { kind: "circle", radius: [1.5, 4], color: "#ff9bb5", soft: true },
|
|
1291
|
+
alpha: [0.1, 0.3],
|
|
1292
|
+
blend: "lighter",
|
|
1293
|
+
region: { y: [0.9, 1] },
|
|
1294
|
+
pulse: { amp: 0.15, period: 2600 },
|
|
1295
|
+
motion: { type: "drift", speed: [0.3, 1.5], angle: 180, bob: 1 }
|
|
1296
|
+
}
|
|
1297
|
+
]
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
// src/examples/snowfall.ts
|
|
1301
|
+
var SNOWFALL_SPEC = {
|
|
1302
|
+
schemaVersion: 1,
|
|
1303
|
+
id: "snowfall",
|
|
1304
|
+
label: "Snowfall",
|
|
1305
|
+
seed: 12,
|
|
1306
|
+
motionIntensity: "calm",
|
|
1307
|
+
background: {
|
|
1308
|
+
type: "gradient",
|
|
1309
|
+
stops: [
|
|
1310
|
+
{ at: 0, color: "#0a1628" },
|
|
1311
|
+
{ at: 0.7, color: "#0d1f3c" },
|
|
1312
|
+
{ at: 1, color: "#121212" }
|
|
1313
|
+
]
|
|
1314
|
+
},
|
|
1315
|
+
layers: [
|
|
1316
|
+
{
|
|
1317
|
+
count: 50,
|
|
1318
|
+
sprite: { kind: "circle", radius: [1, 2.5], color: "#8899aa" },
|
|
1319
|
+
motion: { type: "drift", angle: 90, speed: [15, 40], bob: 3 }
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
count: 35,
|
|
1323
|
+
sprite: { kind: "circle", radius: [2, 4.5], color: "#c0cdd8" },
|
|
1324
|
+
motion: { type: "drift", angle: 85, speed: [35, 80], bob: 6 }
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
count: 15,
|
|
1328
|
+
sprite: { kind: "circle", radius: [3.5, 7], color: "#e8eff5" },
|
|
1329
|
+
motion: { type: "drift", angle: 82, speed: [55, 120], bob: 10 }
|
|
1330
|
+
}
|
|
1331
|
+
]
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
// src/examples/index.ts
|
|
1335
|
+
var SCHEMA_EXAMPLES = [
|
|
1336
|
+
{ id: "aquarium", label: "Aquarium", spec: AQUARIUM_SPEC },
|
|
1337
|
+
{ id: "rain", label: "Rain", spec: RAIN_SPEC },
|
|
1338
|
+
{ id: "snowfall", label: "Snowfall", spec: SNOWFALL_SPEC },
|
|
1339
|
+
{ id: "lanterns", label: "Night Lanterns", spec: LANTERNS_SPEC },
|
|
1340
|
+
{ id: "sakura", label: "Sakura Drift", spec: SAKURA_SPEC },
|
|
1341
|
+
{ id: "dev-dashboard", label: "Dev Dashboard", spec: DASHBOARD_SPEC }
|
|
1342
|
+
];
|
|
1343
|
+
var EXAMPLE_SPECS = SCHEMA_EXAMPLES.map((e) => e.spec);
|
|
1344
|
+
var EXAMPLE_BY_ID = Object.fromEntries(
|
|
1345
|
+
SCHEMA_EXAMPLES.map((e) => [e.id, e.spec])
|
|
1346
|
+
);
|
|
418
1347
|
export {
|
|
419
1348
|
AQUARIUM_SPEC,
|
|
1349
|
+
DASHBOARD_SPEC,
|
|
1350
|
+
EXAMPLE_BY_ID,
|
|
420
1351
|
EXAMPLE_SPECS,
|
|
1352
|
+
LANTERNS_SPEC,
|
|
421
1353
|
LIMITS,
|
|
422
1354
|
RAIN_SPEC,
|
|
1355
|
+
SAKURA_SPEC,
|
|
1356
|
+
SCHEMA_EXAMPLES,
|
|
423
1357
|
SCHEMA_VERSION,
|
|
1358
|
+
SNOWFALL_SPEC,
|
|
1359
|
+
alphaAt,
|
|
1360
|
+
applyDeltasToSpec,
|
|
424
1361
|
assertValidSpec,
|
|
425
1362
|
buildEntities,
|
|
426
1363
|
compileSaver,
|
|
1364
|
+
easeSmooth,
|
|
1365
|
+
lerpSpec,
|
|
427
1366
|
manifestFor,
|
|
428
1367
|
positionAt,
|
|
1368
|
+
resolveSpecPath,
|
|
429
1369
|
spriteVariants,
|
|
1370
|
+
structuralSignature,
|
|
430
1371
|
validateSpec
|
|
431
1372
|
};
|
|
432
1373
|
//# sourceMappingURL=index.js.map
|