@littlepartytime/dev-kit 1.10.0 → 1.12.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.
package/dist/webapp/App.tsx
CHANGED
|
@@ -14,15 +14,25 @@ export default function App() {
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<div
|
|
17
|
+
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
18
18
|
{/* Nav */}
|
|
19
|
-
<nav
|
|
20
|
-
<span
|
|
19
|
+
<nav style={{ background: '#18181b', borderBottom: '1px solid #27272a', padding: '8px 16px', display: 'flex', gap: 16, alignItems: 'center' }}>
|
|
20
|
+
<span style={{ color: '#f59e0b', fontWeight: 700, marginRight: 16 }}>LPT Dev Kit</span>
|
|
21
21
|
{(['preview', 'play', 'debug'] as Page[]).map((p) => (
|
|
22
22
|
<button
|
|
23
23
|
key={p}
|
|
24
24
|
onClick={() => { setPage(p); history.pushState(null, '', `/${p}`); }}
|
|
25
|
-
className=
|
|
25
|
+
className="dk-nav-btn"
|
|
26
|
+
style={{
|
|
27
|
+
padding: '4px 12px',
|
|
28
|
+
borderRadius: 4,
|
|
29
|
+
border: 'none',
|
|
30
|
+
cursor: 'pointer',
|
|
31
|
+
fontSize: 14,
|
|
32
|
+
...(page === p
|
|
33
|
+
? { background: '#d97706', color: '#fff' }
|
|
34
|
+
: { background: 'transparent', color: '#a1a1aa' }),
|
|
35
|
+
}}
|
|
26
36
|
>
|
|
27
37
|
{p.charAt(0).toUpperCase() + p.slice(1)}
|
|
28
38
|
</button>
|
|
@@ -30,7 +40,7 @@ export default function App() {
|
|
|
30
40
|
</nav>
|
|
31
41
|
|
|
32
42
|
{/* Content */}
|
|
33
|
-
<main
|
|
43
|
+
<main style={{ flex: 1, padding: 16 }}>
|
|
34
44
|
{page === 'preview' && <Preview />}
|
|
35
45
|
{page === 'play' && <Play />}
|
|
36
46
|
{page === 'debug' && <Debug />}
|
|
@@ -40,15 +40,14 @@ export default function PhoneFrame({ children }: { children: React.ReactNode })
|
|
|
40
40
|
return (
|
|
41
41
|
<div
|
|
42
42
|
ref={containerRef}
|
|
43
|
-
|
|
44
|
-
style={{ flex: 1, minWidth: 0, minHeight: 0 }}
|
|
43
|
+
style={{ flex: 1, minWidth: 0, minHeight: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', overflow: 'hidden' }}
|
|
45
44
|
>
|
|
46
45
|
{/* Wrapper sized to the scaled phone for correct layout flow */}
|
|
47
46
|
<div style={{ width: BODY_W * scale, height: BODY_H * scale, flexShrink: 0 }}>
|
|
48
47
|
{/* Phone body at original pixel size, visually scaled */}
|
|
49
48
|
<div
|
|
50
|
-
className="relative"
|
|
51
49
|
style={{
|
|
50
|
+
position: 'relative',
|
|
52
51
|
width: BODY_W,
|
|
53
52
|
height: BODY_H,
|
|
54
53
|
transform: `scale(${scale})`,
|
|
@@ -57,8 +56,9 @@ export default function PhoneFrame({ children }: { children: React.ReactNode })
|
|
|
57
56
|
>
|
|
58
57
|
{/* Frame / bezel */}
|
|
59
58
|
<div
|
|
60
|
-
className="absolute inset-0"
|
|
61
59
|
style={{
|
|
60
|
+
position: 'absolute',
|
|
61
|
+
inset: 0,
|
|
62
62
|
borderRadius: OUTER_R,
|
|
63
63
|
background: '#1c1c1e',
|
|
64
64
|
boxShadow:
|
|
@@ -68,8 +68,10 @@ export default function PhoneFrame({ children }: { children: React.ReactNode })
|
|
|
68
68
|
|
|
69
69
|
{/* Screen */}
|
|
70
70
|
<div
|
|
71
|
-
className="absolute overflow-hidden bg-black"
|
|
72
71
|
style={{
|
|
72
|
+
position: 'absolute',
|
|
73
|
+
overflow: 'hidden',
|
|
74
|
+
background: '#000',
|
|
73
75
|
top: BEZEL,
|
|
74
76
|
left: BEZEL,
|
|
75
77
|
width: SCREEN_W,
|
|
@@ -82,8 +84,9 @@ export default function PhoneFrame({ children }: { children: React.ReactNode })
|
|
|
82
84
|
contain:paint makes this the containing block for
|
|
83
85
|
position:fixed elements inside the game. */}
|
|
84
86
|
<div
|
|
85
|
-
className="absolute overflow-hidden"
|
|
86
87
|
style={{
|
|
88
|
+
position: 'absolute',
|
|
89
|
+
overflow: 'hidden',
|
|
87
90
|
top: SAFE_AREA_TOP,
|
|
88
91
|
left: 0,
|
|
89
92
|
right: 0,
|
|
@@ -97,14 +100,26 @@ export default function PhoneFrame({ children }: { children: React.ReactNode })
|
|
|
97
100
|
|
|
98
101
|
{/* Dynamic Island */}
|
|
99
102
|
<div
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
style={{
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
left: '50%',
|
|
106
|
+
transform: 'translateX(-50%)',
|
|
107
|
+
background: '#000',
|
|
108
|
+
borderRadius: 9999,
|
|
109
|
+
top: BEZEL + 11,
|
|
110
|
+
width: 126,
|
|
111
|
+
height: 37,
|
|
112
|
+
zIndex: 10,
|
|
113
|
+
}}
|
|
102
114
|
/>
|
|
103
115
|
|
|
104
116
|
{/* Home Indicator */}
|
|
105
117
|
<div
|
|
106
|
-
className="absolute left-1/2 -translate-x-1/2 rounded-full"
|
|
107
118
|
style={{
|
|
119
|
+
position: 'absolute',
|
|
120
|
+
left: '50%',
|
|
121
|
+
transform: 'translateX(-50%)',
|
|
122
|
+
borderRadius: 9999,
|
|
108
123
|
bottom: BEZEL + 8,
|
|
109
124
|
width: 134,
|
|
110
125
|
height: 5,
|
package/dist/webapp/index.html
CHANGED
|
@@ -4,9 +4,22 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>LPT Dev Kit</title>
|
|
7
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
7
|
<style>
|
|
9
|
-
|
|
8
|
+
/* ── Dev-Kit UI base ── */
|
|
9
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
10
|
+
body { background: #0a0a0a; color: #e5e5e5; font-family: system-ui, -apple-system, sans-serif; }
|
|
11
|
+
|
|
12
|
+
/* ── Interactive states (can't be expressed as inline styles) ── */
|
|
13
|
+
.dk-nav-btn:hover { color: #fff; }
|
|
14
|
+
.dk-btn-amber:hover { background: #f59e0b; }
|
|
15
|
+
.dk-btn-zinc:hover { background: #52525b; }
|
|
16
|
+
.dk-btn-green:hover { background: #16a34a; }
|
|
17
|
+
.dk-player-btn { transition: background 0.15s; }
|
|
18
|
+
.dk-player-btn:hover { background: #3f3f46; }
|
|
19
|
+
.dk-resize-bar .dk-resize-line { transition: background 0.15s; }
|
|
20
|
+
.dk-resize-bar:hover .dk-resize-line { background: #71717a; }
|
|
21
|
+
.dk-input:focus { outline: none; border-color: #71717a; }
|
|
22
|
+
.dk-btn-amber:disabled, .dk-btn-green:disabled { opacity: 0.5; }
|
|
10
23
|
</style>
|
|
11
24
|
</head>
|
|
12
25
|
<body>
|
|
@@ -18,20 +18,22 @@ export default function Debug() {
|
|
|
18
18
|
return () => { sock.disconnect(); };
|
|
19
19
|
}, []);
|
|
20
20
|
|
|
21
|
+
const panelStyle: React.CSSProperties = { background: '#18181b', borderRadius: 8, padding: 16, overflow: 'auto' };
|
|
22
|
+
|
|
21
23
|
return (
|
|
22
|
-
<div
|
|
24
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, height: 'calc(100vh - 80px)' }}>
|
|
23
25
|
{/* Room State */}
|
|
24
|
-
<div
|
|
25
|
-
<h2
|
|
26
|
-
<pre
|
|
26
|
+
<div style={panelStyle}>
|
|
27
|
+
<h2 style={{ fontSize: 18, fontWeight: 700, marginBottom: 16, color: '#fbbf24' }}>Room State</h2>
|
|
28
|
+
<pre style={{ fontSize: 11, fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
|
27
29
|
{JSON.stringify(room, null, 2)}
|
|
28
30
|
</pre>
|
|
29
31
|
</div>
|
|
30
32
|
|
|
31
33
|
{/* Game State */}
|
|
32
|
-
<div
|
|
33
|
-
<h2
|
|
34
|
-
<pre
|
|
34
|
+
<div style={panelStyle}>
|
|
35
|
+
<h2 style={{ fontSize: 18, fontWeight: 700, marginBottom: 16, color: '#fbbf24' }}>Full Game State</h2>
|
|
36
|
+
<pre style={{ fontSize: 11, fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
|
35
37
|
{fullState ? JSON.stringify(fullState, null, 2) : 'No game in progress'}
|
|
36
38
|
</pre>
|
|
37
39
|
</div>
|
|
@@ -2,6 +2,10 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|
|
2
2
|
import { io, Socket } from 'socket.io-client';
|
|
3
3
|
import PhoneFrame from '../components/PhoneFrame';
|
|
4
4
|
|
|
5
|
+
const card: React.CSSProperties = { background: '#18181b', borderRadius: 8, padding: 24 };
|
|
6
|
+
const inputStyle: React.CSSProperties = { width: '100%', background: '#27272a', border: '1px solid #3f3f46', borderRadius: 4, padding: '8px 12px', marginBottom: 16, color: '#e5e5e5', fontSize: 14 };
|
|
7
|
+
const btnAmber: React.CSSProperties = { width: '100%', background: '#d97706', color: '#fff', border: 'none', padding: '8px 0', borderRadius: 4, fontWeight: 600, cursor: 'pointer', fontSize: 14 };
|
|
8
|
+
|
|
5
9
|
export default function Play() {
|
|
6
10
|
const [socket, setSocket] = useState<Socket | null>(null);
|
|
7
11
|
const [nickname, setNickname] = useState('');
|
|
@@ -53,24 +57,30 @@ export default function Play() {
|
|
|
53
57
|
},
|
|
54
58
|
reportResult: () => {},
|
|
55
59
|
getAssetUrl: (assetPath: string) => `/assets/${assetPath}`,
|
|
60
|
+
getDeviceCapabilities: () => ({ haptics: false, motion: false }),
|
|
61
|
+
haptic: () => {},
|
|
62
|
+
onShake: () => () => {},
|
|
63
|
+
onTilt: () => () => {},
|
|
56
64
|
} : null;
|
|
57
65
|
|
|
58
66
|
if (!joined) {
|
|
59
67
|
return (
|
|
60
|
-
<div
|
|
61
|
-
<div
|
|
62
|
-
<h2
|
|
68
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '60vh' }}>
|
|
69
|
+
<div style={{ ...card, width: 320 }}>
|
|
70
|
+
<h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 16 }}>Join Game</h2>
|
|
63
71
|
<input
|
|
64
72
|
type="text"
|
|
65
73
|
placeholder="Your nickname"
|
|
66
74
|
value={nickname}
|
|
67
75
|
onChange={(e) => setNickname(e.target.value)}
|
|
68
76
|
onKeyDown={(e) => e.key === 'Enter' && join()}
|
|
69
|
-
className="
|
|
77
|
+
className="dk-input"
|
|
78
|
+
style={inputStyle}
|
|
70
79
|
/>
|
|
71
80
|
<button
|
|
72
81
|
onClick={join}
|
|
73
|
-
className="
|
|
82
|
+
className="dk-btn-amber"
|
|
83
|
+
style={btnAmber}
|
|
74
84
|
>
|
|
75
85
|
Join
|
|
76
86
|
</button>
|
|
@@ -81,23 +91,35 @@ export default function Play() {
|
|
|
81
91
|
|
|
82
92
|
if (room.phase === 'lobby' || room.phase === 'ready') {
|
|
83
93
|
return (
|
|
84
|
-
<div
|
|
85
|
-
<div
|
|
86
|
-
<h2
|
|
87
|
-
<div
|
|
94
|
+
<div style={{ maxWidth: 448, margin: '32px auto 0' }}>
|
|
95
|
+
<div style={card}>
|
|
96
|
+
<h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 16 }}>Lobby</h2>
|
|
97
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 24 }}>
|
|
88
98
|
{room.players.map((p: any) => (
|
|
89
|
-
<div key={p.id}
|
|
99
|
+
<div key={p.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: '#27272a', borderRadius: 4, padding: '8px 12px' }}>
|
|
90
100
|
<span>{p.nickname} {p.isHost && '(Host)'}</span>
|
|
91
|
-
<span
|
|
101
|
+
<span style={{ color: p.ready ? '#4ade80' : '#71717a' }}>
|
|
92
102
|
{p.ready ? 'Ready' : 'Not Ready'}
|
|
93
103
|
</span>
|
|
94
104
|
</div>
|
|
95
105
|
))}
|
|
96
106
|
</div>
|
|
97
|
-
<div
|
|
107
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
98
108
|
<button
|
|
99
109
|
onClick={() => socket?.emit('player:ready', !isReady)}
|
|
100
|
-
className={
|
|
110
|
+
className={isReady ? 'dk-btn-zinc' : 'dk-btn-green'}
|
|
111
|
+
style={{
|
|
112
|
+
flex: 1,
|
|
113
|
+
padding: '8px 0',
|
|
114
|
+
borderRadius: 4,
|
|
115
|
+
fontWeight: 600,
|
|
116
|
+
border: 'none',
|
|
117
|
+
cursor: 'pointer',
|
|
118
|
+
fontSize: 14,
|
|
119
|
+
...(isReady
|
|
120
|
+
? { background: '#3f3f46', color: '#d4d4d8' }
|
|
121
|
+
: { background: '#16a34a', color: '#fff' }),
|
|
122
|
+
}}
|
|
101
123
|
>
|
|
102
124
|
{isReady ? 'Cancel Ready' : 'Ready'}
|
|
103
125
|
</button>
|
|
@@ -105,7 +127,8 @@ export default function Play() {
|
|
|
105
127
|
<button
|
|
106
128
|
onClick={() => socket?.emit('game:start')}
|
|
107
129
|
disabled={!room.players.every((p: any) => p.ready) || room.players.length < 2}
|
|
108
|
-
className="
|
|
130
|
+
className="dk-btn-amber"
|
|
131
|
+
style={{ ...btnAmber, flex: 1, width: 'auto' }}
|
|
109
132
|
>
|
|
110
133
|
Start Game
|
|
111
134
|
</button>
|
|
@@ -118,19 +141,20 @@ export default function Play() {
|
|
|
118
141
|
|
|
119
142
|
// Playing or ended
|
|
120
143
|
return (
|
|
121
|
-
<div
|
|
144
|
+
<div style={{ height: 'calc(100vh - 80px)', position: 'relative' }}>
|
|
122
145
|
<PhoneFrame>
|
|
123
146
|
{GameRenderer && platform && gameState ? (
|
|
124
147
|
<GameRenderer platform={platform} state={gameState} />
|
|
125
148
|
) : (
|
|
126
|
-
<div
|
|
149
|
+
<div style={{ padding: 16, color: '#71717a' }}>Loading game...</div>
|
|
127
150
|
)}
|
|
128
151
|
</PhoneFrame>
|
|
129
152
|
{room.phase === 'ended' && isHost && (
|
|
130
|
-
<div
|
|
153
|
+
<div style={{ position: 'absolute', bottom: 16, left: '50%', transform: 'translateX(-50%)', zIndex: 9999 }}>
|
|
131
154
|
<button
|
|
132
155
|
onClick={() => socket?.emit('game:playAgain')}
|
|
133
|
-
className="
|
|
156
|
+
className="dk-btn-amber"
|
|
157
|
+
style={{ ...btnAmber, width: 'auto', padding: '8px 24px', borderRadius: 9999 }}
|
|
134
158
|
>
|
|
135
159
|
Play Again
|
|
136
160
|
</button>
|
|
@@ -3,6 +3,13 @@ import PhoneFrame from '../components/PhoneFrame';
|
|
|
3
3
|
|
|
4
4
|
const PLAYER_NAMES = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi'];
|
|
5
5
|
|
|
6
|
+
/* ── shared inline-style helpers ── */
|
|
7
|
+
const card: React.CSSProperties = { background: '#18181b', borderRadius: 8, padding: 12 };
|
|
8
|
+
const label: React.CSSProperties = { fontSize: 13, fontWeight: 700, color: '#a1a1aa', marginBottom: 8 };
|
|
9
|
+
const inputBase: React.CSSProperties = { width: '100%', background: '#27272a', border: '1px solid #3f3f46', borderRadius: 4, padding: '4px 8px', fontSize: 13, color: '#e5e5e5' };
|
|
10
|
+
const btnAmber: React.CSSProperties = { background: '#d97706', color: '#fff', border: 'none', padding: '4px 12px', borderRadius: 4, fontSize: 13, cursor: 'pointer' };
|
|
11
|
+
const btnZinc: React.CSSProperties = { background: '#3f3f46', color: '#fff', border: 'none', padding: '4px 12px', borderRadius: 4, fontSize: 13, cursor: 'pointer', width: '100%' };
|
|
12
|
+
|
|
6
13
|
export default function Preview() {
|
|
7
14
|
const [playerCount, setPlayerCount] = useState(3);
|
|
8
15
|
const [playerIndex, setPlayerIndex] = useState(0);
|
|
@@ -122,6 +129,10 @@ export default function Preview() {
|
|
|
122
129
|
console.log('Game result reported:', result);
|
|
123
130
|
},
|
|
124
131
|
getAssetUrl: (assetPath: string) => `/assets/${assetPath}`,
|
|
132
|
+
getDeviceCapabilities: () => ({ haptics: false, motion: false }),
|
|
133
|
+
haptic: () => {},
|
|
134
|
+
onShake: () => () => {},
|
|
135
|
+
onTilt: () => () => {},
|
|
125
136
|
};
|
|
126
137
|
}, [engine]);
|
|
127
138
|
|
|
@@ -179,14 +190,14 @@ export default function Preview() {
|
|
|
179
190
|
}, []);
|
|
180
191
|
|
|
181
192
|
return (
|
|
182
|
-
<div
|
|
193
|
+
<div style={{ display: 'flex', gap: 16, height: 'calc(100vh - 80px)' }}>
|
|
183
194
|
{/* Renderer — half the screen width */}
|
|
184
|
-
<div
|
|
195
|
+
<div style={{ width: '50%', height: '100%' }}>
|
|
185
196
|
<PhoneFrame>
|
|
186
197
|
{GameRenderer && platform && viewState ? (
|
|
187
198
|
<GameRenderer platform={platform} state={viewState} />
|
|
188
199
|
) : (
|
|
189
|
-
<div
|
|
200
|
+
<div style={{ padding: 16, color: '#71717a' }}>
|
|
190
201
|
{!engine ? 'Loading engine...' : 'Initializing game...'}
|
|
191
202
|
</div>
|
|
192
203
|
)}
|
|
@@ -194,26 +205,27 @@ export default function Preview() {
|
|
|
194
205
|
</div>
|
|
195
206
|
|
|
196
207
|
{/* Control Panel — fills remaining width, resizable two-column */}
|
|
197
|
-
<div ref={panelRef}
|
|
208
|
+
<div ref={panelRef} style={{ flex: 1, minWidth: 0, display: 'flex', height: '100%' }}>
|
|
198
209
|
{/* Left column: Players & Controls */}
|
|
199
|
-
<div
|
|
210
|
+
<div style={{ width: `${splitRatio * 100}%`, display: 'flex', flexDirection: 'column', gap: 16, overflow: 'auto', paddingRight: 4 }}>
|
|
200
211
|
{/* Player Count */}
|
|
201
|
-
<div
|
|
202
|
-
<h3
|
|
212
|
+
<div style={card}>
|
|
213
|
+
<h3 style={label}>Player Count</h3>
|
|
203
214
|
<input
|
|
204
215
|
type="number"
|
|
205
216
|
min={2}
|
|
206
217
|
max={32}
|
|
207
218
|
value={playerCount}
|
|
208
219
|
onChange={(e) => setPlayerCount(Math.max(2, Math.min(32, Number(e.target.value))))}
|
|
209
|
-
className="
|
|
220
|
+
className="dk-input"
|
|
221
|
+
style={inputBase}
|
|
210
222
|
/>
|
|
211
223
|
</div>
|
|
212
224
|
|
|
213
225
|
{/* Player Switcher */}
|
|
214
|
-
<div
|
|
215
|
-
<h3
|
|
216
|
-
<div
|
|
226
|
+
<div style={{ ...card, flex: 1, overflow: 'auto' }}>
|
|
227
|
+
<h3 style={label}>Current Player</h3>
|
|
228
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
217
229
|
{mockPlayers.map((p, i) => {
|
|
218
230
|
const isActive = i === playerIndex;
|
|
219
231
|
const hue = (i * 137) % 360; // deterministic color per player
|
|
@@ -241,28 +253,49 @@ export default function Preview() {
|
|
|
241
253
|
<button
|
|
242
254
|
key={p.id}
|
|
243
255
|
onClick={() => setPlayerIndex(i)}
|
|
244
|
-
className={
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
className={isActive ? '' : 'dk-player-btn'}
|
|
257
|
+
style={{
|
|
258
|
+
width: '100%',
|
|
259
|
+
display: 'flex',
|
|
260
|
+
alignItems: 'center',
|
|
261
|
+
gap: 8,
|
|
262
|
+
padding: '6px 8px',
|
|
263
|
+
borderRadius: 4,
|
|
264
|
+
fontSize: 13,
|
|
265
|
+
textAlign: 'left' as const,
|
|
266
|
+
border: 'none',
|
|
267
|
+
cursor: 'pointer',
|
|
268
|
+
...(isActive
|
|
269
|
+
? { background: 'rgba(217, 119, 6, 0.2)', boxShadow: 'inset 0 0 0 1px #f59e0b', color: '#fff' }
|
|
270
|
+
: { background: '#27272a', color: '#d4d4d8' }),
|
|
271
|
+
}}
|
|
249
272
|
>
|
|
250
273
|
{/* Avatar */}
|
|
251
274
|
<div
|
|
252
|
-
|
|
253
|
-
|
|
275
|
+
style={{
|
|
276
|
+
width: 24,
|
|
277
|
+
height: 24,
|
|
278
|
+
borderRadius: '50%',
|
|
279
|
+
display: 'flex',
|
|
280
|
+
alignItems: 'center',
|
|
281
|
+
justifyContent: 'center',
|
|
282
|
+
fontSize: 11,
|
|
283
|
+
fontWeight: 700,
|
|
284
|
+
flexShrink: 0,
|
|
285
|
+
background: `hsl(${hue}, 55%, 45%)`,
|
|
286
|
+
}}
|
|
254
287
|
>
|
|
255
288
|
{p.nickname[0]}
|
|
256
289
|
</div>
|
|
257
|
-
<div
|
|
258
|
-
<div
|
|
259
|
-
<span
|
|
290
|
+
<div style={{ minWidth: 0, flex: 1 }}>
|
|
291
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
292
|
+
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.nickname}</span>
|
|
260
293
|
{p.isHost && (
|
|
261
|
-
<span
|
|
294
|
+
<span style={{ fontSize: 10, color: '#fbbf24', flexShrink: 0 }}>HOST</span>
|
|
262
295
|
)}
|
|
263
296
|
</div>
|
|
264
297
|
{roleLabel && (
|
|
265
|
-
<div
|
|
298
|
+
<div style={{ fontSize: 10, color: '#71717a', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
266
299
|
{roleLabel}
|
|
267
300
|
</div>
|
|
268
301
|
)}
|
|
@@ -275,14 +308,15 @@ export default function Preview() {
|
|
|
275
308
|
|
|
276
309
|
{/* Game Result */}
|
|
277
310
|
{gameOver && gameResult && (
|
|
278
|
-
<div
|
|
279
|
-
<h3
|
|
280
|
-
<pre
|
|
311
|
+
<div style={card}>
|
|
312
|
+
<h3 style={{ ...label, color: '#4ade80' }}>Game Over</h3>
|
|
313
|
+
<pre style={{ fontSize: 11, fontFamily: 'monospace', background: '#27272a', borderRadius: 4, padding: 8, overflow: 'auto', maxHeight: 128, whiteSpace: 'pre-wrap' }}>
|
|
281
314
|
{JSON.stringify(gameResult, null, 2)}
|
|
282
315
|
</pre>
|
|
283
316
|
<button
|
|
284
317
|
onClick={resetGame}
|
|
285
|
-
className="
|
|
318
|
+
className="dk-btn-amber"
|
|
319
|
+
style={{ ...btnAmber, marginTop: 8, width: '100%' }}
|
|
286
320
|
>
|
|
287
321
|
Reset Game
|
|
288
322
|
</button>
|
|
@@ -291,10 +325,11 @@ export default function Preview() {
|
|
|
291
325
|
|
|
292
326
|
{/* Reset button (when game is not over) */}
|
|
293
327
|
{!gameOver && engine && (
|
|
294
|
-
<div
|
|
328
|
+
<div style={card}>
|
|
295
329
|
<button
|
|
296
330
|
onClick={resetGame}
|
|
297
|
-
className="
|
|
331
|
+
className="dk-btn-zinc"
|
|
332
|
+
style={btnZinc}
|
|
298
333
|
>
|
|
299
334
|
Reset Game
|
|
300
335
|
</button>
|
|
@@ -304,40 +339,43 @@ export default function Preview() {
|
|
|
304
339
|
|
|
305
340
|
{/* Drag handle */}
|
|
306
341
|
<div
|
|
307
|
-
className="
|
|
342
|
+
className="dk-resize-bar"
|
|
343
|
+
style={{ flexShrink: 0, width: 8, cursor: 'col-resize', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
|
308
344
|
onMouseDown={() => { dragging.current = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; }}
|
|
309
345
|
>
|
|
310
|
-
<div className="
|
|
346
|
+
<div className="dk-resize-line" style={{ width: 2, height: 32, background: '#3f3f46', borderRadius: 2 }} />
|
|
311
347
|
</div>
|
|
312
348
|
|
|
313
349
|
{/* Right column: State & Logs */}
|
|
314
|
-
<div
|
|
350
|
+
<div style={{ width: `${(1 - splitRatio) * 100}%`, display: 'flex', flexDirection: 'column', gap: 16, overflow: 'auto', paddingLeft: 4 }}>
|
|
315
351
|
{/* State Editor */}
|
|
316
|
-
<div
|
|
317
|
-
<h3
|
|
352
|
+
<div style={{ ...card, flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
353
|
+
<h3 style={label}>Game State (Full)</h3>
|
|
318
354
|
<textarea
|
|
319
355
|
value={stateJson}
|
|
320
356
|
onChange={(e) => setStateJson(e.target.value)}
|
|
321
|
-
className="
|
|
357
|
+
className="dk-input"
|
|
358
|
+
style={{ flex: 1, background: '#27272a', border: '1px solid #3f3f46', borderRadius: 4, padding: 8, fontFamily: 'monospace', fontSize: 11, resize: 'none', minHeight: 120, color: '#e5e5e5' }}
|
|
322
359
|
/>
|
|
323
360
|
<button
|
|
324
361
|
onClick={applyState}
|
|
325
|
-
className="
|
|
362
|
+
className="dk-btn-amber"
|
|
363
|
+
style={{ ...btnAmber, marginTop: 8 }}
|
|
326
364
|
>
|
|
327
365
|
Apply State
|
|
328
366
|
</button>
|
|
329
367
|
</div>
|
|
330
368
|
|
|
331
369
|
{/* Action Log */}
|
|
332
|
-
<div
|
|
333
|
-
<h3
|
|
370
|
+
<div style={{ ...card, height: 192, overflow: 'auto' }}>
|
|
371
|
+
<h3 style={label}>Action Log</h3>
|
|
334
372
|
{actions.length === 0 ? (
|
|
335
|
-
<p
|
|
373
|
+
<p style={{ color: '#71717a', fontSize: 11 }}>No actions yet</p>
|
|
336
374
|
) : (
|
|
337
|
-
<div
|
|
375
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
338
376
|
{actions.map((a, i) => (
|
|
339
|
-
<div key={i}
|
|
340
|
-
<span
|
|
377
|
+
<div key={i} style={{ fontSize: 11, fontFamily: 'monospace', background: '#27272a', borderRadius: 4, padding: 4 }}>
|
|
378
|
+
<span style={{ color: '#fbbf24' }}>{a.player}</span>: {JSON.stringify(a.action)}
|
|
341
379
|
</div>
|
|
342
380
|
))}
|
|
343
381
|
</div>
|