@littlepartytime/dev-kit 1.6.2 → 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={{ flex: 1, minWidth: 0, minHeight: 0 }}
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,104 +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
- <PhoneFrame>
167
- {GameRenderer && platform && viewState ? (
168
- <GameRenderer platform={platform} state={viewState} />
169
- ) : (
170
- <div className="p-4 text-zinc-500">
171
- {!engine ? 'Loading engine...' : 'Initializing game...'}
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>
177
+
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
+ />
172
193
  </div>
173
- )}
174
- </PhoneFrame>
175
194
 
176
- {/* Control Panel */}
177
- <div className="w-80 flex flex-col gap-4 overflow-auto">
178
- {/* Player Count */}
179
- <div className="bg-zinc-900 rounded-lg p-3">
180
- <h3 className="text-sm font-bold text-zinc-400 mb-2">Player Count</h3>
181
- <input
182
- type="number"
183
- min={2}
184
- max={32}
185
- value={playerCount}
186
- onChange={(e) => setPlayerCount(Math.max(2, Math.min(32, Number(e.target.value))))}
187
- className="w-full bg-zinc-800 border border-zinc-700 rounded px-2 py-1 text-sm"
188
- />
189
- </div>
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
+ }`}
215
+ >
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>
240
+ </div>
190
241
 
191
- {/* Player Switcher */}
192
- <div className="bg-zinc-900 rounded-lg p-3">
193
- <h3 className="text-sm font-bold text-zinc-400 mb-2">Current Player</h3>
194
- <select
195
- value={playerIndex}
196
- onChange={(e) => setPlayerIndex(Number(e.target.value))}
197
- className="w-full bg-zinc-800 border border-zinc-700 rounded px-2 py-1 text-sm"
198
- >
199
- {mockPlayers.map((p, i) => (
200
- <option key={p.id} value={i}>{p.nickname}{p.isHost ? ' (Host)' : ''}</option>
201
- ))}
202
- </select>
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
+ )}
203
269
  </div>
204
270
 
205
- {/* Game Result */}
206
- {gameOver && gameResult && (
207
- <div className="bg-zinc-900 rounded-lg p-3">
208
- <h3 className="text-sm font-bold text-green-400 mb-2">Game Over</h3>
209
- <pre className="text-xs font-mono bg-zinc-800 rounded p-2 overflow-auto max-h-32 whitespace-pre-wrap">
210
- {JSON.stringify(gameResult, null, 2)}
211
- </pre>
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
+ />
212
281
  <button
213
- onClick={resetGame}
214
- className="mt-2 w-full bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
282
+ onClick={applyState}
283
+ className="mt-2 bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
215
284
  >
216
- Reset Game
285
+ Apply State
217
286
  </button>
218
287
  </div>
219
- )}
220
288
 
221
- {/* Reset button (when game is not over) */}
222
- {!gameOver && engine && (
223
- <div className="bg-zinc-900 rounded-lg p-3">
224
- <button
225
- onClick={resetGame}
226
- className="w-full bg-zinc-700 hover:bg-zinc-600 text-white px-3 py-1 rounded text-sm"
227
- >
228
- Reset Game
229
- </button>
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
+ )}
230
303
  </div>
231
- )}
232
-
233
- {/* State Editor */}
234
- <div className="bg-zinc-900 rounded-lg p-3 flex-1 flex flex-col min-h-0">
235
- <h3 className="text-sm font-bold text-zinc-400 mb-2">Game State (Full)</h3>
236
- <textarea
237
- value={stateJson}
238
- onChange={(e) => setStateJson(e.target.value)}
239
- className="flex-1 bg-zinc-800 border border-zinc-700 rounded p-2 font-mono text-xs resize-none min-h-[120px]"
240
- />
241
- <button
242
- onClick={applyState}
243
- className="mt-2 bg-amber-600 hover:bg-amber-500 text-white px-3 py-1 rounded text-sm"
244
- >
245
- Apply State
246
- </button>
247
- </div>
248
-
249
- {/* Action Log */}
250
- <div className="bg-zinc-900 rounded-lg p-3 h-48 overflow-auto">
251
- <h3 className="text-sm font-bold text-zinc-400 mb-2">Action Log</h3>
252
- {actions.length === 0 ? (
253
- <p className="text-zinc-500 text-xs">No actions yet</p>
254
- ) : (
255
- <div className="space-y-1">
256
- {actions.map((a, i) => (
257
- <div key={i} className="text-xs font-mono bg-zinc-800 rounded p-1">
258
- <span className="text-amber-400">{a.player}</span>: {JSON.stringify(a.action)}
259
- </div>
260
- ))}
261
- </div>
262
- )}
263
304
  </div>
264
305
  </div>
265
306
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlepartytime/dev-kit",
3
- "version": "1.6.2",
3
+ "version": "1.8.0",
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",