castle-web-cli 0.4.74 → 0.4.75
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/commonInstructions.d.ts +1 -1
- package/dist/commonInstructions.js +1 -1
- package/kits/basic-2d/CLAUDE.md +1 -1
- package/kits/basic-2d/editors/SceneEditor.jsx +0 -3
- package/kits/basic-2d/engine/ScenePlayer.jsx +0 -3
- package/kits/basic-2d/pnpm-workspace.yaml +3 -0
- package/kits/basic-3d/CLAUDE.md +1 -1
- package/kits/basic-3d/editors/SceneEditor.jsx +0 -6
- package/kits/basic-3d/engine/ScenePlayer.jsx +0 -3
- package/package.json +1 -1
- package/kits/basic-2d/engine/TouchControls.jsx +0 -136
- package/kits/basic-3d/engine/TouchControls.jsx +0 -136
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const COMMON_INSTRUCTIONS = "## Touch controls (every deck)\n\n- **
|
|
1
|
+
export declare const COMMON_INSTRUCTIONS = "## Touch controls (every deck)\n\n- **Playable on a touchscreen, with only the controls the game actually needs.** Castle decks are played on phones, so whatever input a game does use must work by touch \u2014 direct tap/drag on the game itself wherever possible, and on-screen buttons only where the mechanics genuinely call for them. Do NOT add controls a game doesn't need: never drop in a generic d-pad or movement overlay by default. Prefer touching the game directly over an overlay that just mirrors keyboard keys. Keyboard input is fine to support on top for desktop play. Match the controls to the actual mechanics \u2014 a game with no directional movement should have no movement controls at all.\n";
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
// (e.g. Space being reserved for play/stop) live in each kit's own CLAUDE.md.
|
|
5
5
|
export const COMMON_INSTRUCTIONS = `## Touch controls (every deck)
|
|
6
6
|
|
|
7
|
-
- **
|
|
7
|
+
- **Playable on a touchscreen, with only the controls the game actually needs.** Castle decks are played on phones, so whatever input a game does use must work by touch — direct tap/drag on the game itself wherever possible, and on-screen buttons only where the mechanics genuinely call for them. Do NOT add controls a game doesn't need: never drop in a generic d-pad or movement overlay by default. Prefer touching the game directly over an overlay that just mirrors keyboard keys. Keyboard input is fine to support on top for desktop play. Match the controls to the actual mechanics — a game with no directional movement should have no movement controls at all.
|
|
8
8
|
`;
|
package/kits/basic-2d/CLAUDE.md
CHANGED
|
@@ -144,7 +144,7 @@ if (scene.keys.has('KeyX')) /* launch ball */ ;
|
|
|
144
144
|
|
|
145
145
|
**Space is reserved** — the editor binds it to the play/stop toggle, so don't bind Space to a gameplay action (jump / shoot / launch / ...). Use arrows, WASD, letter keys, or on-screen buttons instead.
|
|
146
146
|
|
|
147
|
-
For HUD text use a behavior's `ui` hook (returns React); for in-world text or shapes, draw with `ctx` from `draw`.
|
|
147
|
+
For HUD text use a behavior's `ui` hook (returns React); for in-world text or shapes, draw with `ctx` from `draw`.
|
|
148
148
|
|
|
149
149
|
## Common breakout-shaped recipe (sketch)
|
|
150
150
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
2
|
import { renderSpriteFrame } from '../engine/pxart';
|
|
3
3
|
import { basename, formatJson, parseJsonFile } from '../engine/files';
|
|
4
|
-
import { TouchControls } from '../engine/TouchControls';
|
|
5
4
|
import {
|
|
6
5
|
addActor,
|
|
7
6
|
arrangeActors,
|
|
@@ -90,7 +89,6 @@ export function SceneEditor({
|
|
|
90
89
|
const showMulti = selectedActorIds.length > 1 || multiSelectMode;
|
|
91
90
|
const inspectorSheet = useSelectionInspectorSheet(true);
|
|
92
91
|
const { value: sceneData, error } = parseJsonFile(path, text);
|
|
93
|
-
const getRuntimeKeys = useCallback(() => runtimeRef.current?.keys ?? null, []);
|
|
94
92
|
const getRuntime = useCallback(() => runtimeRef.current, []);
|
|
95
93
|
useScenePlayLoop({
|
|
96
94
|
sceneData,
|
|
@@ -288,7 +286,6 @@ export function SceneEditor({
|
|
|
288
286
|
</aside>
|
|
289
287
|
</div>
|
|
290
288
|
</EditorBody>
|
|
291
|
-
<TouchControls getKeys={getRuntimeKeys} visible={isPlaying} />
|
|
292
289
|
</>
|
|
293
290
|
);
|
|
294
291
|
}
|
|
@@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { configureSceneCanvas, makeScene } from './scene';
|
|
4
4
|
import { SceneUI } from './SceneUI';
|
|
5
|
-
import { TouchControls } from './TouchControls';
|
|
6
5
|
import { cx, Icon, styles } from './ui';
|
|
7
6
|
import { usePlayLogs } from './playConsole';
|
|
8
7
|
// Engine-level scene player: mount a `SceneRuntime` against a canvas, wire
|
|
@@ -15,7 +14,6 @@ export function ScenePlayer({ sceneData, sprites, behaviorClasses, onFirstFrame
|
|
|
15
14
|
// Bumping this re-runs the mount effect below, which tears down the running
|
|
16
15
|
// loop/runtime and rebuilds a fresh scene from the same data -- a clean restart.
|
|
17
16
|
const [runToken, setRunToken] = useState(0);
|
|
18
|
-
const getKeys = useCallback(() => runtimeRef.current?.keys ?? null, []);
|
|
19
17
|
const getRuntime = useCallback(() => runtimeRef.current, []);
|
|
20
18
|
const restart = useCallback(() => setRunToken((token) => token + 1), []);
|
|
21
19
|
useEffect(() => {
|
|
@@ -87,7 +85,6 @@ export function ScenePlayer({ sceneData, sprites, behaviorClasses, onFirstFrame
|
|
|
87
85
|
style={{ width: '100%', height: '100%', display: 'block', outline: 'none' }}
|
|
88
86
|
/>
|
|
89
87
|
<SceneUI getRuntime={getRuntime} />
|
|
90
|
-
<TouchControls getKeys={getKeys} />
|
|
91
88
|
</div>
|
|
92
89
|
{/* Portal to <body> so the console drawer escapes the SDK's card-sized,
|
|
93
90
|
transformed `#root > *` wrapper and pins to the panel, outside the
|
package/kits/basic-3d/CLAUDE.md
CHANGED
|
@@ -142,7 +142,7 @@ if (scene.keys.has('KeyJ')) /* jump */ ;
|
|
|
142
142
|
|
|
143
143
|
**Space is reserved** — the editor binds it to the play/stop toggle, so don't bind Space to a gameplay action (jump / shoot / launch / ...). Use arrows, WASD, letter keys, or on-screen buttons instead.
|
|
144
144
|
|
|
145
|
-
For HUD text use a behavior's `ui` hook (returns React, 500x700 design space); for in-world visuals, use Mesh/Model components or a custom `sync`.
|
|
145
|
+
For HUD text use a behavior's `ui` hook (returns React, 500x700 design space); for in-world visuals, use Mesh/Model components or a custom `sync`.
|
|
146
146
|
|
|
147
147
|
## Common exploration-shaped recipe (sketch)
|
|
148
148
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import { basename, formatJson, parseJsonFile } from '../engine/files';
|
|
4
|
-
import { TouchControls } from '../engine/TouchControls';
|
|
5
4
|
import {
|
|
6
5
|
addActor,
|
|
7
6
|
defaultCameraSpec,
|
|
@@ -83,10 +82,6 @@ export function SceneEditor({
|
|
|
83
82
|
applyScene,
|
|
84
83
|
recordSnapshot: history.recordSnapshot,
|
|
85
84
|
});
|
|
86
|
-
const getRuntimeKeys = useCallback(
|
|
87
|
-
() => viewport.playRuntimeRef.current?.keys ?? null,
|
|
88
|
-
[viewport.playRuntimeRef]
|
|
89
|
-
);
|
|
90
85
|
const getPlayRuntime = useCallback(
|
|
91
86
|
() => viewport.playRuntimeRef.current,
|
|
92
87
|
[viewport.playRuntimeRef]
|
|
@@ -240,7 +235,6 @@ export function SceneEditor({
|
|
|
240
235
|
</aside>
|
|
241
236
|
</div>
|
|
242
237
|
</EditorBody>
|
|
243
|
-
<TouchControls getKeys={getRuntimeKeys} visible={isPlaying} />
|
|
244
238
|
</>
|
|
245
239
|
);
|
|
246
240
|
}
|
|
@@ -4,7 +4,6 @@ import { makeScene, defaultCameraSpec } from './scene';
|
|
|
4
4
|
import { applyCameraSpec, fitRendererToCanvas } from './threeUtil';
|
|
5
5
|
import { attachSceneKeys, makePlayPointerHandlers, ThreeCanvas } from './SceneViewport';
|
|
6
6
|
import { SceneUI } from './SceneUI';
|
|
7
|
-
import { TouchControls } from './TouchControls';
|
|
8
7
|
// Engine-level scene player: mount a `SceneRuntime` against a WebGL canvas,
|
|
9
8
|
// wire keyboard / pointer input, run the update+sync+render loop, and render
|
|
10
9
|
// the behavior-driven UI overlay. No game logic lives here -- behaviors and
|
|
@@ -13,7 +12,6 @@ export function ScenePlayer({ sceneData, models, behaviorClasses, onFirstFrame }
|
|
|
13
12
|
const runtimeRef = useRef(null);
|
|
14
13
|
const cameraRef = useRef(null);
|
|
15
14
|
const firstFrameRef = useRef(false);
|
|
16
|
-
const getKeys = useCallback(() => runtimeRef.current?.keys ?? null, []);
|
|
17
15
|
const getRuntime = useCallback(() => runtimeRef.current, []);
|
|
18
16
|
const pointerHandlers = useMemo(() => makePlayPointerHandlers(getRuntime), [getRuntime]);
|
|
19
17
|
useEffect(() => {
|
|
@@ -55,7 +53,6 @@ export function ScenePlayer({ sceneData, models, behaviorClasses, onFirstFrame }
|
|
|
55
53
|
{...pointerHandlers}
|
|
56
54
|
/>
|
|
57
55
|
<SceneUI getRuntime={getRuntime} />
|
|
58
|
-
<TouchControls getKeys={getKeys} />
|
|
59
56
|
</div>
|
|
60
57
|
);
|
|
61
58
|
}
|
package/package.json
CHANGED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
// On-screen movement controls: a 4-way d-pad feeds arrow keys onto
|
|
3
|
-
// `scene.keys` -- the exact same keyboard keys behaviors consume, no separate
|
|
4
|
-
// input path.
|
|
5
|
-
const KEYS = {
|
|
6
|
-
up: 'ArrowUp',
|
|
7
|
-
down: 'ArrowDown',
|
|
8
|
-
left: 'ArrowLeft',
|
|
9
|
-
right: 'ArrowRight',
|
|
10
|
-
};
|
|
11
|
-
export function TouchControls({ getKeys, visible = true }) {
|
|
12
|
-
const [isTouch, setIsTouch] = useState(false);
|
|
13
|
-
const heldRef = useRef(new Set());
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
const params = new URLSearchParams(window.location.search);
|
|
16
|
-
const override = params.get('touch');
|
|
17
|
-
if (override === '1' || override === 'true') {
|
|
18
|
-
setIsTouch(true);
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
if (override === '0' || override === 'false') {
|
|
22
|
-
setIsTouch(false);
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
const mq = window.matchMedia('(pointer: coarse)');
|
|
26
|
-
const update = () => {
|
|
27
|
-
setIsTouch(mq.matches || 'ontouchstart' in window || navigator.maxTouchPoints > 0);
|
|
28
|
-
};
|
|
29
|
-
update();
|
|
30
|
-
mq.addEventListener?.('change', update);
|
|
31
|
-
return () => mq.removeEventListener?.('change', update);
|
|
32
|
-
}, []);
|
|
33
|
-
const release = useCallback(
|
|
34
|
-
(id) => {
|
|
35
|
-
if (!heldRef.current.has(id)) return;
|
|
36
|
-
heldRef.current.delete(id);
|
|
37
|
-
getKeys()?.delete(KEYS[id]);
|
|
38
|
-
},
|
|
39
|
-
[getKeys]
|
|
40
|
-
);
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
if (!visible || !isTouch) {
|
|
43
|
-
// Drop any keys we were holding when controls hide.
|
|
44
|
-
const keys = getKeys();
|
|
45
|
-
if (keys) for (const id of heldRef.current) keys.delete(KEYS[id]);
|
|
46
|
-
heldRef.current.clear();
|
|
47
|
-
}
|
|
48
|
-
}, [visible, isTouch, getKeys]);
|
|
49
|
-
if (!visible || !isTouch) return null;
|
|
50
|
-
const press = (id) => {
|
|
51
|
-
heldRef.current.add(id);
|
|
52
|
-
getKeys()?.add(KEYS[id]);
|
|
53
|
-
};
|
|
54
|
-
const button = (id, label, extra) => (
|
|
55
|
-
<button
|
|
56
|
-
key={id}
|
|
57
|
-
type="button"
|
|
58
|
-
tabIndex={-1}
|
|
59
|
-
aria-label={id}
|
|
60
|
-
onTouchStart={(event) => {
|
|
61
|
-
event.preventDefault();
|
|
62
|
-
press(id);
|
|
63
|
-
}}
|
|
64
|
-
onTouchEnd={(event) => {
|
|
65
|
-
event.preventDefault();
|
|
66
|
-
release(id);
|
|
67
|
-
}}
|
|
68
|
-
onTouchCancel={(event) => {
|
|
69
|
-
event.preventDefault();
|
|
70
|
-
release(id);
|
|
71
|
-
}}
|
|
72
|
-
onMouseDown={(event) => {
|
|
73
|
-
event.preventDefault();
|
|
74
|
-
press(id);
|
|
75
|
-
}}
|
|
76
|
-
onMouseUp={() => release(id)}
|
|
77
|
-
onMouseLeave={() => release(id)}
|
|
78
|
-
onContextMenu={(event) => event.preventDefault()}
|
|
79
|
-
style={{ ...buttonStyle, ...extra }}>
|
|
80
|
-
{label}
|
|
81
|
-
</button>
|
|
82
|
-
);
|
|
83
|
-
return (
|
|
84
|
-
<div style={containerStyle}>
|
|
85
|
-
<div style={dpadStyle}>
|
|
86
|
-
{button('up', '▲', dpadUpStyle)}
|
|
87
|
-
{button('left', '◀', dpadLeftStyle)}
|
|
88
|
-
{button('right', '▶', dpadRightStyle)}
|
|
89
|
-
{button('down', '▼', dpadDownStyle)}
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
const containerStyle = {
|
|
95
|
-
position: 'fixed',
|
|
96
|
-
left: 0,
|
|
97
|
-
right: 0,
|
|
98
|
-
bottom: 0,
|
|
99
|
-
display: 'flex',
|
|
100
|
-
justifyContent: 'flex-start',
|
|
101
|
-
alignItems: 'flex-end',
|
|
102
|
-
padding: '12px 16px calc(env(safe-area-inset-bottom, 0px) + 12px)',
|
|
103
|
-
pointerEvents: 'none',
|
|
104
|
-
zIndex: 50,
|
|
105
|
-
};
|
|
106
|
-
// 3x3 grid -- the d-pad arms occupy the cross cells, corners stay empty.
|
|
107
|
-
const dpadStyle = {
|
|
108
|
-
display: 'grid',
|
|
109
|
-
gridTemplateColumns: 'repeat(3, 64px)',
|
|
110
|
-
gridTemplateRows: 'repeat(3, 64px)',
|
|
111
|
-
gap: 6,
|
|
112
|
-
pointerEvents: 'auto',
|
|
113
|
-
};
|
|
114
|
-
const dpadUpStyle = { gridColumn: 2, gridRow: 1 };
|
|
115
|
-
const dpadLeftStyle = { gridColumn: 1, gridRow: 2 };
|
|
116
|
-
const dpadRightStyle = { gridColumn: 3, gridRow: 2 };
|
|
117
|
-
const dpadDownStyle = { gridColumn: 2, gridRow: 3 };
|
|
118
|
-
const buttonStyle = {
|
|
119
|
-
width: 64,
|
|
120
|
-
height: 64,
|
|
121
|
-
borderRadius: 12,
|
|
122
|
-
border: '1px solid rgba(255, 255, 255, 0.35)',
|
|
123
|
-
background: 'rgba(0, 0, 0, 0.35)',
|
|
124
|
-
color: 'rgba(255, 255, 255, 0.92)',
|
|
125
|
-
fontSize: 24,
|
|
126
|
-
fontWeight: 600,
|
|
127
|
-
display: 'flex',
|
|
128
|
-
alignItems: 'center',
|
|
129
|
-
justifyContent: 'center',
|
|
130
|
-
userSelect: 'none',
|
|
131
|
-
WebkitUserSelect: 'none',
|
|
132
|
-
WebkitTouchCallout: 'none',
|
|
133
|
-
WebkitTapHighlightColor: 'transparent',
|
|
134
|
-
touchAction: 'none',
|
|
135
|
-
cursor: 'pointer',
|
|
136
|
-
};
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
// On-screen movement controls: a 4-way d-pad feeds arrow keys onto
|
|
3
|
-
// `scene.keys` -- the exact same keyboard keys behaviors consume, no separate
|
|
4
|
-
// input path.
|
|
5
|
-
const KEYS = {
|
|
6
|
-
up: 'ArrowUp',
|
|
7
|
-
down: 'ArrowDown',
|
|
8
|
-
left: 'ArrowLeft',
|
|
9
|
-
right: 'ArrowRight',
|
|
10
|
-
};
|
|
11
|
-
export function TouchControls({ getKeys, visible = true }) {
|
|
12
|
-
const [isTouch, setIsTouch] = useState(false);
|
|
13
|
-
const heldRef = useRef(new Set());
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
const params = new URLSearchParams(window.location.search);
|
|
16
|
-
const override = params.get('touch');
|
|
17
|
-
if (override === '1' || override === 'true') {
|
|
18
|
-
setIsTouch(true);
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
if (override === '0' || override === 'false') {
|
|
22
|
-
setIsTouch(false);
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
const mq = window.matchMedia('(pointer: coarse)');
|
|
26
|
-
const update = () => {
|
|
27
|
-
setIsTouch(mq.matches || 'ontouchstart' in window || navigator.maxTouchPoints > 0);
|
|
28
|
-
};
|
|
29
|
-
update();
|
|
30
|
-
mq.addEventListener?.('change', update);
|
|
31
|
-
return () => mq.removeEventListener?.('change', update);
|
|
32
|
-
}, []);
|
|
33
|
-
const release = useCallback(
|
|
34
|
-
(id) => {
|
|
35
|
-
if (!heldRef.current.has(id)) return;
|
|
36
|
-
heldRef.current.delete(id);
|
|
37
|
-
getKeys()?.delete(KEYS[id]);
|
|
38
|
-
},
|
|
39
|
-
[getKeys]
|
|
40
|
-
);
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
if (!visible || !isTouch) {
|
|
43
|
-
// Drop any keys we were holding when controls hide.
|
|
44
|
-
const keys = getKeys();
|
|
45
|
-
if (keys) for (const id of heldRef.current) keys.delete(KEYS[id]);
|
|
46
|
-
heldRef.current.clear();
|
|
47
|
-
}
|
|
48
|
-
}, [visible, isTouch, getKeys]);
|
|
49
|
-
if (!visible || !isTouch) return null;
|
|
50
|
-
const press = (id) => {
|
|
51
|
-
heldRef.current.add(id);
|
|
52
|
-
getKeys()?.add(KEYS[id]);
|
|
53
|
-
};
|
|
54
|
-
const button = (id, label, extra) => (
|
|
55
|
-
<button
|
|
56
|
-
key={id}
|
|
57
|
-
type="button"
|
|
58
|
-
tabIndex={-1}
|
|
59
|
-
aria-label={id}
|
|
60
|
-
onTouchStart={(event) => {
|
|
61
|
-
event.preventDefault();
|
|
62
|
-
press(id);
|
|
63
|
-
}}
|
|
64
|
-
onTouchEnd={(event) => {
|
|
65
|
-
event.preventDefault();
|
|
66
|
-
release(id);
|
|
67
|
-
}}
|
|
68
|
-
onTouchCancel={(event) => {
|
|
69
|
-
event.preventDefault();
|
|
70
|
-
release(id);
|
|
71
|
-
}}
|
|
72
|
-
onMouseDown={(event) => {
|
|
73
|
-
event.preventDefault();
|
|
74
|
-
press(id);
|
|
75
|
-
}}
|
|
76
|
-
onMouseUp={() => release(id)}
|
|
77
|
-
onMouseLeave={() => release(id)}
|
|
78
|
-
onContextMenu={(event) => event.preventDefault()}
|
|
79
|
-
style={{ ...buttonStyle, ...extra }}>
|
|
80
|
-
{label}
|
|
81
|
-
</button>
|
|
82
|
-
);
|
|
83
|
-
return (
|
|
84
|
-
<div style={containerStyle}>
|
|
85
|
-
<div style={dpadStyle}>
|
|
86
|
-
{button('up', '▲', dpadUpStyle)}
|
|
87
|
-
{button('left', '◀', dpadLeftStyle)}
|
|
88
|
-
{button('right', '▶', dpadRightStyle)}
|
|
89
|
-
{button('down', '▼', dpadDownStyle)}
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
const containerStyle = {
|
|
95
|
-
position: 'fixed',
|
|
96
|
-
left: 0,
|
|
97
|
-
right: 0,
|
|
98
|
-
bottom: 0,
|
|
99
|
-
display: 'flex',
|
|
100
|
-
justifyContent: 'flex-start',
|
|
101
|
-
alignItems: 'flex-end',
|
|
102
|
-
padding: '12px 16px calc(env(safe-area-inset-bottom, 0px) + 12px)',
|
|
103
|
-
pointerEvents: 'none',
|
|
104
|
-
zIndex: 50,
|
|
105
|
-
};
|
|
106
|
-
// 3x3 grid -- the d-pad arms occupy the cross cells, corners stay empty.
|
|
107
|
-
const dpadStyle = {
|
|
108
|
-
display: 'grid',
|
|
109
|
-
gridTemplateColumns: 'repeat(3, 64px)',
|
|
110
|
-
gridTemplateRows: 'repeat(3, 64px)',
|
|
111
|
-
gap: 6,
|
|
112
|
-
pointerEvents: 'auto',
|
|
113
|
-
};
|
|
114
|
-
const dpadUpStyle = { gridColumn: 2, gridRow: 1 };
|
|
115
|
-
const dpadLeftStyle = { gridColumn: 1, gridRow: 2 };
|
|
116
|
-
const dpadRightStyle = { gridColumn: 3, gridRow: 2 };
|
|
117
|
-
const dpadDownStyle = { gridColumn: 2, gridRow: 3 };
|
|
118
|
-
const buttonStyle = {
|
|
119
|
-
width: 64,
|
|
120
|
-
height: 64,
|
|
121
|
-
borderRadius: 12,
|
|
122
|
-
border: '1px solid rgba(255, 255, 255, 0.35)',
|
|
123
|
-
background: 'rgba(0, 0, 0, 0.35)',
|
|
124
|
-
color: 'rgba(255, 255, 255, 0.92)',
|
|
125
|
-
fontSize: 24,
|
|
126
|
-
fontWeight: 600,
|
|
127
|
-
display: 'flex',
|
|
128
|
-
alignItems: 'center',
|
|
129
|
-
justifyContent: 'center',
|
|
130
|
-
userSelect: 'none',
|
|
131
|
-
WebkitUserSelect: 'none',
|
|
132
|
-
WebkitTouchCallout: 'none',
|
|
133
|
-
WebkitTapHighlightColor: 'transparent',
|
|
134
|
-
touchAction: 'none',
|
|
135
|
-
cursor: 'pointer',
|
|
136
|
-
};
|