@littlepartytime/dev-kit 1.7.0 → 1.8.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.
|
@@ -41,7 +41,7 @@ export default function PhoneFrame({ children }: { children: React.ReactNode })
|
|
|
41
41
|
<div
|
|
42
42
|
ref={containerRef}
|
|
43
43
|
className="flex items-center justify-center h-full overflow-hidden"
|
|
44
|
-
style={{
|
|
44
|
+
style={{ minWidth: 0, minHeight: 0 }}
|
|
45
45
|
>
|
|
46
46
|
{/* Wrapper sized to the scaled phone for correct layout flow */}
|
|
47
47
|
<div style={{ width: BODY_W * scale, height: BODY_H * scale, flexShrink: 0 }}>
|
|
@@ -162,124 +162,145 @@ export default function Preview() {
|
|
|
162
162
|
|
|
163
163
|
return (
|
|
164
164
|
<div className="flex gap-4 h-[calc(100vh-80px)]">
|
|
165
|
-
{/* Renderer */}
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
165
|
+
{/* Renderer — constrain to phone width so it doesn't hog space */}
|
|
166
|
+
<div className="shrink-0 h-full">
|
|
167
|
+
<PhoneFrame>
|
|
168
|
+
{GameRenderer && platform && viewState ? (
|
|
169
|
+
<GameRenderer platform={platform} state={viewState} />
|
|
170
|
+
) : (
|
|
171
|
+
<div className="p-4 text-zinc-500">
|
|
172
|
+
{!engine ? 'Loading engine...' : 'Initializing game...'}
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
</PhoneFrame>
|
|
176
|
+
</div>
|
|
175
177
|
|
|
176
|
-
{/* Control Panel */}
|
|
177
|
-
<div className="w-
|
|
178
|
-
{/*
|
|
179
|
-
<div className="
|
|
180
|
-
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
{/* Control Panel — fills remaining width, two-column grid */}
|
|
179
|
+
<div className="flex-1 min-w-0 grid grid-cols-2 gap-4 overflow-auto">
|
|
180
|
+
{/* Left column: Players & Controls */}
|
|
181
|
+
<div className="flex flex-col gap-4">
|
|
182
|
+
{/* Player Count */}
|
|
183
|
+
<div className="bg-zinc-900 rounded-lg p-3">
|
|
184
|
+
<h3 className="text-sm font-bold text-zinc-400 mb-2">Player Count</h3>
|
|
185
|
+
<input
|
|
186
|
+
type="number"
|
|
187
|
+
min={2}
|
|
188
|
+
max={32}
|
|
189
|
+
value={playerCount}
|
|
190
|
+
onChange={(e) => setPlayerCount(Math.max(2, Math.min(32, Number(e.target.value))))}
|
|
191
|
+
className="w-full bg-zinc-800 border border-zinc-700 rounded px-2 py-1 text-sm"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
190
194
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
style={{ background: `hsl(${hue}, 55%, 45%)` }}
|
|
195
|
+
{/* Player Switcher */}
|
|
196
|
+
<div className="bg-zinc-900 rounded-lg p-3 flex-1 overflow-auto">
|
|
197
|
+
<h3 className="text-sm font-bold text-zinc-400 mb-2">Current Player</h3>
|
|
198
|
+
<div className="space-y-1">
|
|
199
|
+
{mockPlayers.map((p, i) => {
|
|
200
|
+
const isActive = i === playerIndex;
|
|
201
|
+
const hue = (i * 137) % 360; // deterministic color per player
|
|
202
|
+
const playerState = fullState?.players?.find((ps: any) => ps.id === p.id);
|
|
203
|
+
const extraEntries = playerState
|
|
204
|
+
? Object.entries(playerState).filter(([k]) => k !== 'id')
|
|
205
|
+
: [];
|
|
206
|
+
return (
|
|
207
|
+
<button
|
|
208
|
+
key={p.id}
|
|
209
|
+
onClick={() => setPlayerIndex(i)}
|
|
210
|
+
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded text-sm text-left transition-colors ${
|
|
211
|
+
isActive
|
|
212
|
+
? 'bg-amber-600/20 ring-1 ring-amber-500 text-white'
|
|
213
|
+
: 'bg-zinc-800 hover:bg-zinc-700 text-zinc-300'
|
|
214
|
+
}`}
|
|
212
215
|
>
|
|
213
|
-
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
{/* Avatar */}
|
|
217
|
+
<div
|
|
218
|
+
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold shrink-0"
|
|
219
|
+
style={{ background: `hsl(${hue}, 55%, 45%)` }}
|
|
220
|
+
>
|
|
221
|
+
{p.nickname[0]}
|
|
222
|
+
</div>
|
|
223
|
+
<div className="min-w-0 flex-1">
|
|
224
|
+
<div className="flex items-center gap-1">
|
|
225
|
+
<span className="truncate">{p.nickname}</span>
|
|
226
|
+
{p.isHost && (
|
|
227
|
+
<span className="text-[10px] text-amber-400 shrink-0">HOST</span>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
{extraEntries.length > 0 && (
|
|
231
|
+
<div className="text-[10px] text-zinc-500 truncate">
|
|
232
|
+
{extraEntries.map(([k, v]) => `${k}: ${typeof v === 'object' ? JSON.stringify(v) : String(v)}`).join(' · ')}
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
</button>
|
|
237
|
+
);
|
|
238
|
+
})}
|
|
239
|
+
</div>
|
|
222
240
|
</div>
|
|
241
|
+
|
|
242
|
+
{/* Game Result */}
|
|
243
|
+
{gameOver && gameResult && (
|
|
244
|
+
<div className="bg-zinc-900 rounded-lg p-3">
|
|
245
|
+
<h3 className="text-sm font-bold text-green-400 mb-2">Game Over</h3>
|
|
246
|
+
<pre className="text-xs font-mono bg-zinc-800 rounded p-2 overflow-auto max-h-32 whitespace-pre-wrap">
|
|
247
|
+
{JSON.stringify(gameResult, null, 2)}
|
|
248
|
+
</pre>
|
|
249
|
+
<button
|
|
250
|
+
onClick={resetGame}
|
|
251
|
+
className="mt-2 w-full bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
|
|
252
|
+
>
|
|
253
|
+
Reset Game
|
|
254
|
+
</button>
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
{/* Reset button (when game is not over) */}
|
|
259
|
+
{!gameOver && engine && (
|
|
260
|
+
<div className="bg-zinc-900 rounded-lg p-3">
|
|
261
|
+
<button
|
|
262
|
+
onClick={resetGame}
|
|
263
|
+
className="w-full bg-zinc-700 hover:bg-zinc-600 text-white px-3 py-1 rounded text-sm"
|
|
264
|
+
>
|
|
265
|
+
Reset Game
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
223
269
|
</div>
|
|
224
270
|
|
|
225
|
-
{/*
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
271
|
+
{/* Right column: State & Logs */}
|
|
272
|
+
<div className="flex flex-col gap-4">
|
|
273
|
+
{/* State Editor */}
|
|
274
|
+
<div className="bg-zinc-900 rounded-lg p-3 flex-1 flex flex-col min-h-0">
|
|
275
|
+
<h3 className="text-sm font-bold text-zinc-400 mb-2">Game State (Full)</h3>
|
|
276
|
+
<textarea
|
|
277
|
+
value={stateJson}
|
|
278
|
+
onChange={(e) => setStateJson(e.target.value)}
|
|
279
|
+
className="flex-1 bg-zinc-800 border border-zinc-700 rounded p-2 font-mono text-xs resize-none min-h-[120px]"
|
|
280
|
+
/>
|
|
232
281
|
<button
|
|
233
|
-
onClick={
|
|
234
|
-
className="mt-2
|
|
282
|
+
onClick={applyState}
|
|
283
|
+
className="mt-2 bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
|
|
235
284
|
>
|
|
236
|
-
|
|
285
|
+
Apply State
|
|
237
286
|
</button>
|
|
238
287
|
</div>
|
|
239
|
-
)}
|
|
240
288
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
289
|
+
{/* Action Log */}
|
|
290
|
+
<div className="bg-zinc-900 rounded-lg p-3 h-48 overflow-auto">
|
|
291
|
+
<h3 className="text-sm font-bold text-zinc-400 mb-2">Action Log</h3>
|
|
292
|
+
{actions.length === 0 ? (
|
|
293
|
+
<p className="text-zinc-500 text-xs">No actions yet</p>
|
|
294
|
+
) : (
|
|
295
|
+
<div className="space-y-1">
|
|
296
|
+
{actions.map((a, i) => (
|
|
297
|
+
<div key={i} className="text-xs font-mono bg-zinc-800 rounded p-1">
|
|
298
|
+
<span className="text-amber-400">{a.player}</span>: {JSON.stringify(a.action)}
|
|
299
|
+
</div>
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
250
303
|
</div>
|
|
251
|
-
)}
|
|
252
|
-
|
|
253
|
-
{/* State Editor */}
|
|
254
|
-
<div className="bg-zinc-900 rounded-lg p-3 flex-1 flex flex-col min-h-0">
|
|
255
|
-
<h3 className="text-sm font-bold text-zinc-400 mb-2">Game State (Full)</h3>
|
|
256
|
-
<textarea
|
|
257
|
-
value={stateJson}
|
|
258
|
-
onChange={(e) => setStateJson(e.target.value)}
|
|
259
|
-
className="flex-1 bg-zinc-800 border border-zinc-700 rounded p-2 font-mono text-xs resize-none min-h-[120px]"
|
|
260
|
-
/>
|
|
261
|
-
<button
|
|
262
|
-
onClick={applyState}
|
|
263
|
-
className="mt-2 bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
|
|
264
|
-
>
|
|
265
|
-
Apply State
|
|
266
|
-
</button>
|
|
267
|
-
</div>
|
|
268
|
-
|
|
269
|
-
{/* Action Log */}
|
|
270
|
-
<div className="bg-zinc-900 rounded-lg p-3 h-48 overflow-auto">
|
|
271
|
-
<h3 className="text-sm font-bold text-zinc-400 mb-2">Action Log</h3>
|
|
272
|
-
{actions.length === 0 ? (
|
|
273
|
-
<p className="text-zinc-500 text-xs">No actions yet</p>
|
|
274
|
-
) : (
|
|
275
|
-
<div className="space-y-1">
|
|
276
|
-
{actions.map((a, i) => (
|
|
277
|
-
<div key={i} className="text-xs font-mono bg-zinc-800 rounded p-1">
|
|
278
|
-
<span className="text-amber-400">{a.player}</span>: {JSON.stringify(a.action)}
|
|
279
|
-
</div>
|
|
280
|
-
))}
|
|
281
|
-
</div>
|
|
282
|
-
)}
|
|
283
304
|
</div>
|
|
284
305
|
</div>
|
|
285
306
|
</div>
|