@littlepartytime/dev-kit 1.11.0 → 1.12.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.
@@ -14,15 +14,25 @@ export default function App() {
14
14
  });
15
15
 
16
16
  return (
17
- <div className="min-h-screen flex flex-col">
17
+ <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
18
18
  {/* Nav */}
19
- <nav className="bg-zinc-900 border-b border-zinc-800 px-4 py-2 flex gap-4">
20
- <span className="text-amber-500 font-bold mr-4">LPT Dev Kit</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={`px-3 py-1 rounded ${page === p ? 'bg-amber-600 text-white' : 'text-zinc-400 hover:text-white'}`}
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 className="flex-1 p-4">
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
- className="flex items-center justify-center h-full overflow-hidden"
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
- className="absolute left-1/2 -translate-x-1/2 bg-black rounded-full"
101
- style={{ top: BEZEL + 11, width: 126, height: 37, zIndex: 10 }}
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,
@@ -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
- body { background: #0a0a0a; color: #e5e5e5; }
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 className="grid grid-cols-2 gap-4 h-[calc(100vh-80px)]">
24
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, height: 'calc(100vh - 80px)' }}>
23
25
  {/* Room State */}
24
- <div className="bg-zinc-900 rounded-lg p-4 overflow-auto">
25
- <h2 className="text-lg font-bold mb-4 text-amber-400">Room State</h2>
26
- <pre className="text-xs font-mono whitespace-pre-wrap">
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 className="bg-zinc-900 rounded-lg p-4 overflow-auto">
33
- <h2 className="text-lg font-bold mb-4 text-amber-400">Full Game State</h2>
34
- <pre className="text-xs font-mono whitespace-pre-wrap">
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 className="flex items-center justify-center h-[60vh]">
65
- <div className="bg-zinc-900 rounded-lg p-6 w-80">
66
- <h2 className="text-xl font-bold mb-4">Join Game</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="w-full bg-zinc-800 border border-zinc-700 rounded px-3 py-2 mb-4"
77
+ className="dk-input"
78
+ style={inputStyle}
74
79
  />
75
80
  <button
76
81
  onClick={join}
77
- className="w-full bg-amber-600 hover:bg-amber-500 text-white py-2 rounded font-semibold"
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 className="max-w-md mx-auto mt-8">
89
- <div className="bg-zinc-900 rounded-lg p-6">
90
- <h2 className="text-xl font-bold mb-4">Lobby</h2>
91
- <div className="space-y-2 mb-6">
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} className="flex items-center justify-between bg-zinc-800 rounded px-3 py-2">
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 className={p.ready ? 'text-green-400' : 'text-zinc-500'}>
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 className="flex gap-2">
107
+ <div style={{ display: 'flex', gap: 8 }}>
102
108
  <button
103
109
  onClick={() => socket?.emit('player:ready', !isReady)}
104
- className={`flex-1 py-2 rounded font-semibold ${isReady ? 'bg-zinc-700 text-zinc-300' : 'bg-green-600 text-white'}`}
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="flex-1 bg-amber-600 hover:bg-amber-500 disabled:opacity-50 text-white py-2 rounded font-semibold"
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 className="h-[calc(100vh-80px)] relative">
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 className="p-4 text-zinc-500">Loading game...</div>
149
+ <div style={{ padding: 16, color: '#71717a' }}>Loading game...</div>
131
150
  )}
132
151
  </PhoneFrame>
