@littlepartytime/dev-kit 1.11.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('');
|
|
@@ -61,20 +65,22 @@ export default function Play() {
|
|
|
61
65
|
|
|
62
66
|
if (!joined) {
|
|
63
67
|
return (
|
|
64
|
-
<div
|
|
65
|
-
<div
|
|
66
|
-
<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>
|
|
67
71
|
<input
|
|
68
72
|
type="text"
|
|
69
73
|
placeholder="Your nickname"
|
|
70
74
|
value={nickname}
|
|
71
75
|
onChange={(e) => setNickname(e.target.value)}
|
|
72
76
|
onKeyDown={(e) => e.key === 'Enter' && join()}
|
|
73
|
-
className="
|
|
77
|
+
className="dk-input"
|
|
78
|
+
style={inputStyle}
|
|
74
79
|
/>
|
|
75
80
|
<button
|
|
76
81
|
onClick={join}
|
|
77
|
-
className="
|
|
82
|
+
className="dk-btn-amber"
|
|
83
|
+
style={btnAmber}
|
|
78
84
|
>
|
|
79
85
|
Join
|
|
80
86
|
</button>
|
|
@@ -85,23 +91,35 @@ export default function Play() {
|
|
|
85
91
|
|
|
86
92
|
if (room.phase === 'lobby' || room.phase === 'ready') {
|
|
87
93
|
return (
|
|
88
|
-
<div
|
|
89
|
-
<div
|
|
90
|
-
<h2
|
|
91
|
-
<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 }}>
|
|
92
98
|
{room.players.map((p: any) => (
|
|
93
|
-
<div key={p.id}
|
|
99
|
+
<div key={p.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: '#27272a', borderRadius: 4, padding: '8px 12px' }}>
|
|
94
100
|
<span>{p.nickname} {p.isHost && '(Host)'}</span>
|
|
95
|
-
<span
|
|
101
|
+
<span style={{ color: p.ready ? '#4ade80' : '#71717a' }}>
|
|
96
102
|
{p.ready ? 'Ready' : 'Not Ready'}
|
|
97
103
|
</span>
|
|
98
104
|
</div>
|
|
99
105
|
))}
|
|
100
106
|
</div>
|
|
101
|
-
<div
|
|
107
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
102
108
|
<button
|
|
103
109
|
onClick={() => socket?.emit('player:ready', !isReady)}
|
|
104
|
-
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
|
+
}}
|
|
105
123
|
>
|
|
106
124
|
{isReady ? 'Cancel Ready' : 'Ready'}
|
|
107
125
|
</button>
|
|
@@ -109,7 +127,8 @@ export default function Play() {
|
|
|
109
127
|
<button
|
|
110
128
|
onClick={() => socket?.emit('game:start')}
|
|
111
129
|
disabled={!room.players.every((p: any) => p.ready) || room.players.length < 2}
|
|
112
|
-
className="
|
|
130
|
+
className="dk-btn-amber"
|
|
131
|
+
style={{ ...btnAmber, flex: 1, width: 'auto' }}
|
|
113
132
|
>
|
|
114
133
|
Start Game
|
|
115
134
|
</button>
|
|
@@ -122,19 +141,20 @@ export default function Play() {
|
|
|
122
141
|
|
|
123
142
|
// Playing or ended
|
|
124
143
|
return (
|
|
125
|
-
<div
|
|
144
|
+
<div style={{ height: 'calc(100vh - 80px)', position: 'relative' }}>
|
|
126
145
|
<PhoneFrame>
|
|
127
146
|
{GameRenderer && platform && gameState ? (
|
|
128
147
|
<GameRenderer platform={platform} state={gameState} />
|
|
129
148
|
) : (
|
|
130
|
-
<div
|
|
149
|
+
<div style={{ padding: 16, color: '#71717a' }}>Loading game...</div>
|
|
131
150
|
)}
|
|
132
151
|
</PhoneFrame>
|
|
133
152
|
{room.phase === 'ended' && isHost && (
|
|
134
|
-
<div
|
|
153
|
+
<div style={{ position: 'absolute', bottom: 16, left: '50%', transform: 'translateX(-50%)', zIndex: 9999 }}>
|
|
135
154
|
<button
|
|
136
155
|
onClick={() => socket?.emit('game:playAgain')}
|
|
137
|
-
className="
|
|
156
|
+
className="dk-btn-amber"
|
|
157
|
+
style={{ ...btnAmber, width: 'auto', padding: '8px 24px', borderRadius: 9999 }}
|
|
138
158
|
>
|
|
139
159
|
Play Again
|
|
140
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);
|
|
@@ -183,14 +190,14 @@ export default function Preview() {
|
|
|
183
190
|
}, []);
|
|
184
191
|
|
|
185
192
|
return (
|
|
186
|
-
<div
|
|
193
|
+
<div style={{ display: 'flex', gap: 16, height: 'calc(100vh - 80px)' }}>
|
|
187
194
|
{/* Renderer — half the screen width */}
|
|
188
|
-
<div
|
|
195
|
+
<div style={{ width: '50%', height: '100%' }}>
|
|
189
196
|
<PhoneFrame>
|
|
190
197
|
{GameRenderer && platform && viewState ? (
|
|
191
198
|
<GameRenderer platform={platform} state={viewState} />
|
|
192
199
|
) : (
|
|
193
|
-
<div
|
|
200
|
+
<div style={{ padding: 16, color: '#71717a' }}>
|
|
194
201
|
{!engine ? 'Loading engine...' : 'Initializing game...'}
|
|
195
202
|
</div>
|
|
196
203
|
)}
|
|
@@ -198,26 +205,27 @@ export default function Preview() {
|
|
|
198
205
|
</div>
|
|
199
206
|
|
|
200
207
|
{/* Control Panel — fills remaining width, resizable two-column */}
|
|
201
|
-
<div ref={panelRef}
|
|
208
|
+
<div ref={panelRef} style={{ flex: 1, minWidth: 0, display: 'flex', height: '100%' }}>
|
|
202
209
|
{/* Left column: Players & Controls */}
|
|
203
|
-
<div
|
|
210
|
+
<div style={{ width: `${splitRatio * 100}%`, display: 'flex', flexDirection: 'column', gap: 16, overflow: 'auto', paddingRight: 4 }}>
|
|
204
211
|
{/* Player Count */}
|
|
205
|
-
<div
|
|
206
|
-
<h3
|
|
212
|
+
<div style={card}>
|
|
213
|
+
<h3 style={label}>Player Count</h3>
|
|
207
214
|
<input
|
|
208
215
|
type="number"
|
|
209
216
|
min={2}
|
|
210
217
|
max={32}
|
|
211
218
|
value={playerCount}
|
|
212
219
|
onChange={(e) => setPlayerCount(Math.max(2, Math.min(32, Number(e.target.value))))}
|
|
213
|
-
className="
|
|
220
|
+
className="dk-input"
|
|
221
|
+
style={inputBase}
|
|
214
222
|
/>
|
|
215
223
|
</div>
|
|
216
224
|
|
|
217
225
|
{/* Player Switcher */}
|
|
218
|
-
<div
|
|
219
|
-
<h3
|
|
220
|
-
<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 }}>
|
|
221
229
|
{mockPlayers.map((p, i) => {
|
|
222
230
|
const isActive = i === playerIndex;
|
|
223
231
|
const hue = (i * 137) % 360; // deterministic color per player
|
|
@@ -245,28 +253,49 @@ export default function Preview() {
|
|
|
245
253
|
<button
|
|
246
254
|
key={p.id}
|
|
247
255
|
onClick={() => setPlayerIndex(i)}
|
|
248
|
-
className={
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
}}
|
|
253
272
|
>
|
|
254
273
|
{/* Avatar */}
|
|
255
274
|
<div
|
|
256
|
-
|
|
257
|
-
|
|
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
|
+
}}
|
|
258
287
|
>
|
|
259
288
|
{p.nickname[0]}
|
|
260
289
|
</div>
|
|
261
|
-
<div
|
|
262
|
-
<div
|
|
263
|
-
<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>
|
|
264
293
|
{p.isHost && (
|
|
265
|
-
<span
|
|
294
|
+
<span style={{ fontSize: 10, color: '#fbbf24', flexShrink: 0 }}>HOST</span>
|
|
266
295
|
)}
|
|
267
296
|
</div>
|
|
268
297
|
{roleLabel && (
|
|
269
|
-
<div
|
|
298
|
+
<div style={{ fontSize: 10, color: '#71717a', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
270
299
|
{roleLabel}
|
|
271
300
|
</div>
|
|
272
301
|
)}
|
|
@@ -279,14 +308,15 @@ export default function Preview() {
|
|
|
279
308
|
|
|
280
309
|
{/* Game Result */}
|
|
281
310
|
{gameOver && gameResult && (
|
|
282
|
-
<div
|
|
283
|
-
<h3
|
|
284
|
-
<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' }}>
|
|
285
314
|
{JSON.stringify(gameResult, null, 2)}
|
|
286
315
|
</pre>
|
|
287
316
|
<button
|
|
288
317
|
onClick={resetGame}
|
|
289
|
-
className="
|
|
318
|
+
className="dk-btn-amber"
|
|
319
|
+
style={{ ...btnAmber, marginTop: 8, width: '100%' }}
|
|
290
320
|
>
|
|
291
321
|
Reset Game
|
|
292
322
|
</button>
|
|
@@ -295,10 +325,11 @@ export default function Preview() {
|
|
|
295
325
|
|
|
296
326
|
{/* Reset button (when game is not over) */}
|
|
297
327
|
{!gameOver && engine && (
|
|
298
|
-
<div
|
|
328
|
+
<div style={card}>
|
|
299
329
|
<button
|
|
300
330
|
onClick={resetGame}
|
|
301
|
-
className="
|
|
331
|
+
className="dk-btn-zinc"
|
|
332
|
+
style={btnZinc}
|
|
302
333
|
>
|
|
303
334
|
Reset Game
|
|
304
335
|
</button>
|
|
@@ -308,40 +339,43 @@ export default function Preview() {
|
|
|
308
339
|
|
|
309
340
|
{/* Drag handle */}
|
|
310
341
|
<div
|
|
311
|
-
className="
|
|
342
|
+
className="dk-resize-bar"
|
|
343
|
+
style={{ flexShrink: 0, width: 8, cursor: 'col-resize', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
|
312
344
|
onMouseDown={() => { dragging.current = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; }}
|
|
313
345
|
>
|
|
314
|
-
<div className="
|
|
346
|
+
<div className="dk-resize-line" style={{ width: 2, height: 32, background: '#3f3f46', borderRadius: 2 }} />
|
|
315
347
|
</div>
|
|
316
348
|
|
|
317
349
|
{/* Right column: State & Logs */}
|
|
318
|
-
<div
|
|
350
|
+
<div style={{ width: `${(1 - splitRatio) * 100}%`, display: 'flex', flexDirection: 'column', gap: 16, overflow: 'auto', paddingLeft: 4 }}>
|
|
319
351
|
{/* State Editor */}
|
|
320
|
-
<div
|
|
321
|
-
<h3
|
|
352
|
+
<div style={{ ...card, flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
|
353
|
+
<h3 style={label}>Game State (Full)</h3>
|
|
322
354
|
<textarea
|
|
323
355
|
value={stateJson}
|
|
324
356
|
onChange={(e) => setStateJson(e.target.value)}
|
|
325
|
-
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' }}
|
|
326
359
|
/>
|
|
327
360
|
<button
|
|
328
361
|
onClick={applyState}
|
|
329
|
-
className="
|
|
362
|
+
className="dk-btn-amber"
|
|
363
|
+
style={{ ...btnAmber, marginTop: 8 }}
|
|
330
364
|
>
|
|
331
365
|
Apply State
|
|
332
366
|
</button>
|
|
333
367
|
</div>
|
|
334
368
|
|
|
335
369
|
{/* Action Log */}
|
|
336
|
-
<div
|
|
337
|
-
<h3
|
|
370
|
+
<div style={{ ...card, height: 192, overflow: 'auto' }}>
|
|
371
|
+
<h3 style={label}>Action Log</h3>
|
|
338
372
|
{actions.length === 0 ? (
|
|
339
|
-
<p
|
|
373
|
+
<p style={{ color: '#71717a', fontSize: 11 }}>No actions yet</p>
|
|
340
374
|
) : (
|
|
341
|
-
<div
|
|
375
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
342
376
|
{actions.map((a, i) => (
|
|
343
|
-
<div key={i}
|
|
344
|
-
<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)}
|
|
345
379
|
</div>
|
|
346
380
|
))}
|
|
347
381
|
</div>
|