@wallarm-org/design-system 0.55.0 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/components/AnimatedBackground/AnimatedBackground.d.ts +2 -31
  2. package/dist/components/AnimatedBackground/AnimatedBackground.js +102 -25
  3. package/dist/components/AnimatedBackground/GameHud.d.ts +15 -0
  4. package/dist/components/AnimatedBackground/GameHud.js +141 -0
  5. package/dist/components/AnimatedBackground/index.d.ts +2 -3
  6. package/dist/components/AnimatedBackground/index.js +1 -2
  7. package/dist/components/AnimatedBackground/module/constants.d.ts +28 -0
  8. package/dist/components/AnimatedBackground/module/constants.js +29 -0
  9. package/dist/components/AnimatedBackground/module/engine-colors.d.ts +16 -0
  10. package/dist/components/AnimatedBackground/module/engine-colors.js +49 -0
  11. package/dist/components/AnimatedBackground/module/engine-grid.d.ts +6 -0
  12. package/dist/components/AnimatedBackground/module/engine-grid.js +14 -0
  13. package/dist/components/AnimatedBackground/module/engine.d.ts +33 -0
  14. package/dist/components/AnimatedBackground/module/engine.js +206 -0
  15. package/dist/components/AnimatedBackground/module/game-logic.d.ts +87 -0
  16. package/dist/components/AnimatedBackground/module/game-logic.js +472 -0
  17. package/dist/components/AnimatedBackground/module/game-renderer.d.ts +16 -0
  18. package/dist/components/AnimatedBackground/module/game-renderer.js +121 -0
  19. package/dist/components/AnimatedBackground/module/index.d.ts +4 -0
  20. package/dist/components/AnimatedBackground/module/index.js +4 -0
  21. package/dist/components/AnimatedBackground/module/lib.d.ts +2 -0
  22. package/dist/components/AnimatedBackground/{lib.js → module/lib.js} +2 -1
  23. package/dist/components/AnimatedBackground/module/types.d.ts +77 -0
  24. package/dist/components/AnimatedBackground/module/useGameKeyboard.d.ts +3 -0
  25. package/dist/components/AnimatedBackground/module/useGameKeyboard.js +73 -0
  26. package/dist/components/Flex/Flex.d.ts +1 -1
  27. package/dist/components/SegmentedControl/SegmentedControlSeparator.d.ts +1 -1
  28. package/dist/components/Separator/Separator.d.ts +1 -1
  29. package/dist/components/Skeleton/Skeleton.d.ts +1 -1
  30. package/dist/components/Stack/Stack.d.ts +1 -1
  31. package/dist/metadata/components.json +23 -13
  32. package/dist/theme/components/login-background.css +36 -0
  33. package/package.json +1 -1
  34. package/dist/components/AnimatedBackground/classes.d.ts +0 -3
  35. package/dist/components/AnimatedBackground/classes.js +0 -13
  36. package/dist/components/AnimatedBackground/engine.d.ts +0 -9
  37. package/dist/components/AnimatedBackground/engine.js +0 -217
  38. package/dist/components/AnimatedBackground/lib.d.ts +0 -3
  39. package/dist/components/AnimatedBackground/types.d.ts +0 -24
  40. /package/dist/components/AnimatedBackground/{types.js → module/types.js} +0 -0
