@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.
- package/dist/components/AnimatedBackground/AnimatedBackground.d.ts +2 -31
- package/dist/components/AnimatedBackground/AnimatedBackground.js +102 -25
- package/dist/components/AnimatedBackground/GameHud.d.ts +15 -0
- package/dist/components/AnimatedBackground/GameHud.js +141 -0
- package/dist/components/AnimatedBackground/index.d.ts +2 -3
- package/dist/components/AnimatedBackground/index.js +1 -2
- package/dist/components/AnimatedBackground/module/constants.d.ts +28 -0
- package/dist/components/AnimatedBackground/module/constants.js +29 -0
- package/dist/components/AnimatedBackground/module/engine-colors.d.ts +16 -0
- package/dist/components/AnimatedBackground/module/engine-colors.js +49 -0
- package/dist/components/AnimatedBackground/module/engine-grid.d.ts +6 -0
- package/dist/components/AnimatedBackground/module/engine-grid.js +14 -0
- package/dist/components/AnimatedBackground/module/engine.d.ts +33 -0
- package/dist/components/AnimatedBackground/module/engine.js +206 -0
- package/dist/components/AnimatedBackground/module/game-logic.d.ts +87 -0
- package/dist/components/AnimatedBackground/module/game-logic.js +472 -0
- package/dist/components/AnimatedBackground/module/game-renderer.d.ts +16 -0
- package/dist/components/AnimatedBackground/module/game-renderer.js +121 -0
- package/dist/components/AnimatedBackground/module/index.d.ts +4 -0
- package/dist/components/AnimatedBackground/module/index.js +4 -0
- package/dist/components/AnimatedBackground/module/lib.d.ts +2 -0
- package/dist/components/AnimatedBackground/{lib.js → module/lib.js} +2 -1
- package/dist/components/AnimatedBackground/module/types.d.ts +77 -0
- package/dist/components/AnimatedBackground/module/useGameKeyboard.d.ts +3 -0
- package/dist/components/AnimatedBackground/module/useGameKeyboard.js +73 -0
- package/dist/components/Flex/Flex.d.ts +1 -1
- package/dist/components/SegmentedControl/SegmentedControlSeparator.d.ts +1 -1
- package/dist/components/Separator/Separator.d.ts +1 -1
- package/dist/components/Skeleton/Skeleton.d.ts +1 -1
- package/dist/components/Stack/Stack.d.ts +1 -1
- package/dist/metadata/components.json +23 -13
- package/dist/theme/components/login-background.css +36 -0
- package/package.json +1 -1
- package/dist/components/AnimatedBackground/classes.d.ts +0 -3
- package/dist/components/AnimatedBackground/classes.js +0 -13
- package/dist/components/AnimatedBackground/engine.d.ts +0 -9
- package/dist/components/AnimatedBackground/engine.js +0 -217
- package/dist/components/AnimatedBackground/lib.d.ts +0 -3
- package/dist/components/AnimatedBackground/types.d.ts +0 -24
- /package/dist/components/AnimatedBackground/{types.js → module/types.js} +0 -0
|
@@ -1,32 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
export interface AnimatedBackgroundProps extends Omit<CanvasHTMLAttributes<HTMLCanvasElement>, 'children'> {
|
|
4
|
-
ref?: Ref<HTMLCanvasElement>;
|
|
5
|
-
/** Variant A (`clean`) vs Variant B (`halftone`). */
|
|
6
|
-
texture?: Texture;
|
|
7
|
-
/** Grid cell size in px. */
|
|
8
|
-
spacing?: number;
|
|
9
|
-
/** Seconds for one L->R sweep pass. */
|
|
10
|
-
sweepPeriod?: number;
|
|
11
|
-
/** How far from the scan line (px) dots react. */
|
|
12
|
-
bloomRadius?: number;
|
|
13
|
-
/** Minimum seconds between orange events. Higher = rarer. */
|
|
14
|
-
anomalyInterval?: number;
|
|
15
|
-
/** Global opacity/strength multiplier (0-1). */
|
|
16
|
-
intensity?: number;
|
|
17
|
-
/** Peak alpha of fully-bloomed (emphasized) dots, before intensity. */
|
|
18
|
-
bloomAlpha?: number;
|
|
19
|
-
/** Halftone only: cap on a bloomed pixel's full edge length (px). */
|
|
20
|
-
maxDotSize?: number;
|
|
21
|
-
/** Sweep-line tilt in degrees (0 = vertical; positive leans the top right). */
|
|
22
|
-
tilt?: number;
|
|
23
|
-
/** CSS custom-property name for the dot color. */
|
|
24
|
-
dotColorVar?: string;
|
|
25
|
-
/** CSS custom-property name for the "caught" accent. */
|
|
26
|
-
accentColorVar?: string;
|
|
27
|
-
/** CSS custom-property name for the base fill. */
|
|
28
|
-
baseColorVar?: string;
|
|
29
|
-
/** Force a single static frame (also auto-true under reduced motion). */
|
|
30
|
-
paused?: boolean;
|
|
31
|
-
}
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { AnimatedBackgroundProps } from './module';
|
|
32
3
|
export declare const AnimatedBackground: FC<AnimatedBackgroundProps>;
|
|
@@ -1,30 +1,50 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { cn } from "../../utils/cn.js";
|
|
4
|
-
import {
|
|
5
|
-
import { createSweepEngine } from "./
|
|
6
|
-
|
|
4
|
+
import { GameHud } from "./GameHud.js";
|
|
5
|
+
import { createSweepEngine, resolveOptions, useGameKeyboard } from "./module/index.js";
|
|
6
|
+
const GATE_TARGET = 5;
|
|
7
7
|
const AnimatedBackground = (props)=>{
|
|
8
|
-
const { ref, texture, paused, className, ...rest } = props;
|
|
8
|
+
const { ref, texture, paused, game = false, excludeCardSize, className, ...rest } = props;
|
|
9
9
|
const internalRef = useRef(null);
|
|
10
10
|
const engineRef = useRef(null);
|
|
11
|
+
const reducedMotionRef = useRef(null);
|
|
11
12
|
const options = resolveOptions(props);
|
|
12
13
|
const optionsRef = useRef(options);
|
|
13
14
|
const pausedRef = useRef(paused);
|
|
15
|
+
const gameRef = useRef(game);
|
|
14
16
|
useEffect(()=>{
|
|
15
17
|
optionsRef.current = options;
|
|
16
18
|
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
|
|
17
27
|
});
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
const [catchKey, setCatchKey] = useState(0);
|
|
29
|
+
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);
|
|
22
37
|
useEffect(()=>{
|
|
23
38
|
const canvas = internalRef.current;
|
|
24
39
|
if (!canvas) return;
|
|
25
40
|
const engine = createSweepEngine(canvas, optionsRef.current);
|
|
26
41
|
engineRef.current = engine;
|
|
42
|
+
engine.onStats((s)=>{
|
|
43
|
+
setStats(s);
|
|
44
|
+
if (gameRef.current && !s.done) setCatchKey((prev)=>prev + 1);
|
|
45
|
+
});
|
|
27
46
|
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
47
|
+
reducedMotionRef.current = reduced;
|
|
28
48
|
const apply = ()=>{
|
|
29
49
|
if (reduced.matches || pausedRef.current) {
|
|
30
50
|
engine.stop();
|
|
@@ -49,6 +69,7 @@ const AnimatedBackground = (props)=>{
|
|
|
49
69
|
});
|
|
50
70
|
return ()=>{
|
|
51
71
|
reduced.removeEventListener('change', apply);
|
|
72
|
+
reducedMotionRef.current = null;
|
|
52
73
|
ro.disconnect();
|
|
53
74
|
themeObserver.disconnect();
|
|
54
75
|
cancelAnimationFrame(frame);
|
|
@@ -56,30 +77,86 @@ const AnimatedBackground = (props)=>{
|
|
|
56
77
|
engineRef.current = null;
|
|
57
78
|
};
|
|
58
79
|
}, []);
|
|
80
|
+
const syncKey = `${JSON.stringify(options)}|${paused}`;
|
|
59
81
|
useEffect(()=>{
|
|
60
82
|
const engine = engineRef.current;
|
|
61
83
|
if (!engine) return;
|
|
62
84
|
engine.setOptions(optionsRef.current);
|
|
63
|
-
const reduced =
|
|
64
|
-
if (reduced
|
|
85
|
+
const reduced = reducedMotionRef.current;
|
|
86
|
+
if (reduced?.matches || pausedRef.current) {
|
|
65
87
|
engine.stop();
|
|
66
88
|
engine.renderStatic();
|
|
67
89
|
} else engine.start();
|
|
68
90
|
}, [
|
|
69
|
-
|
|
91
|
+
syncKey
|
|
92
|
+
]);
|
|
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
|
|
70
109
|
]);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 = internalRef.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
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
135
|
+
children: [
|
|
136
|
+
/*#__PURE__*/ jsx("canvas", {
|
|
137
|
+
...rest,
|
|
138
|
+
ref: (node)=>{
|
|
139
|
+
internalRef.current = node;
|
|
140
|
+
if ('function' == typeof ref) ref(node);
|
|
141
|
+
else if (ref) ref.current = node;
|
|
142
|
+
},
|
|
143
|
+
"data-slot": "animated-background",
|
|
144
|
+
"aria-hidden": "true",
|
|
145
|
+
className: cn('h-full w-full pointer-events-none', gameActive && 'pointer-events-auto', className),
|
|
146
|
+
onPointerDown: gameActive ? onPointerDown : void 0
|
|
147
|
+
}),
|
|
148
|
+
gameActive && /*#__PURE__*/ jsx(GameHud, {
|
|
149
|
+
caught: caught,
|
|
150
|
+
armed: armed,
|
|
151
|
+
roundOver: roundOver,
|
|
152
|
+
stats: stats,
|
|
153
|
+
accuracy: accuracy,
|
|
154
|
+
faced: faced,
|
|
155
|
+
catchKey: catchKey,
|
|
156
|
+
gateTarget: GATE_TARGET,
|
|
157
|
+
onTryAgain: handleTryAgain
|
|
158
|
+
})
|
|
159
|
+
]
|
|
83
160
|
});
|
|
84
161
|
};
|
|
85
162
|
AnimatedBackground.displayName = 'AnimatedBackground';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { GameStats } from './module';
|
|
3
|
+
interface GameHudProps {
|
|
4
|
+
caught: number;
|
|
5
|
+
armed: boolean;
|
|
6
|
+
roundOver: boolean;
|
|
7
|
+
stats: GameStats;
|
|
8
|
+
accuracy: number;
|
|
9
|
+
faced: number;
|
|
10
|
+
catchKey: number;
|
|
11
|
+
gateTarget: number;
|
|
12
|
+
onTryAgain: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare const GameHud: FC<GameHudProps>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../utils/cn.js";
|
|
3
|
+
const dotColor = 'var(--animated-bg-dot)';
|
|
4
|
+
const caughtColor = 'var(--animated-bg-caught-dot)';
|
|
5
|
+
const labelStyle = {
|
|
6
|
+
fontFeatureSettings: '"liga" 0',
|
|
7
|
+
color: dotColor
|
|
8
|
+
};
|
|
9
|
+
const GameHud = ({ caught, armed, roundOver, stats, accuracy, faced, catchKey, gateTarget, onTryAgain })=>{
|
|
10
|
+
const showCounter = caught > 0;
|
|
11
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
12
|
+
children: [
|
|
13
|
+
showCounter && /*#__PURE__*/ jsxs("div", {
|
|
14
|
+
className: "fixed top-24 right-24 flex flex-col items-end gap-6 font-mono pointer-events-none",
|
|
15
|
+
style: {
|
|
16
|
+
animation: 'hud-in 0.3s ease-out'
|
|
17
|
+
},
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ jsx("div", {
|
|
20
|
+
className: cn('flex flex-col items-center justify-center border', armed ? 'h-113 w-119' : 'h-52 w-119'),
|
|
21
|
+
style: {
|
|
22
|
+
borderColor: dotColor,
|
|
23
|
+
backgroundColor: `color-mix(in srgb, ${dotColor} 8%, transparent)`
|
|
24
|
+
},
|
|
25
|
+
children: armed ? /*#__PURE__*/ jsxs(Fragment, {
|
|
26
|
+
children: [
|
|
27
|
+
/*#__PURE__*/ jsxs("div", {
|
|
28
|
+
className: "flex flex-col items-center gap-4",
|
|
29
|
+
children: [
|
|
30
|
+
/*#__PURE__*/ jsxs("span", {
|
|
31
|
+
className: "text-base leading-xl font-bold",
|
|
32
|
+
style: {
|
|
33
|
+
fontFeatureSettings: '"tnum"',
|
|
34
|
+
color: caughtColor
|
|
35
|
+
},
|
|
36
|
+
children: [
|
|
37
|
+
stats.stopped,
|
|
38
|
+
/*#__PURE__*/ jsxs("span", {
|
|
39
|
+
className: "opacity-45",
|
|
40
|
+
children: [
|
|
41
|
+
" / ",
|
|
42
|
+
faced
|
|
43
|
+
]
|
|
44
|
+
})
|
|
45
|
+
]
|
|
46
|
+
}),
|
|
47
|
+
/*#__PURE__*/ jsx("span", {
|
|
48
|
+
className: "text-xs leading-xs uppercase",
|
|
49
|
+
style: labelStyle,
|
|
50
|
+
children: "HIT"
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
}),
|
|
54
|
+
/*#__PURE__*/ jsx("div", {
|
|
55
|
+
className: "h-px w-full my-8",
|
|
56
|
+
style: {
|
|
57
|
+
backgroundColor: dotColor
|
|
58
|
+
}
|
|
59
|
+
}),
|
|
60
|
+
/*#__PURE__*/ jsxs("div", {
|
|
61
|
+
className: "flex flex-col items-center gap-4",
|
|
62
|
+
children: [
|
|
63
|
+
/*#__PURE__*/ jsxs("span", {
|
|
64
|
+
className: "text-base leading-xl font-bold",
|
|
65
|
+
style: {
|
|
66
|
+
fontFeatureSettings: '"tnum"',
|
|
67
|
+
color: caughtColor
|
|
68
|
+
},
|
|
69
|
+
children: [
|
|
70
|
+
accuracy,
|
|
71
|
+
"%"
|
|
72
|
+
]
|
|
73
|
+
}),
|
|
74
|
+
/*#__PURE__*/ jsx("span", {
|
|
75
|
+
className: "text-xs leading-xs uppercase",
|
|
76
|
+
style: labelStyle,
|
|
77
|
+
children: "SCORE"
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
})
|
|
81
|
+
]
|
|
82
|
+
}) : /*#__PURE__*/ jsxs("div", {
|
|
83
|
+
className: "flex flex-col items-center gap-4",
|
|
84
|
+
children: [
|
|
85
|
+
/*#__PURE__*/ jsxs("span", {
|
|
86
|
+
className: "text-base leading-xl font-bold",
|
|
87
|
+
style: {
|
|
88
|
+
fontFeatureSettings: '"tnum"',
|
|
89
|
+
animation: 'catch-pop 0.25s ease-out',
|
|
90
|
+
color: caughtColor
|
|
91
|
+
},
|
|
92
|
+
children: [
|
|
93
|
+
caught,
|
|
94
|
+
/*#__PURE__*/ jsxs("span", {
|
|
95
|
+
className: "opacity-45",
|
|
96
|
+
children: [
|
|
97
|
+
" / ",
|
|
98
|
+
gateTarget
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
]
|
|
102
|
+
}, catchKey),
|
|
103
|
+
/*#__PURE__*/ jsx("span", {
|
|
104
|
+
className: "text-xs leading-xs uppercase",
|
|
105
|
+
style: labelStyle,
|
|
106
|
+
children: "INSERT COIN"
|
|
107
|
+
})
|
|
108
|
+
]
|
|
109
|
+
})
|
|
110
|
+
}),
|
|
111
|
+
roundOver && /*#__PURE__*/ jsx("button", {
|
|
112
|
+
type: "button",
|
|
113
|
+
className: "pointer-events-auto text-2xs leading-sm font-mono uppercase underline underline-offset-4 hover:text-[var(--animated-bg-accent-dot)]",
|
|
114
|
+
style: {
|
|
115
|
+
color: dotColor
|
|
116
|
+
},
|
|
117
|
+
onClick: onTryAgain,
|
|
118
|
+
children: "Try again"
|
|
119
|
+
}),
|
|
120
|
+
armed && !roundOver && /*#__PURE__*/ jsx("span", {
|
|
121
|
+
className: "text-2xs leading-sm",
|
|
122
|
+
style: {
|
|
123
|
+
color: dotColor
|
|
124
|
+
},
|
|
125
|
+
children: '← → move · space fire · esc to exit'
|
|
126
|
+
})
|
|
127
|
+
]
|
|
128
|
+
}),
|
|
129
|
+
caught > 0 && !armed && /*#__PURE__*/ jsx("div", {
|
|
130
|
+
className: "fixed bottom-24 left-1/2 -translate-x-1/2 text-xs pointer-events-none",
|
|
131
|
+
style: {
|
|
132
|
+
animation: 'hud-in 0.3s ease-out',
|
|
133
|
+
color: dotColor
|
|
134
|
+
},
|
|
135
|
+
children: 'Click the red anomalies \u2014 catch 5 to arm the cannon, then \u2190 \u2192 move \u00B7 space fire'
|
|
136
|
+
})
|
|
137
|
+
]
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
GameHud.displayName = 'GameHud';
|
|
141
|
+
export { GameHud };
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export { AnimatedBackground
|
|
2
|
-
export {
|
|
3
|
-
export type { EngineOptions, Texture } from './types';
|
|
1
|
+
export { AnimatedBackground } from './AnimatedBackground';
|
|
2
|
+
export type { AnimatedBackgroundProps } from './module';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const NOISE_FREQ_X = 0.045;
|
|
2
|
+
export declare const NOISE_FREQ_Y = 0.032;
|
|
3
|
+
export declare const NOISE_FREQ_T = 0.9;
|
|
4
|
+
export declare const AMB_SCALE = 0.11;
|
|
5
|
+
export declare const HALFTONE_BLOOM_PEAK = 0.62;
|
|
6
|
+
export declare const HALFTONE_BASE_ALPHA = 0.12;
|
|
7
|
+
export declare const ANOMALY_ALPHA_BASE = 0.45;
|
|
8
|
+
export declare const ANOMALY_VIS_THRESHOLD = 0.06;
|
|
9
|
+
export declare const MIN_DOT_VALUE = 0.05;
|
|
10
|
+
export declare const DOT_STEP_SCALE = 1.05;
|
|
11
|
+
export declare const DOT_STEP_BASE = 0.5;
|
|
12
|
+
export declare const CLEAN_BASE_ALPHA = 0.13;
|
|
13
|
+
export declare const CLEAN_DOT_HALF_BLOOM = 1.5;
|
|
14
|
+
export declare const CLEAN_DOT_HALF_BASE = 1;
|
|
15
|
+
export declare const GLOW_LIFETIME = 3.2;
|
|
16
|
+
export declare const GLOW_OUTER_ALPHA = 0.35;
|
|
17
|
+
export declare const GLOW_OUTER_HALF = 5;
|
|
18
|
+
export declare const GLOW_INNER_ALPHA = 0.95;
|
|
19
|
+
export declare const GLOW_INNER_HALF = 2.4;
|
|
20
|
+
export declare const SPAWN_PROBABILITY = 0.05;
|
|
21
|
+
export declare const SWEEP_OFFSET_GUARD = 8;
|
|
22
|
+
export declare const IDLE_MARGIN_X = 30;
|
|
23
|
+
export declare const IDLE_MARGIN_Y = 16;
|
|
24
|
+
export declare const LABEL_MIN_Y = 15;
|
|
25
|
+
export declare const LABEL_OFFSET_Y = 26;
|
|
26
|
+
export declare const LABEL_DRIFT = 8;
|
|
27
|
+
export declare const LABEL_SHADOW_ALPHA = 0.3;
|
|
28
|
+
export declare const LABEL_ALPHA_BOOST = 1.15;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const NOISE_FREQ_X = 0.045;
|
|
2
|
+
const NOISE_FREQ_Y = 0.032;
|
|
3
|
+
const NOISE_FREQ_T = 0.9;
|
|
4
|
+
const AMB_SCALE = 0.11;
|
|
5
|
+
const HALFTONE_BLOOM_PEAK = 0.62;
|
|
6
|
+
const HALFTONE_BASE_ALPHA = 0.12;
|
|
7
|
+
const ANOMALY_ALPHA_BASE = 0.45;
|
|
8
|
+
const ANOMALY_VIS_THRESHOLD = 0.06;
|
|
9
|
+
const MIN_DOT_VALUE = 0.05;
|
|
10
|
+
const DOT_STEP_SCALE = 1.05;
|
|
11
|
+
const DOT_STEP_BASE = 0.5;
|
|
12
|
+
const CLEAN_BASE_ALPHA = 0.13;
|
|
13
|
+
const CLEAN_DOT_HALF_BLOOM = 1.5;
|
|
14
|
+
const CLEAN_DOT_HALF_BASE = 1;
|
|
15
|
+
const GLOW_LIFETIME = 3.2;
|
|
16
|
+
const GLOW_OUTER_ALPHA = 0.35;
|
|
17
|
+
const GLOW_OUTER_HALF = 5;
|
|
18
|
+
const GLOW_INNER_ALPHA = 0.95;
|
|
19
|
+
const GLOW_INNER_HALF = 2.4;
|
|
20
|
+
const SPAWN_PROBABILITY = 0.05;
|
|
21
|
+
const SWEEP_OFFSET_GUARD = 8;
|
|
22
|
+
const IDLE_MARGIN_X = 30;
|
|
23
|
+
const IDLE_MARGIN_Y = 16;
|
|
24
|
+
const LABEL_MIN_Y = 15;
|
|
25
|
+
const LABEL_OFFSET_Y = 26;
|
|
26
|
+
const LABEL_DRIFT = 8;
|
|
27
|
+
const LABEL_SHADOW_ALPHA = 0.3;
|
|
28
|
+
const LABEL_ALPHA_BOOST = 1.15;
|
|
29
|
+
export { AMB_SCALE, ANOMALY_ALPHA_BASE, ANOMALY_VIS_THRESHOLD, CLEAN_BASE_ALPHA, CLEAN_DOT_HALF_BASE, CLEAN_DOT_HALF_BLOOM, DOT_STEP_BASE, DOT_STEP_SCALE, GLOW_INNER_ALPHA, GLOW_INNER_HALF, GLOW_LIFETIME, GLOW_OUTER_ALPHA, GLOW_OUTER_HALF, HALFTONE_BASE_ALPHA, HALFTONE_BLOOM_PEAK, IDLE_MARGIN_X, IDLE_MARGIN_Y, 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, SPAWN_PROBABILITY, SWEEP_OFFSET_GUARD };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface RGB {
|
|
2
|
+
r: number;
|
|
3
|
+
g: number;
|
|
4
|
+
b: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const FALLBACK_DOT: RGB;
|
|
7
|
+
export declare const FALLBACK_ACCENT: RGB;
|
|
8
|
+
export declare const FALLBACK_CAUGHT: RGB;
|
|
9
|
+
export declare const FALLBACK_BASE = "#f8fafc";
|
|
10
|
+
/** Number of discrete alpha levels in pre-computed palettes. */
|
|
11
|
+
export declare const ALPHA_STEPS = 16;
|
|
12
|
+
/** Pre-compute RGBA strings for 0-ALPHA_STEPS so the hot loop never allocates. */
|
|
13
|
+
export declare function buildPalette(rgb: RGB): string[];
|
|
14
|
+
/** Map a 0-1 alpha to the nearest palette index. */
|
|
15
|
+
export declare function alphaIdx(a: number): number;
|
|
16
|
+
export declare function parseColor(value: string): RGB | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const FALLBACK_DOT = {
|
|
2
|
+
r: 69,
|
|
3
|
+
g: 85,
|
|
4
|
+
b: 108
|
|
5
|
+
};
|
|
6
|
+
const FALLBACK_ACCENT = {
|
|
7
|
+
r: 251,
|
|
8
|
+
g: 44,
|
|
9
|
+
b: 54
|
|
10
|
+
};
|
|
11
|
+
const FALLBACK_CAUGHT = {
|
|
12
|
+
r: 0,
|
|
13
|
+
g: 201,
|
|
14
|
+
b: 80
|
|
15
|
+
};
|
|
16
|
+
const FALLBACK_BASE = '#f8fafc';
|
|
17
|
+
const ALPHA_STEPS = 16;
|
|
18
|
+
function buildPalette(rgb) {
|
|
19
|
+
const p = new Array(ALPHA_STEPS + 1);
|
|
20
|
+
for(let i = 0; i <= ALPHA_STEPS; i++)p[i] = `rgba(${rgb.r},${rgb.g},${rgb.b},${(i / ALPHA_STEPS).toFixed(3)})`;
|
|
21
|
+
return p;
|
|
22
|
+
}
|
|
23
|
+
function alphaIdx(a) {
|
|
24
|
+
return Math.min(ALPHA_STEPS, a * ALPHA_STEPS + 0.5 | 0);
|
|
25
|
+
}
|
|
26
|
+
function parseColor(value) {
|
|
27
|
+
const v = value.trim();
|
|
28
|
+
if (!v) return null;
|
|
29
|
+
if ('#' === v[0]) {
|
|
30
|
+
let hex = v.slice(1);
|
|
31
|
+
if (3 === hex.length) hex = hex.split('').map((c)=>c + c).join('');
|
|
32
|
+
if (6 !== hex.length) return null;
|
|
33
|
+
const n = parseInt(hex, 16);
|
|
34
|
+
if (Number.isNaN(n)) return null;
|
|
35
|
+
return {
|
|
36
|
+
r: n >> 16 & 255,
|
|
37
|
+
g: n >> 8 & 255,
|
|
38
|
+
b: 255 & n
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const m = v.match(/rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
|
|
42
|
+
if (m?.[1] && m[2] && m[3]) return {
|
|
43
|
+
r: +m[1],
|
|
44
|
+
g: +m[2],
|
|
45
|
+
b: +m[3]
|
|
46
|
+
};
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
export { ALPHA_STEPS, FALLBACK_ACCENT, FALLBACK_BASE, FALLBACK_CAUGHT, FALLBACK_DOT, alphaIdx, buildPalette, parseColor };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function buildGrid(w, h, sp) {
|
|
2
|
+
const cap = 20000;
|
|
3
|
+
const safeSp = Math.max(sp, Math.sqrt(w * h / cap));
|
|
4
|
+
const a = [];
|
|
5
|
+
for(let y = safeSp / 2; y < h; y += safeSp)for(let x = safeSp / 2; x < w; x += safeSp)a.push({
|
|
6
|
+
x,
|
|
7
|
+
y
|
|
8
|
+
});
|
|
9
|
+
return a;
|
|
10
|
+
}
|
|
11
|
+
function sweepX(t, w, period) {
|
|
12
|
+
return t % period / period * (w + 140) - 70;
|
|
13
|
+
}
|
|
14
|
+
export { buildGrid, sweepX };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { EngineOptions, GameStats } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Framework-agnostic "detection sweep" canvas engine.
|
|
4
|
+
* A scan line crosses a dot grid; dots react as it passes; rare accent events
|
|
5
|
+
* appear behind the sweep. Two textures share this engine, selected via `texture`.
|
|
6
|
+
*
|
|
7
|
+
* Performance notes:
|
|
8
|
+
* - All dots use `fillRect` (no `arc`/`beginPath` overhead).
|
|
9
|
+
* - Color strings are pre-computed in palettes — zero allocation in the hot loop.
|
|
10
|
+
* - No `shadowBlur` — glow is faked with layered rects.
|
|
11
|
+
* - Game sim folds into the existing frame() behind mode guards.
|
|
12
|
+
* - Bounded pools (MAX_TARGETS, MAX_BULLETS). No per-frame allocation.
|
|
13
|
+
*/
|
|
14
|
+
export interface SweepEngine {
|
|
15
|
+
start(): void;
|
|
16
|
+
stop(): void;
|
|
17
|
+
setOptions(next: Partial<EngineOptions>): void;
|
|
18
|
+
renderStatic(t?: number): void;
|
|
19
|
+
resize(): void;
|
|
20
|
+
setGameActive(active: boolean): void;
|
|
21
|
+
catchAt(x: number, y: number): boolean;
|
|
22
|
+
setMode(mode: 'idle' | 'armed'): void;
|
|
23
|
+
startRound(): void;
|
|
24
|
+
exitGame(): void;
|
|
25
|
+
setCannonDir(dir: number): void;
|
|
26
|
+
setFiring(on: boolean): void;
|
|
27
|
+
onStats(cb: (s: GameStats) => void): void;
|
|
28
|
+
setExclusion(box: {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
} | null): void;
|
|
32
|
+
}
|
|
33
|
+
export declare function createSweepEngine(canvas: HTMLCanvasElement, options: EngineOptions): SweepEngine;
|