@wallarm-org/design-system 0.58.1 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/components/AnimatedBackground/AnimatedBackground.js +18 -82
  2. package/dist/components/AnimatedBackground/GameHud.d.ts +1 -0
  3. package/dist/components/AnimatedBackground/GameHud.js +2 -2
  4. package/dist/components/AnimatedBackground/module/celebration-renderer.d.ts +5 -0
  5. package/dist/components/AnimatedBackground/module/celebration-renderer.js +60 -0
  6. package/dist/components/AnimatedBackground/module/celebration.d.ts +102 -0
  7. package/dist/components/AnimatedBackground/module/celebration.js +628 -0
  8. package/dist/components/AnimatedBackground/module/engine-grid.d.ts +8 -1
  9. package/dist/components/AnimatedBackground/module/engine-grid.js +19 -5
  10. package/dist/components/AnimatedBackground/module/engine.d.ts +2 -0
  11. package/dist/components/AnimatedBackground/module/engine.js +24 -5
  12. package/dist/components/AnimatedBackground/module/game-logic.d.ts +8 -0
  13. package/dist/components/AnimatedBackground/module/game-logic.js +81 -37
  14. package/dist/components/AnimatedBackground/module/game-renderer.d.ts +1 -0
  15. package/dist/components/AnimatedBackground/module/game-renderer.js +51 -12
  16. package/dist/components/AnimatedBackground/module/index.d.ts +1 -0
  17. package/dist/components/AnimatedBackground/module/index.js +2 -1
  18. package/dist/components/AnimatedBackground/module/math.d.ts +4 -0
  19. package/dist/components/AnimatedBackground/module/math.js +10 -0
  20. package/dist/components/AnimatedBackground/module/sfx.d.ts +15 -0
  21. package/dist/components/AnimatedBackground/module/sfx.js +143 -0
  22. package/dist/components/AnimatedBackground/module/useGame.d.ts +22 -0
  23. package/dist/components/AnimatedBackground/module/useGame.js +112 -0
  24. package/dist/components/AnimatedBackground/module/useGameKeyboard.d.ts +1 -1
  25. package/dist/components/AnimatedBackground/module/useGameKeyboard.js +23 -14
  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 +2 -2
  32. package/package.json +1 -1
@@ -1,9 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useEffect, useRef } from "react";
3
3
  import { cn } from "../../utils/cn.js";