133
152
  {room.phase === 'ended' && isHost && (
134
- <div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-[9999]">
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="bg-amber-600 hover:bg-amber-500 text-white px-6 py-2 rounded-full font-semibold"
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,12 +3,20 @@ 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
- const [playerCount, setPlayerCount] = useState(3);
14
+ const [playerCount, setPlayerCount] = useState<number | null>(null);
8
15
  const [playerIndex, setPlayerIndex] = useState(0);
9
16
  const [actions, setActions] = useState<any[]>([]);
10
17
  const [GameRenderer, setGameRenderer] = useState<React.ComponentType<any> | null>(null);
11
18
  const [engine, setEngine] = useState<any>(null);
19
+ const [config, setConfig] = useState<{ minPlayers?: number; maxPlayers?: number } | null>(null);
12
20
  const [fullState, setFullState] = useState<any>(null);
13
21
  const [viewState, setViewState] = useState<any>(null);
14
22
  const [gameOver, setGameOver] = useState(false);
@@ -24,6 +32,7 @@ export default function Preview() {
24
32
 
25
33
  // Generate mock players
26
34
  const mockPlayers = useMemo(() => {
35
+ if (playerCount === null) return [];
27
36
  return Array.from({ length: playerCount }, (_, i) => ({
28
37
  id: `player-${i + 1}`,
29
38
  nickname: PLAYER_NAMES[i] || `Player ${i + 1}`,
@@ -35,15 +44,25 @@ export default function Preview() {
35
44
  const mockPlayersRef = useRef(mockPlayers);
36
45
  mockPlayersRef.current = mockPlayers;
37
46
 
38
- // Load renderer and engine dynamically
47
+ const minPlayers = config?.minPlayers ?? 2;
48
+ const maxPlayers = config?.maxPlayers ?? 32;
49
+
50
+ // Load renderer, engine, and config dynamically
39
51
  useEffect(() => {
40
52
  import('/src/index.ts').then((mod) => {
41
53
  setGameRenderer(() => mod.Renderer || mod.default);
42
54
  if (mod.engine) {
43
55
  setEngine(mod.engine);
44
56
  }
57
+ if (mod.config) {
58
+ setConfig(mod.config);
59
+ setPlayerCount(mod.config.minPlayers ?? 3);
60
+ } else {
61
+ setPlayerCount(3);
62
+ }
45
63
  }).catch((err) => {
46
64
  console.error('Failed to load game module:', err);
65
+ setPlayerCount(3);
47
66
  // Fallback: try loading renderer directly
48
67
  import('/src/renderer.tsx').then((mod) => {
49
68
  setGameRenderer(() => mod.default || mod.Renderer);
@@ -55,7 +74,7 @@ export default function Preview() {
55
74
 
56
75
  // Initialize game when engine loads or player count changes
57
76
  useEffect(() => {
58
- if (!engine) return;
77
+ if (!engine || mockPlayers.length === 0) return;
59
78
  const initialState = engine.init(mockPlayers);
60
79
  setFullState(initialState);
61
80
  setGameOver(false);
@@ -159,7 +178,7 @@ export default function Preview() {
159
178
 
160
179
  // Clamp playerIndex when playerCount decreases
161
180
  useEffect(() => {
162
- if (playerIndex >= playerCount) {
181
+ if (playerCount !== null && playerIndex >= playerCount) {
163
182
  setPlayerIndex(0);
164
183
  }
165
184
  }, [playerCount, playerIndex]);
@@ -183,14 +202,14 @@ export default function Preview() {
183
202
  }, []);
184
203
 
185
204
  return (
186
- <div className="flex gap-4 h-[calc(100vh-80px)]">
205
+ <div style={{ display: 'flex', gap: 16, height: 'calc(100vh - 80px)' }}>
187
206
  {/* Renderer — half the screen width */}
188
- <div className="h-full" style={{ width: '50%' }}>
207
+ <div style={{ width: '50%', height: '100%' }}>
189
208
  <PhoneFrame>
190
209
  {GameRenderer && platform && viewState ? (
191
210
  <GameRenderer platform={platform} state={viewState} />
192
211
  ) : (
193
- <div className="p-4 text-zinc-500">
212
+ <div style={{ padding: 16, color: '#71717a' }}>
194
213
  {!engine ? 'Loading engine...' : 'Initializing game...'}
195
214
  </div>
196
215
  )}
@@ -198,26 +217,27 @@ export default function Preview() {
198
217
  </div>
199
218
 
200
219
  {/* Control Panel — fills remaining width, resizable two-column */}
201
- <div ref={panelRef} className="flex-1 min-w-0 flex h-full">
220
+ <div ref={panelRef} style={{ flex: 1, minWidth: 0, display: 'flex', height: '100%' }}>
202
221
  {/* Left column: Players & Controls */}
203
- <div className="flex flex-col gap-4 overflow-auto pr-1" style={{ width: `${splitRatio * 100}%` }}>
222
+ <div style={{ width: `${splitRatio * 100}%`, display: 'flex', flexDirection: 'column', gap: 16, overflow: 'auto', paddingRight: 4 }}>
204
223
  {/* Player Count */}
205
- <div className="bg-zinc-900 rounded-lg p-3">
206
- <h3 className="text-sm font-bold text-zinc-400 mb-2">Player Count</h3>
224
+ <div style={card}>
225
+ <h3 style={label}>Player Count</h3>
207
226
  <input
208
227
  type="number"
209
- min={2}
210
- max={32}
211
- value={playerCount}
212
- onChange={(e) => setPlayerCount(Math.max(2, Math.min(32, Number(e.target.value))))}
213
- className="w-full bg-zinc-800 border border-zinc-700 rounded px-2 py-1 text-sm"
228
+ min={minPlayers}
229
+ max={maxPlayers}
230
+ value={playerCount ?? ''}
231
+ onChange={(e) => setPlayerCount(Math.max(minPlayers, Math.min(maxPlayers, Number(e.target.value))))}
232
+ className="dk-input"
233
+ style={inputBase}
214
234
  />
215
235
  </div>
216
236
 
217
237
  {/* Player Switcher */}
218
- <div className="bg-zinc-900 rounded-lg p-3 flex-1 overflow-auto">
219
- <h3 className="text-sm font-bold text-zinc-400 mb-2">Current Player</h3>
220
- <div className="space-y-1">
238
+ <div style={{ ...card, flex: 1, overflow: 'auto' }}>
239
+ <h3 style={label}>Current Player</h3>
240
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
221
241
  {mockPlayers.map((p, i) => {
222
242
  const isActive = i === playerIndex;
223
243
  const hue = (i * 137) % 360; // deterministic color per player
@@ -245,28 +265,49 @@ export default function Preview() {
245
265
  <button
246
266
  key={p.id}
247
267
  onClick={() => setPlayerIndex(i)}
248
- className={`w-full flex items-center gap-2 px-2 py-1.5 rounded text-sm text-left transition-colors ${
249
- isActive
250
- ? 'bg-amber-600/20 ring-1 ring-amber-500 text-white'
251
- : 'bg-zinc-800 hover:bg-zinc-700 text-zinc-300'
252
- }`}
268
+ className={isActive ? '' : 'dk-player-btn'}
269
+ style={{
270
+ width: '100%',
271
+ display: 'flex',
272
+ alignItems: 'center',
273
+ gap: 8,
274
+ padding: '6px 8px',
275
+ borderRadius: 4,
276
+ fontSize: 13,
277
+ textAlign: 'left' as const,
278
+ border: 'none',
279
+ cursor: 'pointer',
280
+ ...(isActive
281
+ ? { background: 'rgba(217, 119, 6, 0.2)', boxShadow: 'inset 0 0 0 1px #f59e0b', color: '#fff' }
282
+ : { background: '#27272a', color: '#d4d4d8' }),
283
+ }}
253
284
  >
254
285
  {/* Avatar */}
255
286
  <div
256
- className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold shrink-0"
257
- style={{ background: `hsl(${hue}, 55%, 45%)` }}
287
+ style={{
288
+ width: 24,
289
+ height: 24,
290
+ borderRadius: '50%',
291
+ display: 'flex',
292
+ alignItems: 'center',
293
+ justifyContent: 'center',
294
+ fontSize: 11,
295
+ fontWeight: 700,
296
+ flexShrink: 0,
297
+ background: `hsl(${hue}, 55%, 45%)`,
298
+ }}
258
299
  >
259
300
  {p.nickname[0]}
260
301
  </div>
261
- <div className="min-w-0 flex-1">
262
- <div className="flex items-center gap-1">
263
- <span className="truncate">{p.nickname}</span>
302
+ <div style={{ minWidth: 0, flex: 1 }}>
303
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
304
+ <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.nickname}</span>
264
305
  {p.isHost && (
265
- <span className="text-[10px] text-amber-400 shrink-0">HOST</span>
306
+ <span style={{ fontSize: 10, color: '#fbbf24', flexShrink: 0 }}>HOST</span>
266
307
  )}
267
308
  </div>
268
309
  {roleLabel && (
269
- <div className="text-[10px] text-zinc-500 truncate">
310
+ <div style={{ fontSize: 10, color: '#71717a', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
270
311
  {roleLabel}
271
312
  </div>
272
313
  )}
@@ -279,14 +320,15 @@ export default function Preview() {
279
320
 
280
321
  {/* Game Result */}
281
322
  {gameOver && gameResult && (
282
- <div className="bg-zinc-900 rounded-lg p-3">
283
- <h3 className="text-sm font-bold text-green-400 mb-2">Game Over</h3>
284
- <pre className="text-xs font-mono bg-zinc-800 rounded p-2 overflow-auto max-h-32 whitespace-pre-wrap">
323
+ <div style={card}>
324
+ <h3 style={{ ...label, color: '#4ade80' }}>Game Over</h3>
325
+ <pre style={{ fontSize: 11, fontFamily: 'monospace', background: '#27272a', borderRadius: 4, padding: 8, overflow: 'auto', maxHeight: 128, whiteSpace: 'pre-wrap' }}>
285
326
  {JSON.stringify(gameResult, null, 2)}
286
327
  </pre>
287
328
  <button
288
329
  onClick={resetGame}
289
- className="mt-2 w-full bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
330
+ className="dk-btn-amber"
331
+ style={{ ...btnAmber, marginTop: 8, width: '100%' }}
290
332
  >
291
333
  Reset Game
292
334
  </button>
@@ -295,10 +337,11 @@ export default function Preview() {
295
337
 
296
338
  {/* Reset button (when game is not over) */}
297
339
  {!gameOver && engine && (
298
- <div className="bg-zinc-900 rounded-lg p-3">
340
+ <div style={card}>
299
341
  <button
300
342
  onClick={resetGame}
301
- className="w-full bg-zinc-700 hover:bg-zinc-600 text-white px-3 py-1 rounded text-sm"
343
+ className="dk-btn-zinc"
344
+ style={btnZinc}
302
345
  >
303
346
  Reset Game
304
347
  </button>
@@ -308,40 +351,43 @@ export default function Preview() {
308
351
 
309
352
  {/* Drag handle */}
310
353
  <div
311
- className="shrink-0 w-2 cursor-col-resize flex items-center justify-center group"
354
+ className="dk-resize-bar"
355
+ style={{ flexShrink: 0, width: 8, cursor: 'col-resize', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
312
356
  onMouseDown={() => { dragging.current = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; }}
313
357
  >
314
- <div className="w-0.5 h-8 bg-zinc-700 rounded group-hover:bg-zinc-500 transition-colors" />
358
+ <div className="dk-resize-line" style={{ width: 2, height: 32, background: '#3f3f46', borderRadius: 2 }} />
315
359
  </div>
316
360
 
317
361
  {/* Right column: State & Logs */}
318
- <div className="flex flex-col gap-4 overflow-auto pl-1" style={{ width: `${(1 - splitRatio) * 100}%` }}>
362
+ <div style={{ width: `${(1 - splitRatio) * 100}%`, display: 'flex', flexDirection: 'column', gap: 16, overflow: 'auto', paddingLeft: 4 }}>
319
363
  {/* State Editor */}
320
- <div className="bg-zinc-900 rounded-lg p-3 flex-1 flex flex-col min-h-0">
321
- <h3 className="text-sm font-bold text-zinc-400 mb-2">Game State (Full)</h3>
364
+ <div style={{ ...card, flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
365
+ <h3 style={label}>Game State (Full)</h3>
322
366
  <textarea
323
367
  value={stateJson}
324
368
  onChange={(e) => setStateJson(e.target.value)}
325
- className="flex-1 bg-zinc-800 border border-zinc-700 rounded p-2 font-mono text-xs resize-none min-h-[120px]"
369
+ className="dk-input"
370
+ style={{ flex: 1, background: '#27272a', border: '1px solid #3f3f46', borderRadius: 4, padding: 8, fontFamily: 'monospace', fontSize: 11, resize: 'none', minHeight: 120, color: '#e5e5e5' }}
326
371
  />
327
372
  <button
328
373
  onClick={applyState}
329
- className="mt-2 bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
374
+ className="dk-btn-amber"
375
+ style={{ ...btnAmber, marginTop: 8 }}
330
376
  >
331
377
  Apply State
332
378
  </button>
333
379
  </div>
334
380
 
335
381
  {/* Action Log */}
336
- <div className="bg-zinc-900 rounded-lg p-3 h-48 overflow-auto">
337
- <h3 className="text-sm font-bold text-zinc-400 mb-2">Action Log</h3>
382
+ <div style={{ ...card, height: 192, overflow: 'auto' }}>
383
+ <h3 style={label}>Action Log</h3>
338
384
  {actions.length === 0 ? (
339
- <p className="text-zinc-500 text-xs">No actions yet</p>
385
+ <p style={{ color: '#71717a', fontSize: 11 }}>No actions yet</p>
340
386
  ) : (
341
- <div className="space-y-1">
387
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
342
388
  {actions.map((a, i) => (
343
- <div key={i} className="text-xs font-mono bg-zinc-800 rounded p-1">
344
- <span className="text-amber-400">{a.player}</span>: {JSON.stringify(a.action)}
389
+ <div key={i} style={{ fontSize: 11, fontFamily: 'monospace', background: '#27272a', borderRadius: 4, padding: 4 }}>
390
+ <span style={{ color: '#fbbf24' }}>{a.player}</span>: {JSON.stringify(a.action)}
345
391
  </div>
346
392
  ))}
347
393
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlepartytime/dev-kit",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "description": "Development toolkit CLI for Little Party Time game developers",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",