dsh-dock 0.9.5 → 0.9.7
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/LICENSE +18 -18
- package/README.md +325 -325
- package/client.js +42 -8
- package/cordis.patch.yml +2 -2
- package/features/animation/host.js +660 -660
- package/features/animation/view.jsx +2247 -2247
- package/features/balance/host.js +494 -494
- package/features/balance/view.js +351 -351
- package/features/games/games/breakout.jsx +227 -227
- package/features/games/games/gomoku.jsx +244 -244
- package/features/games/games/racer.jsx +169 -169
- package/features/games/games/shared.jsx +118 -118
- package/features/games/games/snake.jsx +144 -144
- package/features/games/games/sokoban.jsx +180 -180
- package/features/games/games/tank.jsx +128 -128
- package/features/games/games/tetris.jsx +308 -308
- package/features/games/games/xiangqi.jsx +277 -277
- package/features/games/tank-core.js +152 -152
- package/features/games/view.jsx +639 -639
- package/features/games/xiangqi-core.js +299 -299
- package/features/heartbeat/view.js +35 -35
- package/features/mobile-relay/gateway.js +472 -415
- package/features/mobile-relay/host.js +570 -546
- package/features/mobile-relay/view.jsx +300 -300
- package/features/modelconfig/host.js +403 -403
- package/features/modelconfig/view.js +490 -490
- package/features/theme/view.js +61 -61
- package/features/tokenlog/host.js +678 -678
- package/features/tokenlog/view.jsx +561 -561
- package/features/visionproxy/host.js +315 -315
- package/index.js +175 -118
- package/package.json +59 -59
- package/src/client.jsx +668 -668
- package/src/host-core.js +98 -95
- package/src/shared.js +167 -130
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
/* 趣味游戏 · 坦克大战(经典机台)——纯 Client;击毁敌方坦克、守住基地过关。 */
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
import { GRID, EMPTY, STEEL, BRICK, BASE, initialState, step, nextWave, findBase } from "../tank-core.js";
|
|
4
|
-
import { focusStage, useGameControls } from "./shared.jsx";
|
|
5
|
-
|
|
6
|
-
const DIR_ANGLE = { up: 0, right: 90, down: 180, left: 270 };
|
|
7
|
-
const ENEMY_COLORS = ["#f87171", "#fb923c", "#c084fc", "#4ade80", "#38bdf8"];
|
|
8
|
-
|
|
9
|
-
export function TankGame(props) {
|
|
10
|
-
const [snap, setSnap] = useState(initialState);
|
|
11
|
-
const [status, setStatus] = useState("play"); // play | wave | over | win
|
|
12
|
-
const snapRef = useRef(snap); snapRef.current = snap;
|
|
13
|
-
const keys = useRef({ dir: null, shoot: false });
|
|
14
|
-
const pausedRef = useRef(!!(props && props.paused)); pausedRef.current = !!(props && props.paused);
|
|
15
|
-
const statusRef = useRef("play"); statusRef.current = status;
|
|
16
|
-
const boardRef = useRef(null);
|
|
17
|
-
|
|
18
|
-
const start = useCallback(() => {
|
|
19
|
-
setSnap(initialState()); setStatus("play"); statusRef.current = "play";
|
|
20
|
-
}, []);
|
|
21
|
-
const nextLvl = useCallback(() => {
|
|
22
|
-
setSnap((s) => nextWave(s)); setStatus("play"); statusRef.current = "play";
|
|
23
|
-
}, []);
|
|
24
|
-
|
|
25
|
-
// 主循环
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
const timer = setInterval(() => {
|
|
28
|
-
if (pausedRef.current) return;
|
|
29
|
-
setSnap((s) => {
|
|
30
|
-
if (s.over || s.win) return s;
|
|
31
|
-
const input = { dir: keys.current.dir, shoot: keys.current.shoot };
|
|
32
|
-
const ns = step(s, input);
|
|
33
|
-
if (ns.over && statusRef.current === "play") { statusRef.current = "over"; setStatus("over"); }
|
|
34
|
-
else if (ns.win && statusRef.current === "play") { statusRef.current = "wave"; setStatus("wave"); }
|
|
35
|
-
return ns;
|
|
36
|
-
});
|
|
37
|
-
}, 120);
|
|
38
|
-
return () => clearInterval(timer);
|
|
39
|
-
}, []);
|
|
40
|
-
|
|
41
|
-
const stageRef = useRef(null);
|
|
42
|
-
const onKeyDown = useCallback((event) => {
|
|
43
|
-
if (pausedRef.current) return;
|
|
44
|
-
const map = { ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right", w: "up", s: "down", a: "left", d: "right", W: "up", S: "down", A: "left", D: "right" };
|
|
45
|
-
const dir = map[event.key];
|
|
46
|
-
if (dir) { event.preventDefault(); keys.current.dir = dir; return; }
|
|
47
|
-
if (event.key === " ") { event.preventDefault(); keys.current.shoot = true; }
|
|
48
|
-
else if (event.key.toLowerCase() === "r") { event.preventDefault(); start(); }
|
|
49
|
-
}, [start]);
|
|
50
|
-
const onKeyUp = useCallback((event) => {
|
|
51
|
-
const map = { ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right", w: "up", s: "down", a: "left", d: "right", W: "up", S: "down", A: "left", D: "right" };
|
|
52
|
-
const dir = map[event.key];
|
|
53
|
-
if (dir && keys.current.dir === dir) keys.current.dir = null;
|
|
54
|
-
if (event.key === " ") keys.current.shoot = false;
|
|
55
|
-
}, []);
|
|
56
|
-
useGameControls(stageRef, pausedRef.current, onKeyDown, onKeyUp);
|
|
57
|
-
|
|
58
|
-
const grid = snap.grid;
|
|
59
|
-
const player = snap.player;
|
|
60
|
-
const enemies = snap.enemies;
|
|
61
|
-
const bullets = snap.bullets;
|
|
62
|
-
let playerPos = null;
|
|
63
|
-
if (player && player.alive) playerPos = { r: player.r, c: player.c };
|
|
64
|
-
const enemyMap = {};
|
|
65
|
-
for (const e of enemies) enemyMap[e.r + "-" + e.c] = e;
|
|
66
|
-
const bulletMap = {};
|
|
67
|
-
for (const b of bullets) bulletMap[b.r + "-" + b.c] = b;
|
|
68
|
-
const basePos = findBase(grid);
|
|
69
|
-
|
|
70
|
-
const cells = [];
|
|
71
|
-
for (let r = 0; r < GRID; r += 1) {
|
|
72
|
-
for (let c = 0; c < GRID; c += 1) {
|
|
73
|
-
const key = r + "-" + c;
|
|
74
|
-
let cls = "dgame-tank-cell";
|
|
75
|
-
let inner = null;
|
|
76
|
-
if (grid[r][c] === STEEL) cls += " steel";
|
|
77
|
-
else if (grid[r][c] === BRICK) cls += " brick";
|
|
78
|
-
else if (grid[r][c] === BASE) cls += " base";
|
|
79
|
-
if (playerPos && playerPos.r === r && playerPos.c === c) {
|
|
80
|
-
cls += " pw";
|
|
81
|
-
inner = <i className="dgame-tank-body pw" style={{ transform: "rotate(" + DIR_ANGLE[player.dir] + "deg)" }} />;
|
|
82
|
-
} else if (enemyMap[key]) {
|
|
83
|
-
const e = enemyMap[key];
|
|
84
|
-
cls += " en";
|
|
85
|
-
inner = <i className="dgame-tank-body en" style={{ background: ENEMY_COLORS[(e.r + e.c) % ENEMY_COLORS.length], transform: "rotate(" + DIR_ANGLE[e.dir] + "deg)" }} />;
|
|
86
|
-
}
|
|
87
|
-
if (bullets && bulletMap[key]) cls += " bullet";
|
|
88
|
-
cells.push(<span key={key} className={cls}>{inner}{bulletMap[key] ? <i className="dgame-tank-bullet" aria-hidden="true" /> : null}{grid[r][c] === BASE ? <b className="dgame-tank-flag" aria-hidden="true">⚑</b> : null}</span>);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const statusText = status === "over" ? "基地失守 · 游戏结束" : status === "wave" ? "第 " + snap.wave + " 波清空!进入下一波" : ("第 " + snap.wave + " 波 · 待歼灭 " + (snap.remaining + snap.enemies.length) + " 辆 · 生命 × " + (player && player.lives));
|
|
93
|
-
|
|
94
|
-
return <section className="dgame-game" aria-label="坦克大战">
|
|
95
|
-
<div className="dgame-game-head"><div><h3>坦克大战</h3><p>方向键移动,空格开炮;击毁敌方坦克,别让敌军摧毁中央基地。</p></div><div className="dgame-score"><span>得分 <strong>{snap.score}</strong></span><span>生命 × {player && player.lives}</span></div></div>
|
|
96
|
-
<div className="dgame-tank-stage" ref={stageRef} tabIndex={0} role="grid" onKeyDown={onKeyDown} onKeyUp={onKeyUp} onClick={() => focusStage(stageRef)} aria-label="坦克大战游戏区域,方向键移动,空格开炮">
|
|
97
|
-
<div className="dgame-tank-grid">{cells}</div>
|
|
98
|
-
{status === "over" ? <div className="dgame-over"><strong>胜败已分 · 得 {snap.score} 分</strong><button type="button" onClick={start}>重新开始</button></div> : null}
|
|
99
|
-
{status === "wave" ? <div className="dgame-over"><strong>第 {snap.wave} 波清空!</strong><button type="button" onClick={nextLvl}>下一波</button></div> : null}
|
|
100
|
-
</div>
|
|
101
|
-
<div className="dgame-controls dgame-tank-controls">
|
|
102
|
-
<button type="button" aria-label="向上" onClick={() => { keys.current.dir = "up"; setTimeout(() => { if (keys.current.dir === "up") keys.current.dir = null; }, 90); }}>↑</button>
|
|
103
|
-
<span>方向键 / WASD · 空格开炮 · R 重开</span>
|
|
104
|
-
<button type="button" className="dgame-tank-fire" aria-label="开炮" onClick={() => { keys.current.shoot = true; setTimeout(() => { keys.current.shoot = false; }, 90); }}>开炮</button>
|
|
105
|
-
</div>
|
|
106
|
-
</section>;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function TankPreview() {
|
|
110
|
-
// 迷你战场:砖墙/钢墙色块 + 我方坦克 + 敌方坦克 + 基地,与真实游戏元素一致。
|
|
111
|
-
const ROWS = [
|
|
112
|
-
"..B..B..",
|
|
113
|
-
"S.....E.",
|
|
114
|
-
"..B.B...",
|
|
115
|
-
"B..S...B",
|
|
116
|
-
"...P..F.",
|
|
117
|
-
];
|
|
118
|
-
const cls = { ".": "void", B: "brick", S: "steel", P: "pw", E: "en", F: "base" };
|
|
119
|
-
return <span className="dgcov-prev dgcov-prev-tank" aria-hidden="true">
|
|
120
|
-
<span className="dgp-tank-grid">
|
|
121
|
-
{ROWS.flatMap((row, r) => row.split("").map((ch, c) => <i key={r + "-" + c} className={"dgp-tank-" + cls[ch]} />))}
|
|
122
|
-
</span>
|
|
123
|
-
</span>;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export const tankGame = { Game: TankGame, Preview: TankPreview, css: `
|
|
127
|
-
.dgame-tank-stage{position:relative;align-self:center;width:min(100%,320px);aspect-ratio:1/1;border:1px solid color-mix(in srgb,rgb(250 204 21) 32%,var(--dsw-alias-border-l1));border-radius:8px;overflow:hidden;outline:none;background:linear-gradient(180deg,#1a1425,#100d1c);box-shadow:inset 0 0 26px rgb(0 0 0/.5)}.dgame-tank-stage:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,rgb(250 204 21) 30%,transparent)}.dgame-tank-grid{display:grid;grid-template-columns:repeat(15,1fr);grid-template-rows:repeat(15,1fr);width:100%;height:100%;gap:0}.dgame-tank-cell{position:relative;background:rgba(255,255,255,.03);min-width:0;min-height:0}.dgame-tank-cell.steel{background:linear-gradient(180deg,#94a3b8,#64748b)}.dgame-tank-cell.brick{background:repeating-linear-gradient(45deg,#a3672a 0 55%,#8a5a1c 55% 100%);box-shadow:inset 0 0 0 1px rgba(0,0,0,.25)}.dgame-tank-cell.base{background:#fbbf24}.dgame-tank-cell.bullet::before{content:'';position:absolute;inset:38%;background:#fef3c7;box-shadow:0 0 5px #fef3c7;z-index:4}.dgame-tank-flag{position:absolute;inset:0;display:grid;place-items:center;color:#7c2d12;font-size:18px;z-index:2}.dgame-tank-body{position:absolute;inset:16%;display:block;border-radius:16%;z-index:3;transition:transform .1s ease}.dgame-tank-body.pw{background:linear-gradient(135deg,#fde047,#eab308);box-shadow:0 0 7px color-mix(in srgb,#eab308 55%,transparent)}.dgame-tank-body.en{background:#f87171;box-shadow:0 0 5px color-mix(in srgb,#f87171 40%,transparent)}.dgame-tank-body::before{content:'';position:absolute;left:38%;top:-30%;width:24%;height:64%;background:rgba(255,255,255,.5);border-radius:3px}.dgame-tank-controls{grid-template-columns:1fr auto 1fr}
|
|
128
|
-
` };
|
|
1
|
+
/* 趣味游戏 · 坦克大战(经典机台)——纯 Client;击毁敌方坦克、守住基地过关。 */
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { GRID, EMPTY, STEEL, BRICK, BASE, initialState, step, nextWave, findBase } from "../tank-core.js";
|
|
4
|
+
import { focusStage, useGameControls } from "./shared.jsx";
|
|
5
|
+
|
|
6
|
+
const DIR_ANGLE = { up: 0, right: 90, down: 180, left: 270 };
|
|
7
|
+
const ENEMY_COLORS = ["#f87171", "#fb923c", "#c084fc", "#4ade80", "#38bdf8"];
|
|
8
|
+
|
|
9
|
+
export function TankGame(props) {
|
|
10
|
+
const [snap, setSnap] = useState(initialState);
|
|
11
|
+
const [status, setStatus] = useState("play"); // play | wave | over | win
|
|
12
|
+
const snapRef = useRef(snap); snapRef.current = snap;
|
|
13
|
+
const keys = useRef({ dir: null, shoot: false });
|
|
14
|
+
const pausedRef = useRef(!!(props && props.paused)); pausedRef.current = !!(props && props.paused);
|
|
15
|
+
const statusRef = useRef("play"); statusRef.current = status;
|
|
16
|
+
const boardRef = useRef(null);
|
|
17
|
+
|
|
18
|
+
const start = useCallback(() => {
|
|
19
|
+
setSnap(initialState()); setStatus("play"); statusRef.current = "play";
|
|
20
|
+
}, []);
|
|
21
|
+
const nextLvl = useCallback(() => {
|
|
22
|
+
setSnap((s) => nextWave(s)); setStatus("play"); statusRef.current = "play";
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
// 主循环
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const timer = setInterval(() => {
|
|
28
|
+
if (pausedRef.current) return;
|
|
29
|
+
setSnap((s) => {
|
|
30
|
+
if (s.over || s.win) return s;
|
|
31
|
+
const input = { dir: keys.current.dir, shoot: keys.current.shoot };
|
|
32
|
+
const ns = step(s, input);
|
|
33
|
+
if (ns.over && statusRef.current === "play") { statusRef.current = "over"; setStatus("over"); }
|
|
34
|
+
else if (ns.win && statusRef.current === "play") { statusRef.current = "wave"; setStatus("wave"); }
|
|
35
|
+
return ns;
|
|
36
|
+
});
|
|
37
|
+
}, 120);
|
|
38
|
+
return () => clearInterval(timer);
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const stageRef = useRef(null);
|
|
42
|
+
const onKeyDown = useCallback((event) => {
|
|
43
|
+
if (pausedRef.current) return;
|
|
44
|
+
const map = { ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right", w: "up", s: "down", a: "left", d: "right", W: "up", S: "down", A: "left", D: "right" };
|
|
45
|
+
const dir = map[event.key];
|
|
46
|
+
if (dir) { event.preventDefault(); keys.current.dir = dir; return; }
|
|
47
|
+
if (event.key === " ") { event.preventDefault(); keys.current.shoot = true; }
|
|
48
|
+
else if (event.key.toLowerCase() === "r") { event.preventDefault(); start(); }
|
|
49
|
+
}, [start]);
|
|
50
|
+
const onKeyUp = useCallback((event) => {
|
|
51
|
+
const map = { ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right", w: "up", s: "down", a: "left", d: "right", W: "up", S: "down", A: "left", D: "right" };
|
|
52
|
+
const dir = map[event.key];
|
|
53
|
+
if (dir && keys.current.dir === dir) keys.current.dir = null;
|
|
54
|
+
if (event.key === " ") keys.current.shoot = false;
|
|
55
|
+
}, []);
|
|
56
|
+
useGameControls(stageRef, pausedRef.current, onKeyDown, onKeyUp);
|
|
57
|
+
|
|
58
|
+
const grid = snap.grid;
|
|
59
|
+
const player = snap.player;
|
|
60
|
+
const enemies = snap.enemies;
|
|
61
|
+
const bullets = snap.bullets;
|
|
62
|
+
let playerPos = null;
|
|
63
|
+
if (player && player.alive) playerPos = { r: player.r, c: player.c };
|
|
64
|
+
const enemyMap = {};
|
|
65
|
+
for (const e of enemies) enemyMap[e.r + "-" + e.c] = e;
|
|
66
|
+
const bulletMap = {};
|
|
67
|
+
for (const b of bullets) bulletMap[b.r + "-" + b.c] = b;
|
|
68
|
+
const basePos = findBase(grid);
|
|
69
|
+
|
|
70
|
+
const cells = [];
|
|
71
|
+
for (let r = 0; r < GRID; r += 1) {
|
|
72
|
+
for (let c = 0; c < GRID; c += 1) {
|
|
73
|
+
const key = r + "-" + c;
|
|
74
|
+
let cls = "dgame-tank-cell";
|
|
75
|
+
let inner = null;
|
|
76
|
+
if (grid[r][c] === STEEL) cls += " steel";
|
|
77
|
+
else if (grid[r][c] === BRICK) cls += " brick";
|
|
78
|
+
else if (grid[r][c] === BASE) cls += " base";
|
|
79
|
+
if (playerPos && playerPos.r === r && playerPos.c === c) {
|
|
80
|
+
cls += " pw";
|
|
81
|
+
inner = <i className="dgame-tank-body pw" style={{ transform: "rotate(" + DIR_ANGLE[player.dir] + "deg)" }} />;
|
|
82
|
+
} else if (enemyMap[key]) {
|
|
83
|
+
const e = enemyMap[key];
|
|
84
|
+
cls += " en";
|
|
85
|
+
inner = <i className="dgame-tank-body en" style={{ background: ENEMY_COLORS[(e.r + e.c) % ENEMY_COLORS.length], transform: "rotate(" + DIR_ANGLE[e.dir] + "deg)" }} />;
|
|
86
|
+
}
|
|
87
|
+
if (bullets && bulletMap[key]) cls += " bullet";
|
|
88
|
+
cells.push(<span key={key} className={cls}>{inner}{bulletMap[key] ? <i className="dgame-tank-bullet" aria-hidden="true" /> : null}{grid[r][c] === BASE ? <b className="dgame-tank-flag" aria-hidden="true">⚑</b> : null}</span>);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const statusText = status === "over" ? "基地失守 · 游戏结束" : status === "wave" ? "第 " + snap.wave + " 波清空!进入下一波" : ("第 " + snap.wave + " 波 · 待歼灭 " + (snap.remaining + snap.enemies.length) + " 辆 · 生命 × " + (player && player.lives));
|
|
93
|
+
|
|
94
|
+
return <section className="dgame-game" aria-label="坦克大战">
|
|
95
|
+
<div className="dgame-game-head"><div><h3>坦克大战</h3><p>方向键移动,空格开炮;击毁敌方坦克,别让敌军摧毁中央基地。</p></div><div className="dgame-score"><span>得分 <strong>{snap.score}</strong></span><span>生命 × {player && player.lives}</span></div></div>
|
|
96
|
+
<div className="dgame-tank-stage" ref={stageRef} tabIndex={0} role="grid" onKeyDown={onKeyDown} onKeyUp={onKeyUp} onClick={() => focusStage(stageRef)} aria-label="坦克大战游戏区域,方向键移动,空格开炮">
|
|
97
|
+
<div className="dgame-tank-grid">{cells}</div>
|
|
98
|
+
{status === "over" ? <div className="dgame-over"><strong>胜败已分 · 得 {snap.score} 分</strong><button type="button" onClick={start}>重新开始</button></div> : null}
|
|
99
|
+
{status === "wave" ? <div className="dgame-over"><strong>第 {snap.wave} 波清空!</strong><button type="button" onClick={nextLvl}>下一波</button></div> : null}
|
|
100
|
+
</div>
|
|
101
|
+
<div className="dgame-controls dgame-tank-controls">
|
|
102
|
+
<button type="button" aria-label="向上" onClick={() => { keys.current.dir = "up"; setTimeout(() => { if (keys.current.dir === "up") keys.current.dir = null; }, 90); }}>↑</button>
|
|
103
|
+
<span>方向键 / WASD · 空格开炮 · R 重开</span>
|
|
104
|
+
<button type="button" className="dgame-tank-fire" aria-label="开炮" onClick={() => { keys.current.shoot = true; setTimeout(() => { keys.current.shoot = false; }, 90); }}>开炮</button>
|
|
105
|
+
</div>
|
|
106
|
+
</section>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function TankPreview() {
|
|
110
|
+
// 迷你战场:砖墙/钢墙色块 + 我方坦克 + 敌方坦克 + 基地,与真实游戏元素一致。
|
|
111
|
+
const ROWS = [
|
|
112
|
+
"..B..B..",
|
|
113
|
+
"S.....E.",
|
|
114
|
+
"..B.B...",
|
|
115
|
+
"B..S...B",
|
|
116
|
+
"...P..F.",
|
|
117
|
+
];
|
|
118
|
+
const cls = { ".": "void", B: "brick", S: "steel", P: "pw", E: "en", F: "base" };
|
|
119
|
+
return <span className="dgcov-prev dgcov-prev-tank" aria-hidden="true">
|
|
120
|
+
<span className="dgp-tank-grid">
|
|
121
|
+
{ROWS.flatMap((row, r) => row.split("").map((ch, c) => <i key={r + "-" + c} className={"dgp-tank-" + cls[ch]} />))}
|
|
122
|
+
</span>
|
|
123
|
+
</span>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const tankGame = { Game: TankGame, Preview: TankPreview, css: `
|
|
127
|
+
.dgame-tank-stage{position:relative;align-self:center;width:min(100%,320px);aspect-ratio:1/1;border:1px solid color-mix(in srgb,rgb(250 204 21) 32%,var(--dsw-alias-border-l1));border-radius:8px;overflow:hidden;outline:none;background:linear-gradient(180deg,#1a1425,#100d1c);box-shadow:inset 0 0 26px rgb(0 0 0/.5)}.dgame-tank-stage:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,rgb(250 204 21) 30%,transparent)}.dgame-tank-grid{display:grid;grid-template-columns:repeat(15,1fr);grid-template-rows:repeat(15,1fr);width:100%;height:100%;gap:0}.dgame-tank-cell{position:relative;background:rgba(255,255,255,.03);min-width:0;min-height:0}.dgame-tank-cell.steel{background:linear-gradient(180deg,#94a3b8,#64748b)}.dgame-tank-cell.brick{background:repeating-linear-gradient(45deg,#a3672a 0 55%,#8a5a1c 55% 100%);box-shadow:inset 0 0 0 1px rgba(0,0,0,.25)}.dgame-tank-cell.base{background:#fbbf24}.dgame-tank-cell.bullet::before{content:'';position:absolute;inset:38%;background:#fef3c7;box-shadow:0 0 5px #fef3c7;z-index:4}.dgame-tank-flag{position:absolute;inset:0;display:grid;place-items:center;color:#7c2d12;font-size:18px;z-index:2}.dgame-tank-body{position:absolute;inset:16%;display:block;border-radius:16%;z-index:3;transition:transform .1s ease}.dgame-tank-body.pw{background:linear-gradient(135deg,#fde047,#eab308);box-shadow:0 0 7px color-mix(in srgb,#eab308 55%,transparent)}.dgame-tank-body.en{background:#f87171;box-shadow:0 0 5px color-mix(in srgb,#f87171 40%,transparent)}.dgame-tank-body::before{content:'';position:absolute;left:38%;top:-30%;width:24%;height:64%;background:rgba(255,255,255,.5);border-radius:3px}.dgame-tank-controls{grid-template-columns:1fr auto 1fr}
|
|
128
|
+
` };
|