4
- import { GameHud } from "./GameHud.js";
5
- import { createSweepEngine, resolveOptions, useGameKeyboard } from "./module/index.js";
6
- const GATE_TARGET = 5;
4
+ import { createSweepEngine, resolveOptions, useGame } from "./module/index.js";
7
5
  const AnimatedBackground = (props)=>{
8
6
  const { ref, texture, paused, game = false, excludeCardSize, className, children, ...rest } = props;
9
7
  const canvasRef = useRef(null);
@@ -12,37 +10,23 @@ const AnimatedBackground = (props)=>{
12
10
  const options = resolveOptions(props);
13
11
  const optionsRef = useRef(options);
14
12
  const pausedRef = useRef(paused);
15
- const gameRef = useRef(game);
16
13
  useEffect(()=>{
17
14
  optionsRef.current = options;
18
15
  pausedRef.current = paused;
19
- gameRef.current = game;
20
- }, []);
21
- const [stats, setStats] = useState({
22
- kills: 0,
23
- stopped: 0,
24
- escaped: 0,
25
- spawned: 0,
26
- done: false
27
16
  });
28
- const [catchKey, setCatchKey] = useState(0);
29
17
  const isHalftone = (texture ?? 'halftone') === 'halftone';
30
- const gameActive = game && isHalftone;
31
- const caught = stats.kills;
32
- const armed = game && caught >= GATE_TARGET;
33
- const roundOver = armed && stats.done;
34
- const faced = stats.stopped + stats.escaped;
35
- const accuracy = faced > 0 ? Math.round(stats.stopped / faced * 100) : 100;
36
- const hasStartedRoundRef = useRef(false);
18
+ const { gameActive, onPointerDown, hudElement, onEngineCreated, onEngineDestroyed } = useGame({
19
+ game,
20
+ isHalftone,
21
+ excludeCardSize,
22
+ canvasRef
23
+ });
37
24
  useEffect(()=>{
38
25
  const canvas = canvasRef.current;
39
26
  if (!canvas) return;
40
27
  const engine = createSweepEngine(canvas, optionsRef.current);
41
28
  engineRef.current = engine;
42
- engine.onStats((s)=>{
43
- setStats(s);
44
- if (gameRef.current && !s.done) setCatchKey((prev)=>prev + 1);
45
- });
29
+ onEngineCreated(engine);
46
30
  const reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
47
31
  reducedMotionRef.current = reduced;
48
32
  const apply = ()=>{
@@ -75,8 +59,12 @@ const AnimatedBackground = (props)=>{
75
59
  cancelAnimationFrame(frame);
76
60
  engine.stop();
77
61
  engineRef.current = null;
62
+ onEngineDestroyed();
78
63
  };
79
- }, []);
64
+ }, [
65
+ onEngineCreated,
66
+ onEngineDestroyed
67
+ ]);
80
68
  const syncKey = `${JSON.stringify(options)}|${paused}`;
81
69
  useEffect(()=>{
82
70
  const engine = engineRef.current;
@@ -90,58 +78,6 @@ const AnimatedBackground = (props)=>{
90
78
  }, [
91
79
  syncKey
92
80
  ]);
93
- useEffect(()=>{
94
- engineRef.current?.setGameActive(game && isHalftone);
95
- }, [
96
- game,
97
- isHalftone
98
- ]);
99
- const exW = excludeCardSize?.width;
100
- const exH = excludeCardSize?.height;
101
- useEffect(()=>{
102
- engineRef.current?.setExclusion(null != exW && null != exH ? {
103
- width: exW,
104
- height: exH
105
- } : null);
106
- }, [
107
- exW,
108
- exH
109
- ]);
110
- useEffect(()=>{
111
- if (!game || !armed) return;
112
- if (hasStartedRoundRef.current) return void engineRef.current?.setMode('armed');
113
- hasStartedRoundRef.current = true;
114
- engineRef.current?.startRound();
115
- }, [
116
- game,
117
- armed
118
- ]);
119
- useGameKeyboard(engineRef, game, armed, roundOver, hasStartedRoundRef);
120
- const onPointerDown = useCallback((e)=>{
121
- if (!game) return;
122
- const canvas = canvasRef.current;
123
- if (!canvas) return;
124
- const rect = canvas.getBoundingClientRect();
125
- const x = e.clientX - rect.left;
126
- const y = e.clientY - rect.top;
127
- engineRef.current?.catchAt(x, y);
128
- }, [
129
- game
130
- ]);
131
- const handleTryAgain = useCallback(()=>{
132
- engineRef.current?.startRound();
133
- }, []);
134
- const gameHud = gameActive && /*#__PURE__*/ jsx(GameHud, {
135
- caught: caught,
136
- armed: armed,
137
- roundOver: roundOver,
138
- stats: stats,
139
- accuracy: accuracy,
140
- faced: faced,
141
- catchKey: catchKey,
142
- gateTarget: GATE_TARGET,
143
- onTryAgain: handleTryAgain
144
- });
145
81
  if (null == children) return /*#__PURE__*/ jsxs(Fragment, {
146
82
  children: [
147
83
  /*#__PURE__*/ jsx("canvas", {
@@ -154,9 +90,9 @@ const AnimatedBackground = (props)=>{
154
90
  },
155
91
  "aria-hidden": "true",
156
92
  className: cn('h-full w-full pointer-events-none', gameActive && 'pointer-events-auto', className),
157
- onPointerDown: gameActive ? onPointerDown : void 0
93
+ onPointerDown: onPointerDown
158
94
  }),
159
- gameHud
95
+ hudElement
160
96
  ]
161
97
  });
162
98
  return /*#__PURE__*/ jsxs("div", {
@@ -169,9 +105,9 @@ const AnimatedBackground = (props)=>{
169
105
  ref: canvasRef,
170
106
  "aria-hidden": "true",
171
107
  className: cn('absolute inset-0 h-full w-full pointer-events-none', gameActive && 'pointer-events-auto'),
172
- onPointerDown: gameActive ? onPointerDown : void 0
108
+ onPointerDown: onPointerDown
173
109
  }),
174
- gameHud,
110
+ hudElement,
175
111
  /*#__PURE__*/ jsx("div", {
176
112
  className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center",
177
113
  children: /*#__PURE__*/ jsx("div", {
@@ -10,6 +10,7 @@ interface GameHudProps {
10
10
  catchKey: number;
11
11
  gateTarget: number;
12
12
  onTryAgain: () => void;
13
+ soundOn: boolean;
13
14
  }
14
15
  export declare const GameHud: FC<GameHudProps>;
15
16
  export {};
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
- const GameHud = ({ caught, armed, roundOver, stats, accuracy, faced, catchKey, gateTarget, onTryAgain })=>{
3
+ const GameHud = ({ caught, armed, roundOver, stats, accuracy, faced, catchKey, gateTarget, onTryAgain, soundOn })=>{
4
4
  const showCounter = caught > 0;
5
5
  return /*#__PURE__*/ jsxs(Fragment, {
6
6
  children: [
@@ -99,7 +99,7 @@ const GameHud = ({ caught, armed, roundOver, stats, accuracy, faced, catchKey, g
99
99
  }),
100
100
  armed && !roundOver && /*#__PURE__*/ jsx("span", {
101
101
  className: "text-2xs leading-sm text-text-secondary",
102
- children: '← move · space fire · esc to exit'
102
+ children: `\u2190 \u2192 move \u00B7 space fire \u00B7 esc exit \u00B7 m sound ${soundOn ? 'off' : 'on'}`
103
103
  })
104
104
  ]
105
105
  }),
@@ -0,0 +1,5 @@
1
+ import type { CelState } from './celebration';
2
+ import type { GameEngineHost } from './game-logic';
3
+ import type { GameRenderCtx } from './game-renderer';
4
+ export declare function drawCelebrationOverlay(rc: GameRenderCtx, cel: CelState, t: number, host: GameEngineHost, fontLoaded: boolean): void;
5
+ export declare function getCelCannonOffset(cel: CelState | null, cannonAway: boolean, t: number, h: number): number;
@@ -0,0 +1,60 @@
1
+ import { cannonLiftOffset, computeHeadlineY, headlineAlpha, headlineRise } from "./celebration.js";
2
+ import { easeOut } from "./math.js";
3
+ function drawCelebrationOverlay(rc, cel, t, host, fontLoaded) {
4
+ const { ctx } = rc;
5
+ const { w, h, gridSp } = host;
6
+ for (const rocket of cel.rockets){
7
+ const re = t - rocket.t0;
8
+ if (re < 0 || rocket.burst) continue;
9
+ if (re >= rocket.dur) continue;
10
+ const p = easeOut(re / rocket.dur);
11
+ const rx = snap(rocket.sx + (rocket.tx - rocket.sx) * p, gridSp);
12
+ const ry = snap(rocket.sy + (rocket.ty - rocket.sy) * p, gridSp);
13
+ ctx.fillStyle = rc.dotPalette[rc.dotPalette.length - 1];
14
+ ctx.fillRect(rx - 4, ry - 4, 8, 8);
15
+ }
16
+ for (const p of cel.particles){
17
+ const alpha = Math.ceil((1 - p.age / p.life) * 4) / 4;
18
+ if (alpha <= 0) continue;
19
+ const sx = snap(p.x, gridSp);
20
+ const sy = snap(p.y, gridSp);
21
+ ctx.globalAlpha = alpha;
22
+ ctx.fillStyle = p.color;
23
+ ctx.fillRect(sx - p.half, sy - p.half, 2 * p.half, 2 * p.half);
24
+ }
25
+ ctx.globalAlpha = 1;
26
+ if (cel.headline && fontLoaded) {
27
+ const alpha = headlineAlpha(cel, t);
28
+ if (alpha > 0) {
29
+ const baseY = computeHeadlineY(h, host.exclusionBox);
30
+ const rise = headlineRise(cel, t);
31
+ const y = baseY + rise;
32
+ ctx.save();
33
+ ctx.globalAlpha = alpha;
34
+ ctx.font = '13px "Press Start 2P"';
35
+ ctx.textAlign = 'center';
36
+ ctx.textBaseline = 'middle';
37
+ ctx.fillStyle = `rgba(0,0,0,${(0.3 * alpha).toFixed(3)})`;
38
+ ctx.fillText(cel.headline, w / 2 + 1, y + 1);
39
+ ctx.fillStyle = rc.caughtPalette[rc.caughtPalette.length - 1];
40
+ ctx.fillText(cel.headline, w / 2, y);
41
+ if (cel.subline) {
42
+ ctx.font = '11px "Geist Mono", monospace';
43
+ ctx.globalAlpha = 0.85 * alpha;
44
+ ctx.fillStyle = rc.dotPalette[rc.dotPalette.length - 1];
45
+ ctx.fillText(cel.subline, w / 2, y + 24);
46
+ }
47
+ ctx.restore();
48
+ }
49
+ }
50
+ }
51
+ function getCelCannonOffset(cel, cannonAway, t, h) {
52
+ if (cannonAway) return h + 120;
53
+ if (!cel || cel.tier < 3) return 0;
54
+ return cannonLiftOffset(cel, t, h);
55
+ }
56
+ function snap(v, gridSp) {
57
+ if (gridSp <= 0) return v;
58
+ return gridSp / 2 + Math.round((v - gridSp / 2) / gridSp) * gridSp;
59
+ }
60
+ export { drawCelebrationOverlay, getCelCannonOffset };
@@ -0,0 +1,102 @@
1
+ import type { GameEngineHost } from './game-logic';
2
+ export declare const CEL_DUR: readonly [0, 2.6, 3.8, 4, 5.2];
3
+ export declare const CEL_MAX_PARTICLES = 900;
4
+ export declare const CEL_LIFT_AT = 0.5;
5
+ export declare const CEL_LIFT_DUR = 1.2;
6
+ export declare const CEL_PAL: readonly ["rgb(255,60,60)", "rgb(255,180,40)", "rgb(60,220,80)", "rgb(60,200,220)", "rgb(80,100,255)", "rgb(180,80,220)", "rgb(255,100,180)"];
7
+ /** Sentinel value for celDotEffect — means "use caught-green palette". */
8
+ export declare const CEL_CAUGHT_COL: "__caught__";
9
+ export interface CelParticle {
10
+ x: number;
11
+ y: number;
12
+ vx: number;
13
+ vy: number;
14
+ age: number;
15
+ life: number;
16
+ half: number;
17
+ color: string;
18
+ conf: boolean;
19
+ drag: number;
20
+ gravity: number;
21
+ }
22
+ export interface CelRocket {
23
+ t0: number;
24
+ sx: number;
25
+ sy: number;
26
+ tx: number;
27
+ ty: number;
28
+ dur: number;
29
+ burst: boolean;
30
+ }
31
+ export interface CelPulse {
32
+ t0: number;
33
+ x: number;
34
+ y: number;
35
+ dur: number;
36
+ }
37
+ export interface CelState {
38
+ t0: number;
39
+ tier: number;
40
+ score: number;
41
+ settled: boolean;
42
+ particles: CelParticle[];
43
+ rockets: CelRocket[];
44
+ pulses: CelPulse[];
45
+ headline: string;
46
+ subline: string;
47
+ scoreCells: Map<number, true>;
48
+ scoreColCount: number;
49
+ liftStarted: boolean;
50
+ confettiSpawned2: number;
51
+ confettiVolley1: boolean;
52
+ confettiVolley2: boolean;
53
+ burstSpawned: boolean;
54
+ rainAccum: number;
55
+ }
56
+ export declare function tierForScore(score: number): number;
57
+ export declare function buildScoreCells(score: number, gridCols: number, gridSp: number, w: number, h: number, exclusionBox: {
58
+ width: number;
59
+ height: number;
60
+ } | null): {
61
+ cells: Map<number, true>;
62
+ colCount: number;
63
+ };
64
+ export declare function startCelebration(score: number, t: number, host: GameEngineHost): CelState | null;
65
+ export declare function stepCelebration(cel: CelState, t: number, dt: number, host: GameEngineHost, caughtColor: string, dotColor: string): void;
66
+ export declare function adjustCelebrationTimeMarkers(cel: CelState, skip: number): void;
67
+ export interface CelDotParams {
68
+ elapsed: number;
69
+ tier: number;
70
+ waveRadius: number;
71
+ waveStrength: number;
72
+ waveOriginX: number;
73
+ waveOriginY: number;
74
+ waveSigmaSq2: number;
75
+ blastRadius: number;
76
+ blastStrength: number;
77
+ blastOriginX: number;
78
+ blastOriginY: number;
79
+ blastSigmaSq2: number;
80
+ sweepX: number;
81
+ sweepActive: boolean;
82
+ rainbowQ: number;
83
+ rainbowActive: boolean;
84
+ t: number;
85
+ cel: CelState;
86
+ }
87
+ export declare function computeCelFrameParams(cel: CelState, t: number, w: number, h: number): CelDotParams;
88
+ /**
89
+ * Per-dot celebration effect — returns boost/color modifiers or null.
90
+ * Called inside the hot dot loop only when cel !== null.
91
+ */
92
+ export declare function celDotEffect(dotIndex: number, dotX: number, dotY: number, p: CelDotParams, w: number, h: number): {
93
+ celBoost: number;
94
+ celCol: string | null;
95
+ } | null;
96
+ export declare function computeHeadlineY(h: number, exclusionBox: {
97
+ width: number;
98
+ height: number;
99
+ } | null): number;
100
+ export declare function headlineAlpha(cel: CelState, t: number): number;
101
+ export declare function headlineRise(cel: CelState, t: number): number;
102
+ export declare function cannonLiftOffset(cel: CelState, t: number, h: number): number;