@@ -0,0 +1,472 @@
1
+ import { ANOMALY_VIS_THRESHOLD, DOT_STEP_BASE, DOT_STEP_SCALE, IDLE_MARGIN_X, IDLE_MARGIN_Y } from "./constants.js";
2
+ import { sweepX } from "./engine-grid.js";
3
+ const ROUND_ATTACKS = 100;
4
+ const MAX_TARGETS = 6;
5
+ const MAX_BULLETS = 24;
6
+ const BULLET_SPEED = 720;
7
+ const FIRE_CADENCE = 0.18;
8
+ const CANNON_SPEED = 420;
9
+ const TARGET_LIFE = 4.0;
10
+ const ARMED_SPAWN = 0.55;
11
+ const HIT_R2 = 484;
12
+ const CLICK_RADIUS = 52;
13
+ const REVEAL_DELAY = 0.3;
14
+ const ARM_RISE = 0.45;
15
+ const FIRST_SPAWN_DELAY = 0.7;
16
+ const ANOMALY_R = 42;
17
+ const ANOMALY_R_SQ = ANOMALY_R * ANOMALY_R;
18
+ const CAUGHT_DUR = 1.4;
19
+ const CANNON_HALF_W = 16;
20
+ const CANNON_BASE_OFFSET = 26;
21
+ const CANNON_BARREL_Y = 40;
22
+ const HUD_CLEAR_W = 120;
23
+ const HUD_CLEAR_H = 140;
24
+ const CARD_CLEAR_PAD = 48;
25
+ const SPAWN_EDGE = 24;
26
+ const CANNON_CLEAR = 72;
27
+ const SPAWN_TRIES = 24;
28
+ const IDLE_ANOMALY_LIFE = 2.4;
29
+ const VERDICTS = [
30
+ 'BLOCKED',
31
+ 'TERMINATED',
32
+ 'NEUTRALIZED',
33
+ 'QUARANTINED',
34
+ 'MITIGATED',
35
+ 'CONTAINED'
36
+ ];
37
+ function createGameLogic(host) {
38
+ const state = {
39
+ gameActive: false,
40
+ gameMode: 'idle',
41
+ cannonX: 0,
42
+ armT: 0,
43
+ fontLoaded: false
44
+ };
45
+ const anomalies = [];
46
+ const bullets = [];
47
+ const caughtEffects = [];
48
+ let cannonDir = 0;
49
+ let firing = false;
50
+ let lastFire = -1 / 0;
51
+ let killTotal = 0;
52
+ let roundKills = 0;
53
+ let roundEscaped = 0;
54
+ let roundSpawned = 0;
55
+ let roundDone = false;
56
+ let lastSpawn = -1 / 0;
57
+ let statsCb = null;
58
+ let exclusionBox = null;
59
+ if ("u" > typeof document) document.fonts.load('9px "Press Start 2P"').then(()=>{
60
+ state.fontLoaded = true;
61
+ }).catch(()=>{});
62
+ function emitStats() {
63
+ statsCb?.({
64
+ kills: killTotal,
65
+ stopped: roundKills,
66
+ escaped: roundEscaped,
67
+ spawned: roundSpawned,
68
+ done: roundDone
69
+ });
70
+ }
71
+ function spawnRing() {
72
+ const { w, h } = host;
73
+ if (!exclusionBox) return {
74
+ x: w * (0.1 + 0.8 * Math.random()),
75
+ y: h * (0.1 + 0.35 * Math.random())
76
+ };
77
+ const exW = Math.min(exclusionBox.width, w);
78
+ const exH = Math.min(exclusionBox.height, h);
79
+ const cl = (w - exW) / 2 - CARD_CLEAR_PAD;
80
+ const cr = (w + exW) / 2 + CARD_CLEAR_PAD;
81
+ const ct = (h - exH) / 2 - CARD_CLEAR_PAD;
82
+ const cb = (h + exH) / 2 + CARD_CLEAR_PAD;
83
+ for(let i = 0; i < SPAWN_TRIES; i++){
84
+ const x = SPAWN_EDGE + Math.random() * Math.max(1, w - 2 * SPAWN_EDGE);
85
+ const y = SPAWN_EDGE + Math.random() * Math.max(1, h - SPAWN_EDGE - CANNON_CLEAR);
86
+ if (!(x > cl) || !(x < cr) || !(y > ct) || !(y < cb)) {
87
+ if (!(x > w - HUD_CLEAR_W) || !(y < HUD_CLEAR_H)) return {
88
+ x,
89
+ y
90
+ };
91
+ }
92
+ }
93
+ const bands = [
94
+ {
95
+ area: cl - SPAWN_EDGE,
96
+ cx: (SPAWN_EDGE + cl) / 2,
97
+ cy: h / 2
98
+ },
99
+ {
100
+ area: w - SPAWN_EDGE - cr,
101
+ cx: (cr + w - SPAWN_EDGE) / 2,
102
+ cy: h / 2
103
+ },
104
+ {
105
+ area: ct - SPAWN_EDGE,
106
+ cx: w / 2,
107
+ cy: (SPAWN_EDGE + ct) / 2
108
+ },
109
+ {
110
+ area: h - CANNON_CLEAR - cb,
111
+ cx: w / 2,
112
+ cy: (cb + h - CANNON_CLEAR) / 2
113
+ }
114
+ ];
115
+ let best = bands[0];
116
+ for (const b of bands)if (b.area > best.area) best = b;
117
+ if (best.area > 0) return {
118
+ x: best.cx,
119
+ y: best.cy
120
+ };
121
+ return {
122
+ x: w / 2,
123
+ y: SPAWN_EDGE
124
+ };
125
+ }
126
+ function spawnIdleAnomaly(t) {
127
+ const { w, h } = host;
128
+ if (exclusionBox) {
129
+ const pos = spawnRing();
130
+ if (!pos) return null;
131
+ return {
132
+ x: pos.x,
133
+ y: pos.y,
134
+ t0: t,
135
+ life: IDLE_ANOMALY_LIFE,
136
+ caught: false
137
+ };
138
+ }
139
+ return {
140
+ x: IDLE_MARGIN_X + Math.random() * Math.max(1, w - 2 * IDLE_MARGIN_X),
141
+ y: IDLE_MARGIN_Y + Math.random() * Math.max(1, h - 2 * IDLE_MARGIN_Y),
142
+ t0: t,
143
+ life: IDLE_ANOMALY_LIFE,
144
+ caught: false
145
+ };
146
+ }
147
+ function neutralize(anomaly, t) {
148
+ anomaly.caught = true;
149
+ const cells = [];
150
+ const halfCap = host.opts.maxDotSize / 2;
151
+ const env = Math.max(0, Math.sin((t - anomaly.t0) / anomaly.life * Math.PI));
152
+ for (const dot of host.dots){
153
+ const dx = Math.abs(dot.x - anomaly.x);
154
+ const dy = Math.abs(dot.y - anomaly.y);
155
+ if (dx > ANOMALY_R || dy > ANOMALY_R) continue;
156
+ const distSq = dx * dx + dy * dy;
157
+ if (distSq > ANOMALY_R_SQ) continue;
158
+ const dist = Math.sqrt(distSq);
159
+ const ao = env * (1 - dist / ANOMALY_R);
160
+ if (ao < ANOMALY_VIS_THRESHOLD) continue;
161
+ const step = Math.min(Math.round(5 * ao) * DOT_STEP_SCALE + DOT_STEP_BASE, halfCap);
162
+ cells.push({
163
+ x: dot.x,
164
+ y: dot.y,
165
+ half: step,
166
+ ao
167
+ });
168
+ }
169
+ const label = VERDICTS[Math.random() * VERDICTS.length | 0];
170
+ caughtEffects.push({
171
+ x: anomaly.x,
172
+ y: anomaly.y,
173
+ t0: t,
174
+ cells,
175
+ label
176
+ });
177
+ recordKill();
178
+ }
179
+ function recordKill() {
180
+ killTotal += 1;
181
+ if ('armed' === state.gameMode) roundKills += 1;
182
+ emitStats();
183
+ }
184
+ function endRound() {
185
+ state.gameMode = 'over';
186
+ roundDone = true;
187
+ bullets.length = 0;
188
+ firing = false;
189
+ cannonDir = 0;
190
+ emitStats();
191
+ }
192
+ function isHittable(anomaly, t) {
193
+ if (anomaly.caught) return false;
194
+ const env = Math.max(0, Math.sin((t - anomaly.t0) / anomaly.life * Math.PI));
195
+ if (env < ANOMALY_VIS_THRESHOLD) return false;
196
+ const { w, h, opts, tanTilt } = host;
197
+ const sx = sweepX(t, w, opts.sweepPeriod);
198
+ if ('armed' === state.gameMode || 'over' === state.gameMode) {
199
+ const sxAtY = sx + (h / 2 - anomaly.y) * tanTilt;
200
+ return anomaly.x < sxAtY || t - anomaly.t0 > REVEAL_DELAY;
201
+ }
202
+ return anomaly.x - ANOMALY_R < sx + (h / 2 - anomaly.y) * tanTilt;
203
+ }
204
+ function pruneExpired(t) {
205
+ let kept = 0;
206
+ for (const anomaly of anomalies)if (!anomaly.caught && !(t - anomaly.t0 > anomaly.life)) anomalies[kept++] = anomaly;
207
+ anomalies.length = kept;
208
+ }
209
+ function idleSim(t) {
210
+ let liveCount = 0;
211
+ for (const anomaly of anomalies)if (!anomaly.caught) liveCount++;
212
+ if (liveCount < 2 && t - lastSpawn > host.opts.anomalyInterval) {
213
+ const anomaly = spawnIdleAnomaly(t);
214
+ if (anomaly) {
215
+ anomalies.push(anomaly);
216
+ lastSpawn = t;
217
+ }
218
+ }
219
+ pruneExpired(t);
220
+ }
221
+ function updateCannon(dt) {
222
+ const { w } = host;
223
+ state.cannonX += cannonDir * CANNON_SPEED * dt;
224
+ state.cannonX = Math.max(CANNON_HALF_W, Math.min(w - CANNON_HALF_W, state.cannonX));
225
+ }
226
+ function fireBullets(t) {
227
+ if (firing && t - lastFire >= FIRE_CADENCE && bullets.length < MAX_BULLETS) {
228
+ bullets.push({
229
+ x: state.cannonX,
230
+ y: host.h - CANNON_BARREL_Y
231
+ });
232
+ lastFire = t;
233
+ }
234
+ }
235
+ function moveBullets(dt) {
236
+ let bulletKept = 0;
237
+ for(let i = 0; i < bullets.length; i++){
238
+ const bullet = bullets[i];
239
+ bullet.y -= BULLET_SPEED * dt;
240
+ if (!(bullet.y < -14)) bullets[bulletKept++] = bullet;
241
+ }
242
+ bullets.length = bulletKept;
243
+ }
244
+ function checkCollisions(t) {
245
+ let bulletWrite = 0;
246
+ for(let bi = 0; bi < bullets.length; bi++){
247
+ const bullet = bullets[bi];
248
+ let consumed = false;
249
+ for (const anomaly of anomalies){
250
+ if (anomaly.caught) continue;
251
+ if (!isHittable(anomaly, t)) continue;
252
+ const dx = bullet.x - anomaly.x;
253
+ const dy = bullet.y - anomaly.y;
254
+ if (dx * dx + dy * dy < HIT_R2) {
255
+ neutralize(anomaly, t);
256
+ consumed = true;
257
+ break;
258
+ }
259
+ }
260
+ if (!consumed) bullets[bulletWrite++] = bullet;
261
+ }
262
+ bullets.length = bulletWrite;
263
+ }
264
+ function spawnTargets(t) {
265
+ const sinceArm = t - state.armT;
266
+ if (sinceArm > FIRST_SPAWN_DELAY && roundSpawned < ROUND_ATTACKS && t - lastSpawn >= ARMED_SPAWN) {
267
+ let liveCount = 0;
268
+ for (const anomaly of anomalies)if (!anomaly.caught) liveCount++;
269
+ if (liveCount < MAX_TARGETS) {
270
+ const pos = spawnRing();
271
+ if (pos) {
272
+ anomalies.push({
273
+ x: pos.x,
274
+ y: pos.y,
275
+ t0: t,
276
+ life: TARGET_LIFE,
277
+ caught: false
278
+ });
279
+ roundSpawned += 1;
280
+ lastSpawn = t;
281
+ }
282
+ }
283
+ }
284
+ }
285
+ function pruneAndCount(t) {
286
+ let anomalyKept = 0;
287
+ let escapedCount = 0;
288
+ for (const anomaly of anomalies)if (!anomaly.caught) {
289
+ if (t - anomaly.t0 > anomaly.life) {
290
+ escapedCount++;
291
+ continue;
292
+ }
293
+ anomalies[anomalyKept++] = anomaly;
294
+ }
295
+ anomalies.length = anomalyKept;
296
+ if (escapedCount > 0) {
297
+ roundEscaped += escapedCount;
298
+ emitStats();
299
+ }
300
+ if (roundSpawned >= ROUND_ATTACKS && 0 === anomalies.length) endRound();
301
+ }
302
+ function gameSim(t, dt) {
303
+ if ('idle' === state.gameMode) return void idleSim(t);
304
+ if ('over' === state.gameMode) return void pruneExpired(t);
305
+ updateCannon(dt);
306
+ fireBullets(t);
307
+ moveBullets(dt);
308
+ checkCollisions(t);
309
+ spawnTargets(t);
310
+ pruneAndCount(t);
311
+ }
312
+ function pruneCaughtEffects(t) {
313
+ let kept = 0;
314
+ for (const effect of caughtEffects)if (!(t - effect.t0 >= CAUGHT_DUR)) caughtEffects[kept++] = effect;
315
+ caughtEffects.length = kept;
316
+ }
317
+ function adjustTimeMarkers(skip) {
318
+ for (const anomaly of anomalies)anomaly.t0 += skip;
319
+ for (const effect of caughtEffects)effect.t0 += skip;
320
+ if (state.armT > 0) state.armT += skip;
321
+ if (lastSpawn > -1 / 0 && 0 !== lastSpawn) lastSpawn += skip;
322
+ }
323
+ function setGameActive(active) {
324
+ if (state.gameActive === active) return;
325
+ state.gameActive = active;
326
+ if (active) lastSpawn = -1 / 0;
327
+ else {
328
+ anomalies.length = 0;
329
+ bullets.length = 0;
330
+ caughtEffects.length = 0;
331
+ state.gameMode = 'idle';
332
+ killTotal = 0;
333
+ roundKills = 0;
334
+ roundEscaped = 0;
335
+ roundSpawned = 0;
336
+ roundDone = false;
337
+ firing = false;
338
+ cannonDir = 0;
339
+ }
340
+ }
341
+ function catchAt(x, y, running) {
342
+ if (!running) return false;
343
+ const now = performance.now() / 1000;
344
+ let best = null;
345
+ let bestDist = CLICK_RADIUS * CLICK_RADIUS;
346
+ for (const anomaly of anomalies){
347
+ if (!isHittable(anomaly, now)) continue;
348
+ const dx = x - anomaly.x;
349
+ const dy = y - anomaly.y;
350
+ const d2 = dx * dx + dy * dy;
351
+ if (d2 < bestDist) {
352
+ bestDist = d2;
353
+ best = anomaly;
354
+ }
355
+ }
356
+ if (best) {
357
+ neutralize(best, now);
358
+ return true;
359
+ }
360
+ return false;
361
+ }
362
+ function setMode(mode) {
363
+ if (mode === state.gameMode) return;
364
+ if ('idle' === mode) {
365
+ state.gameMode = 'idle';
366
+ lastSpawn = -1 / 0;
367
+ } else state.gameMode = 'armed';
368
+ }
369
+ function startRound() {
370
+ const { w } = host;
371
+ const now = performance.now() / 1000;
372
+ roundKills = 0;
373
+ roundEscaped = 0;
374
+ roundSpawned = 0;
375
+ roundDone = false;
376
+ state.gameMode = 'armed';
377
+ bullets.length = 0;
378
+ anomalies.length = 0;
379
+ caughtEffects.length = 0;
380
+ firing = false;
381
+ cannonDir = 0;
382
+ state.armT = now;
383
+ lastSpawn = now;
384
+ state.cannonX = w / 2;
385
+ emitStats();
386
+ }
387
+ function exitGame() {
388
+ killTotal = 0;
389
+ roundKills = 0;
390
+ roundEscaped = 0;
391
+ roundSpawned = 0;
392
+ roundDone = false;
393
+ state.gameMode = 'idle';
394
+ bullets.length = 0;
395
+ anomalies.length = 0;
396
+ caughtEffects.length = 0;
397
+ firing = false;
398
+ cannonDir = 0;
399
+ lastSpawn = -1 / 0;
400
+ emitStats();
401
+ }
402
+ function setCannonDir(dir) {
403
+ if ('armed' !== state.gameMode) return;
404
+ cannonDir = dir;
405
+ }
406
+ function setFiring(on) {
407
+ if ('armed' !== state.gameMode) return;
408
+ if (on && !firing) lastFire = -1 / 0;
409
+ firing = on;
410
+ }
411
+ function onStats(cb) {
412
+ statsCb = cb;
413
+ }
414
+ function setExclusion(box) {
415
+ if (!box) {
416
+ exclusionBox = null;
417
+ return;
418
+ }
419
+ exclusionBox = {
420
+ width: Math.min(box.width, host.w),
421
+ height: Math.min(box.height, host.h)
422
+ };
423
+ }
424
+ return {
425
+ anomalies,
426
+ bullets,
427
+ caughtEffects,
428
+ gameSim,
429
+ pruneCaughtEffects,
430
+ adjustTimeMarkers,
431
+ setGameActive,
432
+ catchAt,
433
+ setMode,
434
+ startRound,
435
+ exitGame,
436
+ setCannonDir,
437
+ setFiring,
438
+ onStats,
439
+ setExclusion,
440
+ get gameActive () {
441
+ return state.gameActive;
442
+ },
443
+ set gameActive (v){
444
+ state.gameActive = v;
445
+ },
446
+ get gameMode () {
447
+ return state.gameMode;
448
+ },
449
+ set gameMode (v){
450
+ state.gameMode = v;
451
+ },
452
+ get cannonX () {
453
+ return state.cannonX;
454
+ },
455
+ set cannonX (v){
456
+ state.cannonX = v;
457
+ },
458
+ get armT () {
459
+ return state.armT;
460
+ },
461
+ set armT (v){
462
+ state.armT = v;
463
+ },
464
+ get fontLoaded () {
465
+ return state.fontLoaded;
466
+ },
467
+ set fontLoaded (v){
468
+ state.fontLoaded = v;
469
+ }
470
+ };
471
+ }
472
+ export { ANOMALY_R, ANOMALY_R_SQ, ARMED_SPAWN, ARM_RISE, BULLET_SPEED, CANNON_BARREL_Y, CANNON_BASE_OFFSET, CANNON_CLEAR, CANNON_HALF_W, CANNON_SPEED, CARD_CLEAR_PAD, CAUGHT_DUR, CLICK_RADIUS, FIRE_CADENCE, FIRST_SPAWN_DELAY, HIT_R2, HUD_CLEAR_H, HUD_CLEAR_W, IDLE_ANOMALY_LIFE, MAX_BULLETS, MAX_TARGETS, REVEAL_DELAY, ROUND_ATTACKS, SPAWN_EDGE, SPAWN_TRIES, TARGET_LIFE, VERDICTS, createGameLogic };
@@ -0,0 +1,16 @@
1
+ import type { RGB } from './engine-colors';
2
+ import type { GameEngineHost, GameLogic } from './game-logic';
3
+ export interface GameRenderCtx {
4
+ ctx: CanvasRenderingContext2D;
5
+ dotPalette: string[];
6
+ accentPalette: string[];
7
+ caughtPalette: string[];
8
+ caughtRgb: RGB;
9
+ shadowPalette: string[];
10
+ }
11
+ export declare function createGameRenderer(rc: GameRenderCtx, game: GameLogic, host: GameEngineHost): {
12
+ drawGameDots: (t: number) => void;
13
+ drawCaughtEffects: (t: number) => void;
14
+ drawCannon: (t: number) => void;
15
+ drawBullets: () => void;
16
+ };
@@ -0,0 +1,121 @@
1
+ import { AMB_SCALE, ANOMALY_ALPHA_BASE, ANOMALY_VIS_THRESHOLD, DOT_STEP_BASE, DOT_STEP_SCALE, HALFTONE_BASE_ALPHA, HALFTONE_BLOOM_PEAK, LABEL_ALPHA_BOOST, LABEL_DRIFT, LABEL_MIN_Y, LABEL_OFFSET_Y, LABEL_SHADOW_ALPHA, MIN_DOT_VALUE, NOISE_FREQ_T, NOISE_FREQ_X, NOISE_FREQ_Y } from "./constants.js";
2
+ import { ALPHA_STEPS, alphaIdx } from "./engine-colors.js";
3
+ import { sweepX } from "./engine-grid.js";
4
+ import { ANOMALY_R, ANOMALY_R_SQ, ARM_RISE, CANNON_BASE_OFFSET, CANNON_HALF_W, CAUGHT_DUR, REVEAL_DELAY } from "./game-logic.js";
5
+ function createGameRenderer(rc, game, host) {
6
+ let envCache = [];
7
+ let revealedCache = [];
8
+ function drawGameDots(t) {
9
+ const { ctx, dotPalette, accentPalette } = rc;
10
+ const { w, h, dots, opts, tanTilt } = host;
11
+ const sx = sweepX(t, w, opts.sweepPeriod);
12
+ const intensity = opts.intensity;
13
+ const halfCap = opts.maxDotSize / 2;
14
+ const liveAnomalies = game.anomalies;
15
+ const aLen = liveAnomalies.length;
16
+ const isIdle = 'idle' === game.gameMode;
17
+ if (envCache.length < aLen) {
18
+ envCache = new Array(aLen);
19
+ revealedCache = new Array(aLen);
20
+ }
21
+ for(let i = 0; i < aLen; i++){
22
+ const anomaly = liveAnomalies[i];
23
+ if (anomaly.caught) {
24
+ envCache[i] = 0;
25
+ revealedCache[i] = false;
26
+ } else {
27
+ envCache[i] = Math.max(0, Math.sin((t - anomaly.t0) / anomaly.life * Math.PI));
28
+ revealedCache[i] = isIdle ? true : anomaly.x < sx || t - anomaly.t0 > REVEAL_DELAY;
29
+ }
30
+ }
31
+ for (const dot of dots){
32
+ const sxAt = sx + (h / 2 - dot.y) * tanTilt;
33
+ const amb = AMB_SCALE * (0.5 + 0.5 * Math.sin(dot.x * NOISE_FREQ_X + dot.y * NOISE_FREQ_Y + t * NOISE_FREQ_T));
34
+ const distToSweep = Math.abs(dot.x - sxAt);
35
+ const bloom = distToSweep < opts.bloomRadius ? HALFTONE_BLOOM_PEAK * (1 - distToSweep / opts.bloomRadius) : 0;
36
+ let maxAo = 0;
37
+ for(let i = 0; i < aLen; i++){
38
+ const env = envCache[i];
39
+ if (0 === env) continue;
40
+ const anomaly = liveAnomalies[i];
41
+ const dx = Math.abs(dot.x - anomaly.x);
42
+ if (dx > ANOMALY_R) continue;
43
+ const dy = Math.abs(dot.y - anomaly.y);
44
+ if (dy > ANOMALY_R) continue;
45
+ const distSq = dx * dx + dy * dy;
46
+ if (distSq > ANOMALY_R_SQ) continue;
47
+ if (isIdle ? dot.x >= sxAt : !revealedCache[i]) continue;
48
+ const dist = Math.sqrt(distSq);
49
+ const ao = env * (1 - dist / ANOMALY_R);
50
+ if (ao > maxAo) maxAo = ao;
51
+ }
52
+ const val = Math.min(1, Math.max(amb + bloom, maxAo));
53
+ if (val < MIN_DOT_VALUE) continue;
54
+ const step = Math.round(5 * val);
55
+ const half = Math.min(step * DOT_STEP_SCALE + DOT_STEP_BASE, halfCap);
56
+ if (maxAo > ANOMALY_VIS_THRESHOLD) ctx.fillStyle = accentPalette[alphaIdx(Math.min(1, ANOMALY_ALPHA_BASE + maxAo))];
57
+ else ctx.fillStyle = dotPalette[alphaIdx((HALFTONE_BASE_ALPHA + val * (opts.bloomAlpha - HALFTONE_BASE_ALPHA)) * intensity)];
58
+ ctx.fillRect(dot.x - half, dot.y - half, 2 * half, 2 * half);
59
+ }
60
+ }
61
+ function drawCaughtEffects(t) {
62
+ const { ctx, caughtPalette, shadowPalette } = rc;
63
+ const effects = game.caughtEffects;
64
+ for (const effect of effects){
65
+ const age = t - effect.t0;
66
+ if (age >= CAUGHT_DUR) continue;
67
+ const progress = age / CAUGHT_DUR;
68
+ const fade = 1 - progress * progress;
69
+ for (const cell of effect.cells){
70
+ const alpha = Math.min(1, ANOMALY_ALPHA_BASE + cell.ao) * fade;
71
+ const idx = alphaIdx(alpha);
72
+ if (0 !== idx) {
73
+ ctx.fillStyle = caughtPalette[idx];
74
+ ctx.fillRect(cell.x - cell.half, cell.y - cell.half, 2 * cell.half, 2 * cell.half);
75
+ }
76
+ }
77
+ if (game.fontLoaded) {
78
+ const labelY = Math.round(Math.max(LABEL_MIN_Y, effect.y - LABEL_OFFSET_Y - progress * LABEL_DRIFT));
79
+ const labelX = Math.round(effect.x);
80
+ ctx.font = '9px "Press Start 2P"';
81
+ ctx.textAlign = 'center';
82
+ ctx.textBaseline = 'bottom';
83
+ ctx.fillStyle = shadowPalette[alphaIdx(LABEL_SHADOW_ALPHA * fade)];
84
+ ctx.fillText(effect.label, labelX + 1, labelY + 1);
85
+ ctx.fillStyle = caughtPalette[alphaIdx(Math.min(1, fade * LABEL_ALPHA_BOOST))];
86
+ ctx.fillText(effect.label, labelX, labelY);
87
+ }
88
+ }
89
+ }
90
+ function drawCannon(t) {
91
+ const { ctx, dotPalette } = rc;
92
+ if ('armed' !== game.gameMode && 'over' !== game.gameMode) return;
93
+ if ('armed' === game.gameMode) {
94
+ const riseP = Math.min(1, (t - game.armT) / ARM_RISE);
95
+ const ease = 1 - (1 - riseP) * (1 - riseP);
96
+ const offsetY = (1 - ease) * 40;
97
+ ctx.save();
98
+ ctx.globalAlpha = ease;
99
+ ctx.translate(0, offsetY);
100
+ }
101
+ const baseY = host.h - CANNON_BASE_OFFSET;
102
+ const cx = Math.round(game.cannonX);
103
+ ctx.fillStyle = dotPalette[ALPHA_STEPS];
104
+ ctx.fillRect(cx - CANNON_HALF_W, baseY, 2 * CANNON_HALF_W, 8);
105
+ ctx.fillRect(cx - 6, baseY - 6, 12, 6);
106
+ ctx.fillRect(cx - 2, baseY - 12, 4, 6);
107
+ if ('armed' === game.gameMode) ctx.restore();
108
+ }
109
+ function drawBullets() {
110
+ const { ctx, dotPalette } = rc;
111
+ ctx.fillStyle = dotPalette[ALPHA_STEPS];
112
+ for (const bullet of game.bullets)ctx.fillRect(bullet.x - 2, bullet.y - 7, 4, 14);
113
+ }
114
+ return {
115
+ drawGameDots,
116
+ drawCaughtEffects,
117
+ drawCannon,
118
+ drawBullets
119
+ };
120
+ }
121
+ export { createGameRenderer };
@@ -0,0 +1,4 @@
1
+ export { createSweepEngine, type SweepEngine } from './engine';
2
+ export { resolveOptions } from './lib';
3
+ export type { AnimatedBackgroundProps, EngineOptions, GameStats, Texture } from './types';
4
+ export { useGameKeyboard } from './useGameKeyboard';
@@ -0,0 +1,4 @@
1
+ import { createSweepEngine } from "./engine.js";
2
+ import { resolveOptions } from "./lib.js";
3
+ import { useGameKeyboard } from "./useGameKeyboard.js";
4
+ export { createSweepEngine, resolveOptions, useGameKeyboard };
@@ -0,0 +1,2 @@
1
+ import type { AnimatedBackgroundProps, EngineOptions } from './types';
2
+ export declare const resolveOptions: (props: AnimatedBackgroundProps) => EngineOptions;
@@ -25,7 +25,8 @@ const resolveOptions = (props)=>{
25
25
  tilt: props.tilt ?? 16,
26
26
  dotColorVar: props.dotColorVar ?? '--animated-bg-dot',
27
27
  accentColorVar: props.accentColorVar ?? '--animated-bg-accent-dot',
28
- baseColorVar: props.baseColorVar ?? '--color-component-app-shell-bg'
28
+ baseColorVar: props.baseColorVar ?? '--color-component-app-shell-bg',
29
+ caughtColorVar: props.caughtColorVar ?? '--animated-bg-caught-dot'
29
30
  };
30
31
  };
31
32
  export { resolveOptions };