castle-web-cli 0.4.83 → 0.4.85

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.
Files changed (85) hide show
  1. package/dist/agent-failures.d.ts +17 -0
  2. package/dist/agent-failures.js +151 -0
  3. package/dist/agent.d.ts +26 -0
  4. package/dist/agent.js +317 -74
  5. package/dist/ide.js +35 -13
  6. package/dist/native/loop.js +35 -0
  7. package/dist/native/openrouter.d.ts +7 -0
  8. package/dist/native/openrouter.js +25 -1
  9. package/dist/native/types.d.ts +2 -0
  10. package/dist/native/types.js +0 -38
  11. package/dist/openrouter-catalog.d.ts +28 -0
  12. package/dist/openrouter-catalog.js +299 -0
  13. package/dist/shell/assets/{index-C9Zhmien.js → index-BJLaUTJE.js} +60 -58
  14. package/dist/shell/assets/{index-BMkQt27u.css → index-DonnH--m.css} +1 -1
  15. package/dist/shell/index.html +2 -2
  16. package/kits/physics-2d/.prettierrc +8 -0
  17. package/kits/physics-2d/CLAUDE.md +329 -0
  18. package/kits/physics-2d/behaviors/Camera.jsx +43 -0
  19. package/kits/physics-2d/behaviors/Collider.jsx +199 -0
  20. package/kits/physics-2d/behaviors/Goal.jsx +29 -0
  21. package/kits/physics-2d/behaviors/Layout.jsx +53 -0
  22. package/kits/physics-2d/behaviors/Sprite.jsx +352 -0
  23. package/kits/physics-2d/behaviors/tint.js +47 -0
  24. package/kits/physics-2d/blueprints/ball.scene +14 -0
  25. package/kits/physics-2d/blueprints/block.scene +12 -0
  26. package/kits/physics-2d/blueprints/cauldron.scene +18 -0
  27. package/kits/physics-2d/blueprints/crate.scene +14 -0
  28. package/kits/physics-2d/blueprints/goal.scene +12 -0
  29. package/kits/physics-2d/castle.json +13 -0
  30. package/kits/physics-2d/docs/pxart-format.md +377 -0
  31. package/kits/physics-2d/drawings/block.pxart +25 -0
  32. package/kits/physics-2d/drawings/cauldron.pxart +113 -0
  33. package/kits/physics-2d/editors/BlueprintLibrary.jsx +247 -0
  34. package/kits/physics-2d/editors/ErrorBoundary.jsx +59 -0
  35. package/kits/physics-2d/editors/PlayOnly.jsx +31 -0
  36. package/kits/physics-2d/editors/PxArtEditor.jsx +954 -0
  37. package/kits/physics-2d/editors/SceneEditor.jsx +1681 -0
  38. package/kits/physics-2d/editors/SelectionOverlay.jsx +909 -0
  39. package/kits/physics-2d/editors/SingleEditor.jsx +122 -0
  40. package/kits/physics-2d/editors/behaviorRegistry.js +30 -0
  41. package/kits/physics-2d/editors/editorHistory.js +157 -0
  42. package/kits/physics-2d/editors/inspectorSheet.js +13 -0
  43. package/kits/physics-2d/editors/pixelCanvas.js +11 -0
  44. package/kits/physics-2d/editors/pixelEditorChrome.jsx +74 -0
  45. package/kits/physics-2d/editors/pixelGeometry.js +140 -0
  46. package/kits/physics-2d/editors/pixelInspector.jsx +633 -0
  47. package/kits/physics-2d/editors/pxArtEditorModel.js +732 -0
  48. package/kits/physics-2d/editors/pxArtPlayback.js +92 -0
  49. package/kits/physics-2d/editors/pxArtTimeline.jsx +752 -0
  50. package/kits/physics-2d/editors/pxArtTimeline.module.css +506 -0
  51. package/kits/physics-2d/editors/pxArtTools.js +232 -0
  52. package/kits/physics-2d/editors/useArtboardFit.js +102 -0
  53. package/kits/physics-2d/engine/ScenePlayer.jsx +196 -0
  54. package/kits/physics-2d/engine/SceneUI.jsx +59 -0
  55. package/kits/physics-2d/engine/assets.js +15 -0
  56. package/kits/physics-2d/engine/autoInspector.jsx +70 -0
  57. package/kits/physics-2d/engine/blueprint.js +521 -0
  58. package/kits/physics-2d/engine/collider.js +196 -0
  59. package/kits/physics-2d/engine/files.js +117 -0
  60. package/kits/physics-2d/engine/liveReload.js +88 -0
  61. package/kits/physics-2d/engine/pxart.js +1032 -0
  62. package/kits/physics-2d/engine/pxartSmooth.js +222 -0
  63. package/kits/physics-2d/engine/scene.js +686 -0
  64. package/kits/physics-2d/engine/spriteGeometry.js +32 -0
  65. package/kits/physics-2d/engine/ui.jsx +688 -0
  66. package/kits/physics-2d/engine/ui.module.css +2287 -0
  67. package/kits/physics-2d/eslint.config.js +71 -0
  68. package/kits/physics-2d/index.html +24 -0
  69. package/kits/physics-2d/main.jsx +24 -0
  70. package/kits/physics-2d/package-lock.json +2706 -0
  71. package/kits/physics-2d/package.json +42 -0
  72. package/kits/physics-2d/physics/PhysicsSystem.js +290 -0
  73. package/kits/physics-2d/physics/behaviors/AnalogStick.jsx +101 -0
  74. package/kits/physics-2d/physics/behaviors/Draggable.jsx +79 -0
  75. package/kits/physics-2d/physics/behaviors/RigidBody.jsx +55 -0
  76. package/kits/physics-2d/physics/behaviors/Slingshot.jsx +118 -0
  77. package/kits/physics-2d/physics/controls.js +79 -0
  78. package/kits/physics-2d/physics/index.js +26 -0
  79. package/kits/physics-2d/physics/matterBridge.js +126 -0
  80. package/kits/physics-2d/pnpm-lock.yaml +1761 -0
  81. package/kits/physics-2d/scenes/main.scene +12 -0
  82. package/kits/physics-2d/scenes/sandbox.scene +13 -0
  83. package/kits/physics-2d/scripts/draw.mjs +121 -0
  84. package/kits/physics-2d/vite.config.js +1 -0
  85. package/package.json +1 -1
@@ -0,0 +1,2287 @@
1
+ @font-face {
2
+ font-family: 'Basteleur';
3
+ src:
4
+ url('/Basteleur-Bold.woff2') format('woff2'),
5
+ url('/Basteleur-Bold.woff') format('woff');
6
+ }
7
+
8
+ :global(:root) {
9
+ --castle-font-body:
10
+ baltomobile, Balto, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
11
+ sans-serif;
12
+ --castle-font-display: var(--castle-font-body);
13
+ --castle-font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
14
+ --castle-black: #111;
15
+ --castle-tap-highlight: #111;
16
+ --castle-panel: #080808;
17
+ --castle-panel-raised: #101010;
18
+ --castle-panel-sunken: #050505;
19
+ --castle-border: #444;
20
+ --castle-border-soft: #2a2a2a;
21
+ --castle-text: #fff;
22
+ --castle-text-dim: #888;
23
+ --castle-text-muted: #888;
24
+ --castle-inspector-bg: #0d0d0d;
25
+ --castle-inspector-text: #e6e6e6;
26
+ --castle-inspector-muted: #888;
27
+ --castle-inspector-border: #2a2a2a;
28
+ --castle-inspector-divider: #2a2a2a;
29
+ --castle-inspector-input-bg: #161616;
30
+ --castle-inspector-button-bg: #0d0d0d;
31
+ --castle-workspace-bg: #0a0a0a;
32
+ --castle-stage-workspace-bg: #161616;
33
+ --castle-sheet-bg: #0d0d0d;
34
+ --castle-card: #121213;
35
+ --castle-selected: #e6e6e6;
36
+ --castle-selected-ink: #111;
37
+ --castle-danger: #ff5d5d;
38
+ --castle-ok: #81e69b;
39
+ --castle-blue: #8db7ff;
40
+ /* Instance-override indicator: label/reset text + a dimmer input border. */
41
+ --castle-override: #b388ff;
42
+ --castle-override-border: #5b4a80;
43
+ --castle-radius: 4px;
44
+ /* Tools/hotbar strip height -- aligned to the inspector "+ Add behavior" row
45
+ (top+bottom pad + 1px borders + select padding + text line). The text line
46
+ uses `rem` (root-relative), NOT `em`: the hotbar inherits the 16px body font
47
+ while the inspector sets font-size:14px, so an `em` here resolved to 20px on
48
+ the hotbar but only 17.5px inside the inspector -- leaving the Add-behavior
49
+ row ~2.5px short of the hotbar. `rem` resolves to the same value in both. */
50
+ --castle-hotbar-row-pad-y: 12px;
51
+ --castle-hotbar-row-h: calc(var(--castle-hotbar-row-pad-y) * 2 + 2px + 10px + 1.25rem);
52
+ /* Full row height shared by the scene hotbar (.sceneTools + its grid track)
53
+ and the inspector "+ Add behavior" row, so both bottom borders land on the
54
+ same y. The +2px matches the Add-behavior row's outer box; it also gives the
55
+ 46px hotbar buttons room to sit fully inside the bar (no bottom clipping). */
56
+ --castle-hotbar-row-full: calc(var(--castle-hotbar-row-h) + 2px);
57
+ color-scheme: dark;
58
+ }
59
+
60
+ :global(*) {
61
+ box-sizing: border-box;
62
+ }
63
+
64
+ /* Hide scrollbars across the whole editor while keeping scroll functional. */
65
+ :global(*) {
66
+ scrollbar-width: none;
67
+ }
68
+
69
+ :global(*)::-webkit-scrollbar {
70
+ display: none;
71
+ }
72
+
73
+ :global(html),
74
+ :global(body),
75
+ :global(#root) {
76
+ width: 100%;
77
+ height: 100%;
78
+ margin: 0;
79
+ }
80
+
81
+ :global(body) {
82
+ overflow: hidden;
83
+ background: var(--castle-workspace-bg);
84
+ color: var(--castle-inspector-text);
85
+ font-family: var(--castle-font-body);
86
+ -webkit-font-smoothing: antialiased;
87
+ }
88
+
89
+ :global(button),
90
+ :global(input),
91
+ :global(select),
92
+ :global(textarea) {
93
+ font: inherit;
94
+ }
95
+
96
+ :global(button),
97
+ :global(input),
98
+ :global(select),
99
+ :global(textarea),
100
+ :global([tabindex]) {
101
+ outline: none;
102
+ }
103
+
104
+ :global(*:focus),
105
+ :global(*:focus-visible) {
106
+ outline: none;
107
+ }
108
+
109
+ /* Editor-root is a vertical stack: full-width header row, then the
110
+ `[file list | workspace]` grid row. */
111
+ .editorRoot {
112
+ width: 100%;
113
+ height: 100%;
114
+ display: flex;
115
+ flex-direction: column;
116
+ background: var(--castle-workspace-bg);
117
+ color: var(--castle-inspector-text);
118
+ }
119
+
120
+ /* Portal target for the active editor's header. `display: contents` lets the
121
+ portaled `.editorHeader` be a direct flex child of `.editorRoot`. */
122
+ .editorHeaderHost {
123
+ display: contents;
124
+ }
125
+
126
+ .appShell {
127
+ flex: 1 1 0;
128
+ min-height: 0;
129
+ display: grid;
130
+ grid-template-columns: 200px 1fr;
131
+ background: var(--castle-workspace-bg);
132
+ color: var(--castle-inspector-text);
133
+ }
134
+
135
+ /* Desktop: the files toggle collapses the file-list column. (Never set in
136
+ compact mode, where the file list is a bottom sheet instead.) */
137
+ .editorRoot.filesHidden .appShell {
138
+ grid-template-columns: 1fr;
139
+ }
140
+
141
+ .editorRoot.filesHidden .fileBrowser {
142
+ display: none;
143
+ }
144
+
145
+ .fileBrowser {
146
+ min-width: 0;
147
+ border-right: 1px solid var(--castle-inspector-divider);
148
+ background: var(--castle-workspace-bg);
149
+ display: flex;
150
+ flex-direction: column;
151
+ }
152
+
153
+ /* Bare file browser for the dockview "Files" panel: no sidebar border or
154
+ mobile-sheet chrome, just a full-height column so the tree fills the panel
155
+ and the empty area below the rows still catches the blank-area menu. */
156
+ .fileBrowserBare {
157
+ min-width: 0;
158
+ height: 100%;
159
+ display: flex;
160
+ flex-direction: column;
161
+ }
162
+
163
+ .editorHeader {
164
+ height: 48px;
165
+ flex: 0 0 48px;
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 8px;
169
+ padding: 10px 12px;
170
+ border-bottom: 1px solid var(--castle-inspector-divider);
171
+ background: var(--castle-workspace-bg);
172
+ }
173
+
174
+ .editorTitle {
175
+ font-family: var(--castle-font-display);
176
+ letter-spacing: 0.01em;
177
+ font-size: 14px;
178
+ line-height: 1;
179
+ }
180
+
181
+ .muted {
182
+ color: var(--castle-inspector-muted);
183
+ font-size: 12px;
184
+ }
185
+
186
+ .fileTree {
187
+ flex: 1 1 0;
188
+ min-height: 0;
189
+ padding-top: 5px;
190
+ overflow-y: auto;
191
+ }
192
+
193
+ .fileBranch {
194
+ min-width: 0;
195
+ }
196
+
197
+ /* File-list rows ride the workspace "notch" ladder: default sits on the
198
+ workspace bg (#0a0a0a, transparent), hover lifts one notch to the sheet bg
199
+ (#0d0d0d), and the selected row lifts one notch further to panel-raised
200
+ (#101010). Text follows the same idea: unselected rows are the SAME light
201
+ text token as the selected/hover row, just faded (reduced opacity) so they
202
+ stay clearly legible on the dark panel; the selected row is the full
203
+ standard light text. */
204
+ .fileRow,
205
+ .fileDirRow {
206
+ width: 100%;
207
+ border: 0;
208
+ border-radius: var(--castle-radius);
209
+ background: transparent;
210
+ /* Plain opaque gray, NOT color-mix(...transparent): Safari fails to paint a
211
+ semi-transparent color-mix result until a repaint is forced (opening
212
+ devtools makes it appear), leaving rows invisible at rest. This solid value
213
+ approximates the light text token faded over the dark panel. */
214
+ color: #a8a8a8;
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: flex-start;
218
+ gap: 6px;
219
+ padding: 5px 9px 5px calc(9px + var(--file-depth, 0) * 20px);
220
+ text-align: left;
221
+ cursor: pointer;
222
+ font-size: 14px;
223
+ }
224
+
225
+ /* Folders read darker than files at rest so the tree's structure (containers
226
+ vs. leaves) is legible at a glance. Hover/selected still lift both to the full
227
+ light text via the higher-specificity rules below. */
228
+ .fileDirRow {
229
+ color: #777;
230
+ }
231
+
232
+ .fileRow:hover,
233
+ .fileDirRow:hover {
234
+ background: var(--castle-sheet-bg);
235
+ color: var(--castle-inspector-text);
236
+ }
237
+
238
+ /* Compound `.fileRow.fileRowSelected` so the selected look (full text +
239
+ one-notch-lighter bg) outranks `.fileRow:hover` on specificity, not source
240
+ order -- the selected row stays put even while hovered. */
241
+ .fileRow.fileRowSelected,
242
+ .fileRow.fileRowSelected:hover {
243
+ background: var(--castle-panel-raised);
244
+ color: var(--castle-inspector-text);
245
+ }
246
+
247
+ /* The disclosure caret only appears on folder rows; keep it a shade darker than
248
+ the folder name (#666 vs #777) at rest, then lift it to the full light text on
249
+ hover alongside the name. Compound `.fileIcon.fileDisclosure` (0,2,0) is
250
+ required to beat the later `.fileIcon` muted-color rule (0,1,0); a bare
251
+ `.fileDisclosure` ties on specificity and loses on source order. */
252
+ .fileIcon.fileDisclosure {
253
+ color: #666;
254
+ }
255
+
256
+ .fileDirRow:hover .fileIcon.fileDisclosure {
257
+ color: inherit;
258
+ }
259
+
260
+ .fileDisclosure svg {
261
+ width: 10px;
262
+ height: 10px;
263
+ }
264
+
265
+ /* Icon + name group: its own gap controls icon<->name spacing, independent of
266
+ the row's disclosure<->label gap. */
267
+ .fileLabel {
268
+ display: inline-flex;
269
+ align-items: center;
270
+ gap: 6px;
271
+ min-width: 0;
272
+ flex: 1 1 auto;
273
+ }
274
+
275
+ /* Inline rename field. Constrained to the row width (flex:1 + min-width:0 +
276
+ border-box) so a long name never pushes the file browser into horizontal
277
+ overflow. */
278
+ .fileRenameInput {
279
+ flex: 1 1 auto;
280
+ min-width: 0;
281
+ width: 100%;
282
+ max-width: 100%;
283
+ box-sizing: border-box;
284
+ padding: 1px 4px;
285
+ border: 1px solid var(--castle-border);
286
+ border-radius: 5px;
287
+ background: var(--castle-panel-raised);
288
+ color: var(--castle-text);
289
+ font: inherit;
290
+ font-size: 13px;
291
+ }
292
+
293
+ .fileRenameInput:focus {
294
+ outline: none;
295
+ border-color: var(--castle-text-dim);
296
+ }
297
+
298
+ /* Type-based file icon: muted so the file name stays the visual emphasis. */
299
+ .fileIcon {
300
+ width: 14px;
301
+ flex: 0 0 14px;
302
+ display: inline-flex;
303
+ align-items: center;
304
+ justify-content: center;
305
+ color: var(--castle-inspector-muted);
306
+ font-size: 14px;
307
+ }
308
+
309
+ .mainEditor {
310
+ min-width: 0;
311
+ min-height: 0;
312
+ width: 100%;
313
+ height: 100%;
314
+ display: flex;
315
+ flex-direction: column;
316
+ background: var(--castle-sheet-bg);
317
+ }
318
+
319
+ .editorHeader {
320
+ justify-content: space-between;
321
+ }
322
+
323
+ .editorHeaderLeft,
324
+ .editorHeaderRight,
325
+ .toolbar {
326
+ display: flex;
327
+ align-items: center;
328
+ gap: 8px;
329
+ min-width: 0;
330
+ }
331
+
332
+ .editorBody {
333
+ min-height: 0;
334
+ flex: 1;
335
+ display: flex;
336
+ overflow: hidden;
337
+ container-type: inline-size;
338
+ container-name: editor;
339
+ }
340
+
341
+ .sceneWorkspace {
342
+ flex: 1;
343
+ min-width: 0;
344
+ min-height: 0;
345
+ display: grid;
346
+ grid-template-columns: minmax(0, 1fr) 280px;
347
+ grid-template-rows: var(--castle-hotbar-row-full) minmax(0, 1fr);
348
+ }
349
+
350
+ .sceneTools {
351
+ grid-column: 1;
352
+ grid-row: 1;
353
+ height: var(--castle-hotbar-row-full);
354
+ min-width: 0;
355
+ display: flex;
356
+ align-items: center;
357
+ padding: 0;
358
+ border-bottom: 1px solid var(--castle-inspector-divider);
359
+ background: var(--castle-workspace-bg);
360
+ }
361
+
362
+ /* Blueprint hotbar -- left-aligned djinn-style stamp palette. Today it holds a
363
+ single "+" Add-actor control; drag-and-drop blueprint slots land here later. */
364
+ .bpHotbar {
365
+ display: flex;
366
+ flex-direction: row;
367
+ align-items: center;
368
+ gap: 6px;
369
+ width: 100%;
370
+ height: 100%;
371
+ padding: 0 6px;
372
+ overflow-x: auto;
373
+ overflow-y: hidden;
374
+ }
375
+
376
+ .bpSlot,
377
+ .bpActionSlot {
378
+ position: relative;
379
+ width: 46px;
380
+ height: 46px;
381
+ /* Never shrink in the flex hotbar -- keep the square 46x46 and let the
382
+ hotbar scroll (overflow-x: auto) when the panel gets narrow, instead of
383
+ squishing slots (the add button collapsed since its icon is only 14px). */
384
+ flex: 0 0 46px;
385
+ display: inline-flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+ padding: 0;
389
+ border: 1px solid var(--castle-inspector-border);
390
+ border-radius: var(--castle-radius);
391
+ background: var(--castle-inspector-button-bg);
392
+ color: var(--castle-inspector-text);
393
+ cursor: pointer;
394
+ font-size: 14px;
395
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
396
+ }
397
+
398
+ .bpSlotBadge {
399
+ position: absolute;
400
+ right: -4px;
401
+ bottom: -4px;
402
+ min-width: 15px;
403
+ height: 15px;
404
+ padding: 0 3px;
405
+ border-radius: 999px;
406
+ background: var(--castle-selected);
407
+ color: var(--castle-selected-ink);
408
+ font-size: 10px;
409
+ font-weight: 700;
410
+ line-height: 15px;
411
+ text-align: center;
412
+ }
413
+
414
+ .bpSlot:hover,
415
+ .bpActionSlot:hover {
416
+ background: var(--castle-inspector-input-bg);
417
+ }
418
+
419
+ .bpSlotSelected,
420
+ .bpSlotSelected:hover {
421
+ border-color: var(--castle-selected);
422
+ box-shadow: 0 0 0 1px var(--castle-selected);
423
+ background: var(--castle-inspector-input-bg);
424
+ }
425
+
426
+ /* The blueprint of the currently-selected actor: distinct from a direct
427
+ blueprint selection -- just a white border, no thickening/glow. */
428
+ .bpSlotInstance,
429
+ .bpSlotInstance:hover {
430
+ border-color: #fff;
431
+ }
432
+
433
+ .bpSlot:disabled,
434
+ .bpActionSlot:disabled {
435
+ cursor: default;
436
+ opacity: 0.35;
437
+ }
438
+
439
+ .bpSlot svg,
440
+ .bpActionSlot svg {
441
+ width: 14px;
442
+ height: 14px;
443
+ flex: 0 0 auto;
444
+ }
445
+
446
+ .bpSlotThumb,
447
+ .bpSlotFallback {
448
+ width: 36px;
449
+ height: 36px;
450
+ flex: 0 0 36px;
451
+ border-radius: 3px;
452
+ }
453
+
454
+ .bpSlotThumb {
455
+ image-rendering: pixelated;
456
+ background: var(--castle-card);
457
+ object-fit: contain;
458
+ }
459
+
460
+ .bpSlotFallback {
461
+ display: inline-flex;
462
+ align-items: center;
463
+ justify-content: center;
464
+ color: var(--castle-inspector-muted);
465
+ }
466
+
467
+ .bpDragPreview {
468
+ position: fixed;
469
+ z-index: 1000;
470
+ pointer-events: none;
471
+ display: flex;
472
+ align-items: center;
473
+ justify-content: center;
474
+ border: 1px dashed var(--castle-text);
475
+ background: rgb(230 230 230 / 0.08);
476
+ opacity: 0.7;
477
+ }
478
+
479
+ .bpDragPreviewOverStage {
480
+ opacity: 0.9;
481
+ }
482
+
483
+ .bpPreviewThumb {
484
+ width: 100%;
485
+ height: 100%;
486
+ flex: 1 1 auto;
487
+ }
488
+
489
+ .confirmBackdrop {
490
+ position: fixed;
491
+ inset: 0;
492
+ z-index: 2000;
493
+ display: flex;
494
+ align-items: center;
495
+ justify-content: center;
496
+ background: rgba(0, 0, 0, 0.55);
497
+ }
498
+
499
+ .confirmDialog {
500
+ width: min(340px, calc(100vw - 32px));
501
+ padding: 18px;
502
+ border: 1px solid var(--castle-border);
503
+ border-radius: 10px;
504
+ background: var(--castle-panel-raised);
505
+ color: var(--castle-text);
506
+ box-shadow: 0 20px 48px rgba(0, 0, 0, 0.5);
507
+ }
508
+
509
+ .confirmTitle {
510
+ font-size: 15px;
511
+ font-weight: 650;
512
+ margin-bottom: 8px;
513
+ }
514
+
515
+ .confirmMessage {
516
+ font-size: 13px;
517
+ color: var(--castle-text-dim);
518
+ line-height: 1.45;
519
+ margin-bottom: 16px;
520
+ }
521
+
522
+ .confirmActions {
523
+ display: flex;
524
+ justify-content: flex-end;
525
+ gap: 8px;
526
+ }
527
+
528
+ .blueprintBadge {
529
+ display: inline-flex;
530
+ align-items: center;
531
+ padding: 2px 7px;
532
+ border-radius: 999px;
533
+ border: 1px solid var(--castle-inspector-border);
534
+ background: var(--castle-inspector-input-bg);
535
+ color: var(--castle-inspector-muted);
536
+ font-size: 11px;
537
+ font-weight: 600;
538
+ letter-spacing: 0.02em;
539
+ text-transform: uppercase;
540
+ }
541
+
542
+ .editorHeaderTitleRow {
543
+ display: inline-flex;
544
+ align-items: center;
545
+ gap: 8px;
546
+ }
547
+
548
+ .instanceHint {
549
+ display: flex;
550
+ align-items: center;
551
+ justify-content: space-between;
552
+ gap: 8px;
553
+ padding: 10px 16px;
554
+ border-bottom: 1px solid var(--castle-inspector-divider);
555
+ }
556
+
557
+ .instanceHintLabel {
558
+ color: var(--castle-inspector-muted);
559
+ font-size: 12px;
560
+ }
561
+
562
+ .instanceHeader {
563
+ display: flex;
564
+ flex-direction: column;
565
+ align-items: stretch;
566
+ gap: 12px;
567
+ padding: 14px 16px;
568
+ border-bottom: 1px solid var(--castle-inspector-divider);
569
+ }
570
+
571
+ .instanceHeaderName {
572
+ display: flex;
573
+ align-items: center;
574
+ gap: 8px;
575
+ min-width: 0;
576
+ }
577
+
578
+ .instanceHeaderNameText {
579
+ min-width: 0;
580
+ overflow: hidden;
581
+ text-overflow: ellipsis;
582
+ white-space: nowrap;
583
+ color: var(--castle-inspector-text);
584
+ font-size: 14px;
585
+ font-weight: 400;
586
+ }
587
+
588
+ .instanceHeaderNameStrong {
589
+ font-weight: 600;
590
+ }
591
+
592
+ .instanceHeaderNameCol {
593
+ display: flex;
594
+ flex-direction: column;
595
+ gap: 2px;
596
+ min-width: 0;
597
+ }
598
+
599
+ .instanceHeaderId {
600
+ color: var(--castle-inspector-muted);
601
+ font-size: 12px;
602
+ overflow: hidden;
603
+ text-overflow: ellipsis;
604
+ white-space: nowrap;
605
+ }
606
+
607
+ .revealOverridesButton {
608
+ display: flex;
609
+ align-items: center;
610
+ justify-content: flex-start;
611
+ gap: 8px;
612
+ width: 100%;
613
+ padding: 14px 16px;
614
+ border: 0;
615
+ border-bottom: 1px solid var(--castle-inspector-divider);
616
+ background: transparent;
617
+ color: var(--castle-inspector-muted);
618
+ font-size: 13px;
619
+ text-align: left;
620
+ cursor: pointer;
621
+ }
622
+
623
+ .revealOverridesButton:hover {
624
+ background: var(--castle-inspector-input-bg);
625
+ color: var(--castle-inspector-text);
626
+ }
627
+
628
+ .instanceForkButton {
629
+ display: inline-flex;
630
+ align-items: center;
631
+ justify-content: center;
632
+ gap: 6px;
633
+ align-self: flex-start;
634
+ border: 1px solid var(--castle-inspector-border);
635
+ border-radius: 6px;
636
+ background: var(--castle-inspector-input-bg);
637
+ color: inherit;
638
+ font-size: 11px;
639
+ padding: 5px 9px;
640
+ cursor: pointer;
641
+ white-space: nowrap;
642
+ }
643
+
644
+ .instanceForkButton:hover {
645
+ background: var(--castle-inspector-border);
646
+ }
647
+
648
+ /* Editable blueprint title: reads as a heading until hovered/focused, where a
649
+ border + input background reveal that it's editable. */
650
+ .blueprintNameInput {
651
+ flex: 1;
652
+ min-width: 0;
653
+ border: 1px solid transparent;
654
+ border-radius: var(--castle-radius);
655
+ background: transparent;
656
+ color: var(--castle-inspector-text);
657
+ font-size: 14px;
658
+ font-weight: 600;
659
+ padding: 4px 6px;
660
+ margin: -4px -6px;
661
+ outline: 0;
662
+ }
663
+
664
+ .blueprintNameInput:hover {
665
+ border-color: var(--castle-inspector-border);
666
+ }
667
+
668
+ .blueprintNameInput:focus {
669
+ border-color: var(--castle-inspector-border);
670
+ background: var(--castle-inspector-input-bg);
671
+ }
672
+
673
+ .stageWrap {
674
+ grid-column: 1;
675
+ grid-row: 2;
676
+ min-width: 0;
677
+ min-height: 0;
678
+ display: flex;
679
+ align-items: center;
680
+ justify-content: center;
681
+ padding: 20px;
682
+ background: var(--castle-stage-workspace-bg);
683
+ overflow: hidden;
684
+ }
685
+
686
+ .stageCard {
687
+ position: relative;
688
+ width: min(52vh, 440px);
689
+ aspect-ratio: 5 / 7;
690
+ max-width: 100%;
691
+ overflow: visible;
692
+ }
693
+
694
+ .stageCardClip {
695
+ position: absolute;
696
+ inset: 0;
697
+ background: var(--castle-card);
698
+ border-radius: 12px;
699
+ overflow: hidden;
700
+ box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2);
701
+ }
702
+
703
+ .stageCardEditSurface {
704
+ overflow: visible;
705
+ }
706
+
707
+ .stageCanvas {
708
+ width: 100%;
709
+ height: 100%;
710
+ display: block;
711
+ touch-action: none;
712
+ /* tabIndex=-1 (keyboard-focus capture on pointer-down) would otherwise draw a
713
+ focus ring; focus here is programmatic only, never via keyboard. */
714
+ outline: none;
715
+ }
716
+
717
+ .stageCanvasEdit {
718
+ position: absolute;
719
+ left: -100%;
720
+ top: -100%;
721
+ width: 300%;
722
+ height: 300%;
723
+ }
724
+
725
+ /* Game-time UI overlay -- sits exactly over the canvas and clips to the card.
726
+ Behavior `ui` output lands in `.sceneUiLayer`, which is sized in card units
727
+ and scaled onto the canvas box. */
728
+ .sceneUiRoot {
729
+ position: absolute;
730
+ inset: 0;
731
+ overflow: hidden;
732
+ pointer-events: none;
733
+ }
734
+
735
+ .sceneUiLayer {
736
+ position: absolute;
737
+ left: 0;
738
+ top: 0;
739
+ transform-origin: 0 0;
740
+ }
741
+
742
+ /* On-canvas selection toolbar ("selection handles"). Two stacked layers cover
743
+ the canvas box (sized by a ResizeObserver, like .sceneUiRoot):
744
+ - .selChromeLayer is the card-unit (500x700) layer, scaled onto the canvas
745
+ and translated by -camera so the dashed box / dots / pivot track the edit
746
+ camera exactly like the canvas draw path. Chrome lives in card units, so it
747
+ scales gently with the responsive card.
748
+ - .selButtonLayer is an un-scaled px layer; the floating buttons are placed
749
+ in projected px (world -> screen) so they stay crisp and fixed-size with
750
+ fixed px gaps regardless of card scale. */
751
+ .selOverlayRoot {
752
+ position: absolute;
753
+ inset: 0;
754
+ overflow: visible;
755
+ pointer-events: none;
756
+ animation: selOverlayIn 150ms ease;
757
+ }
758
+
759
+ /* Fade only -- no scale. Scaling the full-card overlay about its center made
760
+ an off-center selection's handles slide inward-from-center on every mount
761
+ (most visible re-selecting a different actor after a deselect, where the
762
+ overlay remounts), reading as "animating in from the old position". */
763
+ @keyframes selOverlayIn {
764
+ from {
765
+ opacity: 0;
766
+ }
767
+ to {
768
+ opacity: 1;
769
+ }
770
+ }
771
+
772
+ .selChromeLayer {
773
+ position: absolute;
774
+ left: 0;
775
+ top: 0;
776
+ transform-origin: 0 0;
777
+ pointer-events: none;
778
+ }
779
+
780
+ .selChromeWorld {
781
+ position: absolute;
782
+ left: 0;
783
+ top: 0;
784
+ }
785
+
786
+ .selColliderBox {
787
+ position: absolute;
788
+ box-sizing: border-box;
789
+ border: 2px dotted rgb(141 183 255 / 0.9);
790
+ background: rgb(141 183 255 / 0.14);
791
+ border-radius: 2px;
792
+ pointer-events: none;
793
+ }
794
+
795
+ .selColliderPickup {
796
+ border-color: rgb(255 225 122 / 0.9);
797
+ background: rgb(255 225 122 / 0.14);
798
+ }
799
+
800
+ .selBox {
801
+ position: absolute;
802
+ box-sizing: border-box;
803
+ border: 2px solid var(--castle-text);
804
+ border-radius: 3px;
805
+ opacity: 0.85;
806
+ pointer-events: none;
807
+ }
808
+
809
+ /* Connector stem from the box's right-center edge to the rotate handle. Lives
810
+ in the un-scaled px layer and is rotated about its left-center anchor, so its
811
+ thickness matches the selection box border at any zoom. */
812
+ .selStem {
813
+ position: absolute;
814
+ height: 2px;
815
+ background: var(--castle-text);
816
+ opacity: 0.85;
817
+ transform-origin: left center;
818
+ pointer-events: none;
819
+ }
820
+
821
+ .selScaleHandle {
822
+ position: absolute;
823
+ border: 0;
824
+ padding: 0;
825
+ background: var(--castle-text);
826
+ border: 2px solid var(--castle-selected-ink);
827
+ transform: translate(-50%, -50%);
828
+ pointer-events: auto;
829
+ touch-action: none;
830
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
831
+ }
832
+
833
+ .selScaleCorner {
834
+ width: 11px;
835
+ height: 11px;
836
+ border-radius: 999px;
837
+ }
838
+
839
+ .selScaleEdge {
840
+ border-radius: 999px;
841
+ }
842
+
843
+ .selScaleEdge[data-handle='n'],
844
+ .selScaleEdge[data-handle='s'] {
845
+ width: 28px;
846
+ height: 9px;
847
+ }
848
+
849
+ .selScaleEdge[data-handle='e'],
850
+ .selScaleEdge[data-handle='w'] {
851
+ width: 9px;
852
+ height: 28px;
853
+ }
854
+
855
+ .selPivot {
856
+ position: absolute;
857
+ width: 13px;
858
+ height: 13px;
859
+ border-radius: 999px;
860
+ border: 2px solid var(--castle-text);
861
+ background: transparent;
862
+ opacity: 0.85;
863
+ transform: translate(-50%, -50%);
864
+ }
865
+
866
+ .selButtonLayer {
867
+ position: absolute;
868
+ inset: 0;
869
+ pointer-events: none;
870
+ }
871
+
872
+ .selBtnWrap {
873
+ position: absolute;
874
+ transform: translate(-50%, -50%);
875
+ pointer-events: auto;
876
+ }
877
+
878
+ .selBtnGroup {
879
+ display: flex;
880
+ gap: 8px;
881
+ }
882
+
883
+ .selArrangeWrap {
884
+ position: relative;
885
+ }
886
+
887
+ .selArrangeMenu {
888
+ position: absolute;
889
+ left: 50%;
890
+ top: calc(100% + 8px);
891
+ min-width: 150px;
892
+ padding: 5px;
893
+ border: 1px solid var(--castle-border);
894
+ border-radius: 10px;
895
+ background: var(--castle-panel-raised);
896
+ color: var(--castle-text);
897
+ box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
898
+ transform: translateX(-50%);
899
+ z-index: 2;
900
+ }
901
+
902
+ .selArrangeItem {
903
+ width: 100%;
904
+ border: 0;
905
+ border-radius: 7px;
906
+ padding: 7px 9px;
907
+ background: transparent;
908
+ color: inherit;
909
+ cursor: pointer;
910
+ font: inherit;
911
+ font-size: 12px;
912
+ text-align: left;
913
+ white-space: nowrap;
914
+ }
915
+
916
+ .selArrangeItem:hover {
917
+ background: var(--castle-panel);
918
+ }
919
+
920
+ .selArrangeItemDisabled,
921
+ .selArrangeItemDisabled:hover {
922
+ opacity: 0.4;
923
+ background: transparent;
924
+ cursor: default;
925
+ }
926
+
927
+ .selBtn {
928
+ width: 32px;
929
+ height: 32px;
930
+ border-radius: 999px;
931
+ border: 1px solid var(--castle-border);
932
+ background: var(--castle-panel-raised);
933
+ color: var(--castle-text);
934
+ display: inline-flex;
935
+ align-items: center;
936
+ justify-content: center;
937
+ padding: 0;
938
+ font-size: 14px;
939
+ cursor: pointer;
940
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
941
+ touch-action: none;
942
+ }
943
+
944
+ .selBtn:hover {
945
+ background: var(--castle-panel);
946
+ border-color: var(--castle-text-dim);
947
+ }
948
+
949
+ .selBtn svg {
950
+ width: 1em;
951
+ height: 1em;
952
+ }
953
+
954
+ .selBtnGrab {
955
+ cursor: grab;
956
+ }
957
+
958
+ .selBtnRotate {
959
+ cursor: grab;
960
+ }
961
+
962
+ /* Touch targets: larger hit area on coarse pointers / compact layout. */
963
+ @media (pointer: coarse), (max-width: 600px) {
964
+ .selBtn {
965
+ width: 44px;
966
+ height: 44px;
967
+ font-size: 18px;
968
+ }
969
+
970
+ .selBtnGroup {
971
+ gap: 12px;
972
+ }
973
+
974
+ .selScaleCorner {
975
+ width: 17px;
976
+ height: 17px;
977
+ }
978
+
979
+ .selScaleEdge[data-handle='n'],
980
+ .selScaleEdge[data-handle='s'] {
981
+ width: 38px;
982
+ height: 15px;
983
+ }
984
+
985
+ .selScaleEdge[data-handle='e'],
986
+ .selScaleEdge[data-handle='w'] {
987
+ width: 15px;
988
+ height: 38px;
989
+ }
990
+ }
991
+
992
+ .inspector {
993
+ grid-column: 2;
994
+ grid-row: 1 / span 2;
995
+ min-width: 0;
996
+ border-left: 1px solid var(--castle-inspector-divider);
997
+ background: var(--castle-workspace-bg);
998
+ color: var(--castle-inspector-text);
999
+ font-size: 14px;
1000
+ overflow: auto;
1001
+ padding: 0;
1002
+ }
1003
+
1004
+ .panel {
1005
+ border: 0;
1006
+ border-bottom: 1px solid var(--castle-inspector-divider);
1007
+ border-radius: 0;
1008
+ background: var(--castle-inspector-bg);
1009
+ color: var(--castle-inspector-text);
1010
+ font-size: 14px;
1011
+ margin: 0;
1012
+ overflow: visible;
1013
+ box-shadow: none;
1014
+ }
1015
+
1016
+ .panel:last-child {
1017
+ border-bottom: 0;
1018
+ }
1019
+
1020
+ .panelHeader {
1021
+ display: flex;
1022
+ align-items: center;
1023
+ justify-content: space-between;
1024
+ gap: 12px;
1025
+ padding: 14px 16px 6px;
1026
+ border-bottom: 0;
1027
+ color: var(--castle-inspector-text);
1028
+ font-size: 14px;
1029
+ font-weight: 650;
1030
+ }
1031
+
1032
+ .panelTitleOverridden {
1033
+ color: var(--castle-override);
1034
+ }
1035
+
1036
+ .panelBody {
1037
+ padding: 8px 16px 4px;
1038
+ }
1039
+
1040
+ .fieldRow {
1041
+ display: grid;
1042
+ grid-template-columns: minmax(82px, 0.5fr) minmax(0, 1fr);
1043
+ align-items: center;
1044
+ gap: 12px;
1045
+ padding-bottom: 12px;
1046
+ margin-bottom: 0;
1047
+ min-height: 28px;
1048
+ }
1049
+
1050
+ .fieldRow:last-child {
1051
+ margin-bottom: 0;
1052
+ }
1053
+
1054
+ .fieldLabel {
1055
+ color: var(--castle-inspector-text);
1056
+ font-size: 14px;
1057
+ }
1058
+
1059
+ /* Instance override: purple label + input border, plus a "Default: X [Reset]"
1060
+ sub-line rendered below the control (see FieldRow). */
1061
+ .fieldLabelOverridden {
1062
+ color: var(--castle-override);
1063
+ }
1064
+
1065
+ .fieldRowOverridden .input,
1066
+ .fieldRowOverridden .select,
1067
+ .fieldRowOverridden .colorInput {
1068
+ border-color: var(--castle-override-border);
1069
+ }
1070
+
1071
+ .fieldOverride {
1072
+ padding-bottom: 12px;
1073
+ }
1074
+
1075
+ .fieldOverride .fieldRow {
1076
+ padding-bottom: 4px;
1077
+ }
1078
+
1079
+ .fieldDefault {
1080
+ display: grid;
1081
+ grid-template-columns: minmax(82px, 0.5fr) minmax(0, 1fr);
1082
+ gap: 12px;
1083
+ }
1084
+
1085
+ .fieldDefaultInner {
1086
+ grid-column: 2;
1087
+ display: flex;
1088
+ align-items: center;
1089
+ gap: 8px;
1090
+ min-width: 0;
1091
+ font-size: 12px;
1092
+ color: var(--castle-inspector-muted);
1093
+ }
1094
+
1095
+ .fieldDefaultInner > span {
1096
+ min-width: 0;
1097
+ overflow: hidden;
1098
+ text-overflow: ellipsis;
1099
+ white-space: nowrap;
1100
+ }
1101
+
1102
+ .fieldResetBtn {
1103
+ margin-left: auto;
1104
+ border: 0;
1105
+ padding: 0;
1106
+ background: transparent;
1107
+ color: var(--castle-override);
1108
+ font-size: 12px;
1109
+ cursor: pointer;
1110
+ }
1111
+
1112
+ .fieldResetBtn:hover {
1113
+ color: var(--castle-inspector-text);
1114
+ }
1115
+
1116
+ .input,
1117
+ .select,
1118
+ .textarea {
1119
+ width: 100%;
1120
+ border: 1px solid var(--castle-inspector-border);
1121
+ border-radius: var(--castle-radius);
1122
+ background: var(--castle-inspector-input-bg);
1123
+ color: var(--castle-inspector-text);
1124
+ padding: 5px 8px;
1125
+ outline: 0;
1126
+ }
1127
+
1128
+ .select {
1129
+ appearance: none;
1130
+ -webkit-appearance: none;
1131
+ padding-right: 26px;
1132
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' fill='none' stroke='%23888888' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1133
+ background-repeat: no-repeat;
1134
+ background-position: right 8px center;
1135
+ text-overflow: ellipsis;
1136
+ }
1137
+
1138
+ .numberField {
1139
+ position: relative;
1140
+ width: 100%;
1141
+ }
1142
+
1143
+ .numberInput {
1144
+ padding-right: 22px;
1145
+ appearance: textfield;
1146
+ -moz-appearance: textfield;
1147
+ }
1148
+
1149
+ .numberInput::-webkit-inner-spin-button,
1150
+ .numberInput::-webkit-outer-spin-button {
1151
+ -webkit-appearance: none;
1152
+ appearance: none;
1153
+ margin: 0;
1154
+ }
1155
+
1156
+ .numberSteppers {
1157
+ position: absolute;
1158
+ top: 1px;
1159
+ right: 1px;
1160
+ bottom: 1px;
1161
+ width: 18px;
1162
+ display: flex;
1163
+ flex-direction: column;
1164
+ border-left: 1px solid var(--castle-inspector-divider);
1165
+ opacity: 0;
1166
+ pointer-events: none;
1167
+ transition: opacity 0.1s;
1168
+ }
1169
+
1170
+ .numberField:hover .numberSteppers,
1171
+ .numberField:focus-within .numberSteppers {
1172
+ opacity: 1;
1173
+ pointer-events: auto;
1174
+ }
1175
+
1176
+ .numberStepper {
1177
+ flex: 1;
1178
+ display: flex;
1179
+ align-items: center;
1180
+ justify-content: center;
1181
+ border: 0;
1182
+ padding: 0;
1183
+ background: transparent;
1184
+ color: var(--castle-inspector-muted);
1185
+ cursor: pointer;
1186
+ font-size: 14px;
1187
+ line-height: 1;
1188
+ }
1189
+
1190
+ .numberStepper:first-child {
1191
+ border-bottom: 1px solid var(--castle-inspector-divider);
1192
+ }
1193
+
1194
+ .numberStepper:hover {
1195
+ background: var(--castle-inspector-input-bg);
1196
+ color: var(--castle-inspector-text);
1197
+ }
1198
+
1199
+ .numberStepper:disabled {
1200
+ opacity: 0.3;
1201
+ cursor: default;
1202
+ background: transparent;
1203
+ }
1204
+
1205
+ .colorInput {
1206
+ width: 100%;
1207
+ height: 28px;
1208
+ border: 1px solid var(--castle-inspector-border);
1209
+ border-radius: var(--castle-radius);
1210
+ background: var(--castle-inspector-input-bg);
1211
+ padding: 2px;
1212
+ outline: 0;
1213
+ cursor: pointer;
1214
+ }
1215
+
1216
+ .toggle {
1217
+ width: 44px;
1218
+ height: 24px;
1219
+ border: 1px solid var(--castle-inspector-border);
1220
+ border-radius: 999px;
1221
+ background: var(--castle-inspector-input-bg);
1222
+ padding: 2px;
1223
+ cursor: pointer;
1224
+ display: flex;
1225
+ align-items: center;
1226
+ justify-content: flex-start;
1227
+ box-shadow: none;
1228
+ }
1229
+
1230
+ .toggleOn {
1231
+ background: var(--castle-black);
1232
+ justify-content: flex-end;
1233
+ }
1234
+
1235
+ .toggleKnob {
1236
+ width: 18px;
1237
+ height: 18px;
1238
+ border-radius: 999px;
1239
+ background: var(--castle-inspector-muted);
1240
+ }
1241
+
1242
+ .toggleOn .toggleKnob {
1243
+ background: var(--castle-text);
1244
+ }
1245
+
1246
+ .textarea {
1247
+ min-height: 0;
1248
+ resize: none;
1249
+ font-family: var(--castle-font-mono);
1250
+ font-size: 14px;
1251
+ line-height: 1.45;
1252
+ }
1253
+
1254
+ /* Shared chrome for the inspector "Button" and every square icon button: a 1px
1255
+ border, sunken fill, text-colored glyph, and a subtle drop line. */
1256
+ .button,
1257
+ .iconButton {
1258
+ border: 1px solid var(--castle-inspector-border);
1259
+ border-radius: var(--castle-radius);
1260
+ background: var(--castle-inspector-button-bg);
1261
+ color: var(--castle-inspector-text);
1262
+ cursor: pointer;
1263
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
1264
+ }
1265
+
1266
+ .button {
1267
+ padding: 6px 10px;
1268
+ }
1269
+
1270
+ /* The square icon button -- the single source of truth every other square icon
1271
+ button across the editor matches. Default/hover/active follow the pixel
1272
+ editor's eyedropper; the disabled dimming follows the undo button. Fixed
1273
+ 28x28, never shrinks in a toolbar row, and centers a 14px glyph. */
1274
+ .iconButton {
1275
+ width: 28px;
1276
+ height: 28px;
1277
+ flex: 0 0 auto;
1278
+ padding: 0;
1279
+ display: inline-flex;
1280
+ align-items: center;
1281
+ justify-content: center;
1282
+ }
1283
+
1284
+ .iconButton svg {
1285
+ width: 14px;
1286
+ height: 14px;
1287
+ }
1288
+
1289
+ .button:hover,
1290
+ .iconButton:hover {
1291
+ background: var(--castle-inspector-input-bg);
1292
+ }
1293
+
1294
+ .buttonDanger {
1295
+ color: var(--castle-danger);
1296
+ }
1297
+
1298
+ .button:disabled,
1299
+ .iconButton:disabled {
1300
+ opacity: 0.35;
1301
+ cursor: default;
1302
+ }
1303
+
1304
+ .actorList {
1305
+ display: grid;
1306
+ gap: 4px;
1307
+ }
1308
+
1309
+ .actorRow {
1310
+ width: 100%;
1311
+ border: 1px solid transparent;
1312
+ background: transparent;
1313
+ color: var(--castle-inspector-text);
1314
+ border-radius: var(--castle-radius);
1315
+ padding: 7px 8px;
1316
+ text-align: left;
1317
+ cursor: pointer;
1318
+ }
1319
+
1320
+ .actorRowSelected {
1321
+ background: var(--castle-black);
1322
+ border-color: var(--castle-black);
1323
+ color: var(--castle-text);
1324
+ }
1325
+
1326
+ .drawingEditor,
1327
+ .codeEditor {
1328
+ flex: 1;
1329
+ min-width: 0;
1330
+ min-height: 0;
1331
+ display: grid;
1332
+ grid-template-columns: minmax(320px, 1fr) 280px;
1333
+ background: var(--castle-workspace-bg);
1334
+ }
1335
+
1336
+ .drawingEditor {
1337
+ grid-template-columns: minmax(320px, 1fr) minmax(50px, 250px);
1338
+ grid-template-rows: minmax(0, 1fr);
1339
+ }
1340
+
1341
+ .drawingEditor > .pxArtInspector {
1342
+ grid-column: 2;
1343
+ }
1344
+
1345
+ .artboardToolColumn,
1346
+ .paintColumn {
1347
+ flex: 0 0 50px;
1348
+ display: flex;
1349
+ align-items: flex-start;
1350
+ justify-content: center;
1351
+ height: 100%;
1352
+ padding: 8px;
1353
+ box-sizing: border-box;
1354
+ background: var(--castle-workspace-bg);
1355
+ }
1356
+
1357
+ .paintColumn {
1358
+ display: none;
1359
+ }
1360
+
1361
+ .drawingToolColumn {
1362
+ display: flex;
1363
+ align-items: flex-start;
1364
+ justify-content: center;
1365
+ width: 50px;
1366
+ min-width: 0;
1367
+ min-height: 0;
1368
+ height: 100%;
1369
+ padding: 8px;
1370
+ box-sizing: border-box;
1371
+ background: var(--castle-workspace-bg);
1372
+ }
1373
+
1374
+ .drawingCanvasWrap {
1375
+ flex: 1 1 0;
1376
+ min-width: 0;
1377
+ min-height: 0;
1378
+ display: flex;
1379
+ align-items: center;
1380
+ justify-content: center;
1381
+ padding: 16px 24px;
1382
+ overflow: hidden;
1383
+ }
1384
+
1385
+ .artboardStack {
1386
+ display: flex;
1387
+ flex-direction: column;
1388
+ align-items: center;
1389
+ gap: 10px;
1390
+ min-width: 0;
1391
+ max-width: 100%;
1392
+ max-height: 100%;
1393
+ }
1394
+
1395
+ .canvasTopStack {
1396
+ display: flex;
1397
+ flex-direction: column;
1398
+ gap: 6px;
1399
+ }
1400
+
1401
+ .canvasTopBar {
1402
+ display: flex;
1403
+ align-items: center;
1404
+ gap: 16px;
1405
+ }
1406
+
1407
+ .canvasSizeBar {
1408
+ display: flex;
1409
+ align-items: center;
1410
+ gap: 6px;
1411
+ flex: 0 0 auto;
1412
+ font-size: 12px;
1413
+ color: var(--castle-inspector-muted);
1414
+ }
1415
+
1416
+ .cornerRadiusBar {
1417
+ display: flex;
1418
+ align-items: center;
1419
+ gap: 6px;
1420
+ flex: 0 0 auto;
1421
+ font-size: 12px;
1422
+ color: var(--castle-inspector-muted);
1423
+ }
1424
+
1425
+ /* Corner-radius segmented control (0 / ¼ / ½) — same bordered-row-of-
1426
+ buttons pattern as .drawingShapeModeRow, sized for short text labels
1427
+ instead of square glyph buttons. */
1428
+ .cornerRadiusSegments {
1429
+ display: flex;
1430
+ border: 1px solid var(--castle-inspector-border);
1431
+ border-radius: var(--castle-radius);
1432
+ overflow: hidden;
1433
+ }
1434
+
1435
+ .cornerRadiusSegments > * + * {
1436
+ border-left: 1px solid var(--castle-inspector-border);
1437
+ }
1438
+
1439
+ .cornerRadiusSegment {
1440
+ min-width: 28px;
1441
+ padding: 3px 8px;
1442
+ border: 0;
1443
+ background: var(--castle-inspector-button-bg);
1444
+ color: var(--castle-inspector-text);
1445
+ cursor: pointer;
1446
+ font-size: 12px;
1447
+ font-variant-numeric: tabular-nums;
1448
+ line-height: 1.35;
1449
+ }
1450
+
1451
+ .cornerRadiusSegment:hover {
1452
+ background: var(--castle-inspector-input-bg);
1453
+ }
1454
+
1455
+ /* Compound selector (not a bare `.cornerRadiusSegmentOn`) for the same reason
1456
+ documented above `.drawingToolButton.drawingToolSelected`: it must beat
1457
+ `.cornerRadiusSegment:hover` on specificity no matter source order. */
1458
+ .cornerRadiusSegment.cornerRadiusSegmentOn,
1459
+ .cornerRadiusSegment.cornerRadiusSegmentOn:hover {
1460
+ background: var(--castle-selected);
1461
+ color: var(--castle-selected-ink);
1462
+ }
1463
+
1464
+ .canvasSizeSep {
1465
+ color: var(--castle-inspector-muted);
1466
+ user-select: none;
1467
+ }
1468
+
1469
+ .canvasSizeSelect {
1470
+ width: 52px;
1471
+ border: 1px solid var(--castle-inspector-border);
1472
+ border-radius: var(--castle-radius);
1473
+ background: var(--castle-inspector-input-bg);
1474
+ color: var(--castle-inspector-text);
1475
+ padding: 3px 22px 3px 6px;
1476
+ font-size: 12px;
1477
+ text-align: center;
1478
+ appearance: none;
1479
+ -webkit-appearance: none;
1480
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' fill='none' stroke='%23888888' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1481
+ background-repeat: no-repeat;
1482
+ background-position: right 6px center;
1483
+ }
1484
+
1485
+ .pxArtInspector {
1486
+ min-width: 0;
1487
+ border-left: 1px solid var(--castle-inspector-divider);
1488
+ background: var(--castle-workspace-bg);
1489
+ color: var(--castle-inspector-text);
1490
+ font-size: 14px;
1491
+ overflow: hidden;
1492
+ display: flex;
1493
+ flex-direction: column;
1494
+ }
1495
+
1496
+ .paintStrip {
1497
+ display: flex;
1498
+ flex-direction: column;
1499
+ width: 34px;
1500
+ flex: 0 0 auto;
1501
+ gap: 8px;
1502
+ }
1503
+
1504
+ /* Each control group (color, size, shape type, mode toggle) is its own boxed
1505
+ section, separated by the strip's gap. Controls stacked inside a section keep
1506
+ 1px dividers so a multi-button group still reads as one segmented unit. */
1507
+ .paintStripSection {
1508
+ display: flex;
1509
+ flex-direction: column;
1510
+ border: 1px solid var(--castle-inspector-border);
1511
+ border-radius: var(--castle-radius);
1512
+ overflow: hidden;
1513
+ }
1514
+
1515
+ .paintStripSection > * + * {
1516
+ border-top: 1px solid var(--castle-inspector-border);
1517
+ }
1518
+
1519
+ .paintStripColor {
1520
+ width: 34px;
1521
+ height: 34px;
1522
+ padding: 0;
1523
+ border: 0;
1524
+ border-radius: 0;
1525
+ cursor: pointer;
1526
+ flex: 0 0 auto;
1527
+ }
1528
+
1529
+ .paintStripSlider {
1530
+ height: 100px;
1531
+ display: flex;
1532
+ align-items: center;
1533
+ justify-content: center;
1534
+ padding: 8px 0;
1535
+ flex: 0 0 auto;
1536
+ }
1537
+
1538
+ .paintStripModeButton {
1539
+ width: 34px;
1540
+ height: 34px;
1541
+ padding: 0;
1542
+ display: inline-flex;
1543
+ align-items: center;
1544
+ justify-content: center;
1545
+ border: 0;
1546
+ border-radius: 0;
1547
+ background: var(--castle-inspector-button-bg);
1548
+ color: var(--castle-inspector-text);
1549
+ cursor: pointer;
1550
+ font-size: 14px;
1551
+ line-height: 1;
1552
+ }
1553
+
1554
+ .paintStripModeButton svg {
1555
+ width: 1em;
1556
+ height: 1em;
1557
+ }
1558
+
1559
+ .paintStripModeButton:hover {
1560
+ background: var(--castle-inspector-input-bg);
1561
+ }
1562
+
1563
+ .paintStripModeButton:disabled {
1564
+ opacity: 0.35;
1565
+ cursor: default;
1566
+ }
1567
+
1568
+ .drawingSizeSliderVertical {
1569
+ width: 88px;
1570
+ height: 14px;
1571
+ margin: 0;
1572
+ cursor: pointer;
1573
+ transform: rotate(-90deg);
1574
+ appearance: none;
1575
+ -webkit-appearance: none;
1576
+ background: transparent;
1577
+ }
1578
+
1579
+ .drawingSizeSliderVertical:disabled {
1580
+ cursor: default;
1581
+ opacity: 0.45;
1582
+ }
1583
+
1584
+ .drawingSizeSliderVertical::-webkit-slider-runnable-track {
1585
+ height: 4px;
1586
+ border-radius: 999px;
1587
+ background: #4a4a4a;
1588
+ }
1589
+
1590
+ .drawingSizeSliderVertical::-webkit-slider-thumb {
1591
+ appearance: none;
1592
+ -webkit-appearance: none;
1593
+ width: 14px;
1594
+ height: 14px;
1595
+ margin-top: -5px;
1596
+ border-radius: 999px;
1597
+ border: 1px solid #d8d8d8;
1598
+ background: #fff;
1599
+ }
1600
+
1601
+ .drawingSizeSliderVertical::-moz-range-track {
1602
+ height: 4px;
1603
+ border-radius: 999px;
1604
+ background: #4a4a4a;
1605
+ }
1606
+
1607
+ .drawingSizeSliderVertical::-moz-range-thumb {
1608
+ width: 14px;
1609
+ height: 14px;
1610
+ border-radius: 999px;
1611
+ border: 1px solid #d8d8d8;
1612
+ background: #fff;
1613
+ }
1614
+
1615
+ .palettePopover {
1616
+ position: fixed;
1617
+ z-index: 200;
1618
+ width: min(280px, calc(100vw - 16px));
1619
+ max-height: min(420px, calc(100vh - 16px));
1620
+ overflow: auto;
1621
+ padding: 12px 14px 14px;
1622
+ border: 1px solid var(--castle-inspector-border);
1623
+ border-radius: var(--castle-radius);
1624
+ background: var(--castle-workspace-bg);
1625
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
1626
+ }
1627
+
1628
+ .palettePopoverHeader {
1629
+ display: flex;
1630
+ align-items: center;
1631
+ justify-content: space-between;
1632
+ gap: 10px;
1633
+ margin-bottom: 10px;
1634
+ }
1635
+
1636
+ .palettePopoverTitle {
1637
+ font-size: 14px;
1638
+ font-weight: 650;
1639
+ }
1640
+
1641
+ .drawingArtboard {
1642
+ min-width: 0;
1643
+ min-height: 0;
1644
+ display: flex;
1645
+ align-items: center;
1646
+ justify-content: center;
1647
+ }
1648
+
1649
+ /* Stacks the native artboard canvas, the smooth-preview canvas, and the
1650
+ display-resolution overlay so the marching-ants outline renders crisp
1651
+ instead of sub-pixel on the upscaled art. The checkerboard "transparency"
1652
+ background lives on the FRAME (not the main canvas) so it doesn't paint
1653
+ over the smooth-preview canvas underneath in "smooth" render mode. */
1654
+ .drawingArtboardFrame {
1655
+ position: relative;
1656
+ flex: 0 0 auto;
1657
+ /* Own stacking context, so the negative-z .drawingSmooth canvas paints just
1658
+ above THIS element's checkerboard background instead of sinking below it
1659
+ (without isolation, negative z-index children paint before all in-flow
1660
+ descendant backgrounds of the outer stacking context — checkerboard
1661
+ included — hiding the smooth render entirely). */
1662
+ isolation: isolate;
1663
+ background:
1664
+ linear-gradient(45deg, #1a1a1a 25%, transparent 25%),
1665
+ linear-gradient(-45deg, #1a1a1a 25%, transparent 25%),
1666
+ linear-gradient(45deg, transparent 75%, #1a1a1a 75%),
1667
+ linear-gradient(-45deg, transparent 75%, #1a1a1a 75%);
1668
+ background-color: #121212;
1669
+ background-size: 16px 16px;
1670
+ background-position:
1671
+ 0 0,
1672
+ 0 8px,
1673
+ 8px -8px,
1674
+ -8px 0;
1675
+ border: 1px solid var(--castle-inspector-border);
1676
+ }
1677
+
1678
+ .drawingOverlay {
1679
+ position: absolute;
1680
+ inset: 0;
1681
+ width: 100%;
1682
+ height: 100%;
1683
+ pointer-events: none;
1684
+ }
1685
+
1686
+ /* Behind the main canvas (negative z-index paints before static content in
1687
+ the same containing block) — holds the corner-rounded render for "smooth"
1688
+ sprites while the main canvas above it stays transparent except for tool
1689
+ overlays, so editing interactions are unaffected. Empty/0-sized in "pixel"
1690
+ mode. */
1691
+ .drawingSmooth {
1692
+ position: absolute;
1693
+ inset: 0;
1694
+ width: 100%;
1695
+ height: 100%;
1696
+ pointer-events: none;
1697
+ z-index: -1;
1698
+ }
1699
+
1700
+ .drawingCanvas {
1701
+ image-rendering: pixelated;
1702
+ position: absolute;
1703
+ inset: 0;
1704
+ width: 100%;
1705
+ height: 100%;
1706
+ flex: 0 0 auto;
1707
+ /* tabIndex=-1 (for keyboard-focus capture on pointer-down) would otherwise
1708
+ draw a focus ring around the artboard; it's focused programmatically, never
1709
+ via keyboard, so suppressing the outline is safe. */
1710
+ outline: none;
1711
+ }
1712
+
1713
+ .palette {
1714
+ display: grid;
1715
+ grid-template-columns: repeat(12, minmax(0, 1fr));
1716
+ gap: 4px;
1717
+ padding-bottom: 12px;
1718
+ }
1719
+
1720
+ .swatch {
1721
+ aspect-ratio: 1;
1722
+ border: 1px solid var(--castle-inspector-border);
1723
+ border-radius: var(--castle-radius);
1724
+ cursor: pointer;
1725
+ }
1726
+
1727
+ /* Selected paint color. A double ring — light on the inside, dark on the
1728
+ outside — so the selection stays legible over ANY of the 64 swatch colors
1729
+ (a single-color border can vanish against a similar swatch). */
1730
+ .swatchSelected {
1731
+ border-color: transparent;
1732
+ box-shadow:
1733
+ inset 0 0 0 2px #fff,
1734
+ 0 0 0 2px var(--castle-black);
1735
+ }
1736
+
1737
+ .drawingToolStrip {
1738
+ display: flex;
1739
+ flex-direction: column;
1740
+ width: 34px;
1741
+ flex: 0 0 auto;
1742
+ max-height: 100%;
1743
+ overflow: auto;
1744
+ border: 1px solid var(--castle-inspector-border);
1745
+ border-radius: var(--castle-radius);
1746
+ }
1747
+
1748
+ .drawingToolStrip > * + * {
1749
+ border-top: 1px solid var(--castle-inspector-border);
1750
+ }
1751
+
1752
+ .drawingToolsPanel {
1753
+ overflow: hidden;
1754
+ display: flex;
1755
+ flex-direction: column;
1756
+ }
1757
+
1758
+ .drawingToolsPanelBody {
1759
+ flex: 1 1 0;
1760
+ min-height: 0;
1761
+ display: grid;
1762
+ grid-template-columns: 58px minmax(0, 1fr);
1763
+ }
1764
+
1765
+ .drawingSettingsColumn {
1766
+ min-width: 0;
1767
+ height: 100%;
1768
+ overflow: auto;
1769
+ }
1770
+
1771
+ .drawingToolButton,
1772
+ .drawingSizeButton {
1773
+ background: var(--castle-inspector-button-bg);
1774
+ color: var(--castle-inspector-text);
1775
+ cursor: pointer;
1776
+ }
1777
+
1778
+ .drawingToolButton {
1779
+ width: 34px;
1780
+ height: 34px;
1781
+ padding: 0;
1782
+ display: inline-flex;
1783
+ align-items: center;
1784
+ justify-content: center;
1785
+ font-size: 14px;
1786
+ line-height: 1;
1787
+ border: 0;
1788
+ border-radius: 0;
1789
+ }
1790
+
1791
+ .drawingToolButton svg {
1792
+ width: 1em;
1793
+ height: 1em;
1794
+ }
1795
+
1796
+ .drawingToolButton:hover {
1797
+ background: var(--castle-inspector-input-bg);
1798
+ }
1799
+
1800
+ .drawingToolButton:disabled {
1801
+ opacity: 0.35;
1802
+ cursor: default;
1803
+ }
1804
+
1805
+ /* Active / selected square icon button -- the eyedropper reference: a light
1806
+ fill with a dark glyph. The single selected/engaged look shared by the
1807
+ IconButton component (`active`), the onion-skin toggle, and the pixel
1808
+ editors' tool strip.
1809
+
1810
+ These use COMPOUND selectors (`.button.buttonActive`,
1811
+ `.drawingToolButton.drawingToolSelected`) on purpose: every active button
1812
+ already carries both classes, so the (0,2,0)/(0,3,0) specificity beats the
1813
+ equal-specificity base + size rules (`.button`/`.iconButton`,
1814
+ `.drawingToolButton`) NO MATTER the source order. A bare `.buttonActive`
1815
+ ties those base rules on specificity and only wins if it happens to appear
1816
+ later in the file -- which Vite HMR can reshuffle when this module is edited,
1817
+ silently dropping the active fill. The compound selector removes that
1818
+ fragility. */
1819
+ .button.buttonActive,
1820
+ .drawingToolButton.drawingToolSelected,
1821
+ .paintStripModeButton.drawingToolSelected {
1822
+ background: var(--castle-selected);
1823
+ border-color: var(--castle-selected);
1824
+ color: var(--castle-selected-ink);
1825
+ }
1826
+
1827
+ .button.buttonActive:hover,
1828
+ .drawingToolButton.drawingToolSelected:hover,
1829
+ .paintStripModeButton.drawingToolSelected:hover {
1830
+ background: var(--castle-selected);
1831
+ border-color: var(--castle-selected);
1832
+ color: var(--castle-selected-ink);
1833
+ }
1834
+
1835
+ /* Horizontal segmented row of shape sub-mode buttons in the wide-layout Tool
1836
+ Settings panel. Reuses .drawingToolButton glyph buttons with shared borders. */
1837
+ .drawingShapeModeRow {
1838
+ display: flex;
1839
+ width: max-content;
1840
+ border: 1px solid var(--castle-inspector-border);
1841
+ border-radius: var(--castle-radius);
1842
+ overflow: hidden;
1843
+ }
1844
+
1845
+ .drawingShapeModeRow > * + * {
1846
+ border-left: 1px solid var(--castle-inspector-border);
1847
+ }
1848
+
1849
+ .drawingToolHint {
1850
+ color: var(--castle-inspector-muted);
1851
+ font-size: 14px;
1852
+ line-height: 1.35;
1853
+ padding-bottom: 8px;
1854
+ }
1855
+
1856
+ .drawingToolSettings {
1857
+ padding-bottom: 8px;
1858
+ }
1859
+
1860
+ .drawingSizePicker {
1861
+ display: grid;
1862
+ gap: 8px;
1863
+ padding-bottom: 8px;
1864
+ }
1865
+
1866
+ .drawingSizeLabel {
1867
+ display: flex;
1868
+ align-items: baseline;
1869
+ justify-content: space-between;
1870
+ gap: 10px;
1871
+ color: var(--castle-inspector-muted);
1872
+ font-size: 14px;
1873
+ line-height: 1.35;
1874
+ }
1875
+
1876
+ .drawingSizeLabel span:last-child {
1877
+ color: var(--castle-inspector-text);
1878
+ font-variant-numeric: tabular-nums;
1879
+ }
1880
+
1881
+ .drawingSizeControlRow {
1882
+ display: grid;
1883
+ grid-template-columns: minmax(0, 1fr);
1884
+ align-items: center;
1885
+ gap: 10px;
1886
+ }
1887
+
1888
+ .drawingSizeSlider {
1889
+ width: 100%;
1890
+ cursor: pointer;
1891
+ appearance: none;
1892
+ -webkit-appearance: none;
1893
+ background: transparent;
1894
+ }
1895
+
1896
+ .drawingSizeSlider:disabled {
1897
+ cursor: default;
1898
+ opacity: 0.45;
1899
+ }
1900
+
1901
+ .drawingSizeSlider::-webkit-slider-runnable-track {
1902
+ height: 4px;
1903
+ border-radius: 999px;
1904
+ background: #4a4a4a;
1905
+ }
1906
+
1907
+ .drawingSizeSlider::-webkit-slider-thumb {
1908
+ appearance: none;
1909
+ -webkit-appearance: none;
1910
+ width: 14px;
1911
+ height: 14px;
1912
+ margin-top: -5px;
1913
+ border-radius: 999px;
1914
+ border: 1px solid #d8d8d8;
1915
+ background: #fff;
1916
+ }
1917
+
1918
+ .drawingFillToggle {
1919
+ min-height: 28px;
1920
+ padding: 4px 9px;
1921
+ border: 1px solid var(--castle-inspector-border);
1922
+ border-radius: var(--castle-radius);
1923
+ background: var(--castle-inspector-button-bg);
1924
+ color: var(--castle-inspector-text);
1925
+ cursor: pointer;
1926
+ font-size: 14px;
1927
+ }
1928
+
1929
+ .drawingFillToggleOn {
1930
+ background: var(--castle-selected);
1931
+ border-color: var(--castle-selected);
1932
+ color: var(--castle-selected-ink);
1933
+ }
1934
+
1935
+ .drawingModeRow {
1936
+ display: flex;
1937
+ align-items: center;
1938
+ justify-content: space-between;
1939
+ gap: 12px;
1940
+ color: var(--castle-inspector-muted);
1941
+ font-size: 14px;
1942
+ line-height: 1.35;
1943
+ }
1944
+
1945
+ .drawingSwitch {
1946
+ position: relative;
1947
+ width: 34px;
1948
+ height: 20px;
1949
+ padding: 0;
1950
+ border: 1px solid var(--castle-inspector-border);
1951
+ border-radius: 999px;
1952
+ background: #343434;
1953
+ cursor: pointer;
1954
+ }
1955
+
1956
+ .drawingSwitchOn {
1957
+ border-color: var(--castle-selected);
1958
+ background: var(--castle-selected);
1959
+ }
1960
+
1961
+ .drawingSwitchKnob {
1962
+ position: absolute;
1963
+ top: 2px;
1964
+ left: 2px;
1965
+ width: 14px;
1966
+ height: 14px;
1967
+ border-radius: 999px;
1968
+ background: #fff;
1969
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
1970
+ transition: transform 120ms ease;
1971
+ }
1972
+
1973
+ .drawingSwitchOn .drawingSwitchKnob {
1974
+ transform: translateX(14px);
1975
+ }
1976
+
1977
+ .drawingSizeSlider::-moz-range-track {
1978
+ height: 4px;
1979
+ border-radius: 999px;
1980
+ background: #4a4a4a;
1981
+ }
1982
+
1983
+ .drawingSizeSlider::-moz-range-thumb {
1984
+ width: 14px;
1985
+ height: 14px;
1986
+ border-radius: 999px;
1987
+ border: 1px solid #d8d8d8;
1988
+ background: #fff;
1989
+ }
1990
+
1991
+ .codeEditor {
1992
+ grid-template-columns: 1fr;
1993
+ }
1994
+
1995
+ .codeMirrorHost {
1996
+ min-width: 0;
1997
+ min-height: 0;
1998
+ height: 100%;
1999
+ }
2000
+
2001
+ /* mobileOnly comes AFTER element-specific rules so `display: none` wins on desktop. */
2002
+ .mobileOnly,
2003
+ .sheetBackdrop,
2004
+ .sheetGrab,
2005
+ .sheetGrabBar {
2006
+ display: none;
2007
+ }
2008
+
2009
+ @media (max-width: 600px) {
2010
+ :root {
2011
+ /* Fallback for the file-browser sheet and the inspector's initial slide-in;
2012
+ the inspector's live height is driven by the hook's --sheet-h (see below). */
2013
+ --sheet-high-height: 62vh;
2014
+ }
2015
+
2016
+ /* `display: contents` makes wrapper spans transparent in layout so their
2017
+ children become direct flex items of the parent (gap applies, etc). */
2018
+ .mobileOnly {
2019
+ display: contents;
2020
+ }
2021
+
2022
+ /* Backdrop sits below the header so the files toggle stays interactive. */
2023
+ .sheetBackdrop {
2024
+ display: block;
2025
+ position: fixed;
2026
+ top: 52px;
2027
+ left: 0;
2028
+ right: 0;
2029
+ bottom: 0;
2030
+ z-index: 30;
2031
+ background: transparent;
2032
+ }
2033
+
2034
+ .appShell {
2035
+ grid-template-columns: 1fr;
2036
+ grid-template-rows: 1fr;
2037
+ position: relative;
2038
+ }
2039
+
2040
+ .mainEditor {
2041
+ grid-column: 1;
2042
+ grid-row: 1;
2043
+ min-height: 0;
2044
+ }
2045
+
2046
+ /* Header: title (left, compact) + centered icon group (right slot includes files button). */
2047
+ .editorHeader {
2048
+ height: 52px;
2049
+ flex: 0 0 52px;
2050
+ padding: 8px 10px;
2051
+ gap: 8px;
2052
+ justify-content: space-between;
2053
+ }
2054
+
2055
+ .editorHeaderLeft {
2056
+ flex: 1;
2057
+ min-width: 0;
2058
+ }
2059
+
2060
+ .editorHeaderRight {
2061
+ flex: 0 0 auto;
2062
+ justify-content: flex-end;
2063
+ gap: 14px;
2064
+ }
2065
+
2066
+ .editorTitle {
2067
+ font-size: 14px;
2068
+ overflow: hidden;
2069
+ text-overflow: ellipsis;
2070
+ white-space: nowrap;
2071
+ }
2072
+
2073
+ .muted {
2074
+ overflow: hidden;
2075
+ text-overflow: ellipsis;
2076
+ white-space: nowrap;
2077
+ }
2078
+
2079
+ /* Scene hotbar row stays visible when compact (it holds drag-and-drop
2080
+ blueprints later). Drawing playback lives in the header instead. */
2081
+ .sceneWorkspace {
2082
+ grid-template-columns: 1fr;
2083
+ grid-template-rows: var(--castle-hotbar-row-full) minmax(0, 1fr);
2084
+ }
2085
+
2086
+ .drawingEditor {
2087
+ grid-template-columns: 1fr;
2088
+ grid-template-rows: minmax(0, 1fr);
2089
+ }
2090
+
2091
+ .stageWrap {
2092
+ grid-column: 1;
2093
+ grid-row: 2;
2094
+ align-items: flex-start;
2095
+ padding: 10px 12px 12px;
2096
+ background: var(--castle-stage-workspace-bg);
2097
+ }
2098
+
2099
+ /* One-column layout: the card MUST keep a definite width. Its clip/canvas
2100
+ children are absolutely positioned (no intrinsic size), so a flex item with
2101
+ `width: auto` + `aspect-ratio` collapses to 0x0 -- the stage went blank and
2102
+ only the dark `.stageWrap` showed. Drive width from the viewport (capped by
2103
+ 480px and by 62vh so the aspect-derived height always fits without
2104
+ `max-height` clamping and distorting the maze). */
2105
+ .stageCard {
2106
+ width: min(92vw, 480px, 62vh);
2107
+ max-width: 100%;
2108
+ max-height: 100%;
2109
+ }
2110
+
2111
+ /* PxArt artboard: keep flanking tool/paint columns; fit hook drives canvas size. */
2112
+ .drawingCanvasWrap {
2113
+ align-items: center;
2114
+ padding: 10px 8px 12px;
2115
+ }
2116
+
2117
+ .artboardToolColumn,
2118
+ .paintColumn {
2119
+ padding: 8px 4px;
2120
+ }
2121
+
2122
+ /* File-browser + inspector sheets share fixed-bottom positioning, height,
2123
+ transform/transition, and chrome. Per-sheet differences (z-index, side
2124
+ border, background, padding) follow in dedicated rules below. */
2125
+ .fileBrowser,
2126
+ .inspector {
2127
+ position: fixed;
2128
+ left: 0;
2129
+ right: 0;
2130
+ bottom: 0;
2131
+ width: 100%;
2132
+ height: var(--sheet-high-height);
2133
+ transform: translateY(100%);
2134
+ transition:
2135
+ transform 0.28s cubic-bezier(0.32, 0.72, 0, 1),
2136
+ height 0.28s cubic-bezier(0.32, 0.72, 0, 1);
2137
+ border-top: 1px solid var(--castle-inspector-divider);
2138
+ box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.18);
2139
+ overflow: hidden;
2140
+ display: flex;
2141
+ flex-direction: column;
2142
+ }
2143
+
2144
+ /* File-browser sheet: hidden off-screen by default; slides up to high snap. */
2145
+ .fileBrowser {
2146
+ z-index: 50;
2147
+ border-right: 0;
2148
+ background: var(--castle-sheet-bg);
2149
+ }
2150
+
2151
+ .fileBrowser[data-sheet-snap='high'] {
2152
+ transform: translateY(0);
2153
+ }
2154
+
2155
+ .fileTree {
2156
+ flex: 1 1 0;
2157
+ min-height: 0;
2158
+ overflow-y: auto;
2159
+ }
2160
+
2161
+ /* Inspector sheet: hidden off-screen by default; slides up when open. */
2162
+ .inspector {
2163
+ z-index: 45;
2164
+ border-left: 0;
2165
+ background: var(--castle-workspace-bg);
2166
+ padding: 0;
2167
+ }
2168
+
2169
+ /* Open inspector sheet: continuous height driven by the hook's live --sheet-h
2170
+ (falls back to the default expanded height). Drag the grab handle to resize
2171
+ freely; the hook picks the settle point on release. */
2172
+ .inspector[data-sheet-open='true'] {
2173
+ transform: translateY(0);
2174
+ height: var(--sheet-h, var(--sheet-high-height));
2175
+ }
2176
+
2177
+ /* While dragging, drop the transition so the sheet tracks the finger 1:1; the
2178
+ settle animation returns the moment this class is removed on release. */
2179
+ .inspector.sheetDragging {
2180
+ transition: none;
2181
+ }
2182
+
2183
+ /* Shared sheet chrome -- grab bar pill. Always visible when sheet is visible. */
2184
+ .sheetGrab {
2185
+ display: flex;
2186
+ flex-direction: column;
2187
+ align-items: stretch;
2188
+ justify-content: center;
2189
+ flex: 0 0 auto;
2190
+ padding: 8px 14px 8px;
2191
+ cursor: grab;
2192
+ user-select: none;
2193
+ touch-action: none;
2194
+ background: var(--castle-sheet-bg);
2195
+ border-bottom: 1px solid var(--castle-inspector-divider);
2196
+ }
2197
+
2198
+ .inspector .sheetGrab {
2199
+ background: var(--castle-workspace-bg);
2200
+ }
2201
+
2202
+ .sheetGrabBar {
2203
+ display: block;
2204
+ width: 36px;
2205
+ height: 4px;
2206
+ border-radius: 999px;
2207
+ background: var(--castle-inspector-divider);
2208
+ margin: 0 auto;
2209
+ flex: 0 0 4px;
2210
+ }
2211
+
2212
+ .sheetBody {
2213
+ flex: 1;
2214
+ min-height: 0;
2215
+ overflow: auto;
2216
+ }
2217
+
2218
+ .inspectorBody {
2219
+ background: var(--castle-workspace-bg);
2220
+ }
2221
+
2222
+ .codeInspectorInfo {
2223
+ display: grid;
2224
+ grid-template-columns: 80px 1fr;
2225
+ gap: 6px 12px;
2226
+ padding: 14px 16px;
2227
+ font-size: 14px;
2228
+ color: var(--castle-inspector-text);
2229
+ word-break: break-all;
2230
+ }
2231
+ }
2232
+
2233
+ /* Bare (panel) editor wrapper: fills the panel iframe; single column.
2234
+ Use 100% not 100vw/100vh — in a dock iframe vw/vh are the TOP-LEVEL
2235
+ viewport, so the editor "thinks" it is full-window-wide and container
2236
+ queries on .editorBody never flip to compact. */
2237
+ .panelBare {
2238
+ display: grid;
2239
+ grid-template-columns: 1fr;
2240
+ width: 100%;
2241
+ height: 100%;
2242
+ min-width: 0;
2243
+ min-height: 0;
2244
+ background: var(--castle-workspace-bg);
2245
+ }
2246
+ /* In a panel the scene editor's tools row must stay visible even below 860px --
2247
+ it normally moves into the mobile header, which bare mode removes. Restore the
2248
+ full-height tools row (--castle-hotbar-row-full, same as the desktop track) and
2249
+ put the stage back in row 2 (the narrow layout moves it up into row 1). The row
2250
+ must use the full height -- a shorter hardcoded track left the centered 46px
2251
+ hotbar buttons overhanging the stage, which clipped their bottoms.
2252
+
2253
+ The PxArt editor (.drawingEditor) is intentionally NOT included here: it has
2254
+ no docked tools row (its controls live in the header), and its left column
2255
+ (.editorLeft) is pinned to grid-row 1, so injecting a 48px first row collapsed
2256
+ the artboard to zero height across the 600-860px band. PxArt compact layout
2257
+ is driven by the @container editor (max-width: 620px) rule on .editorBody. */
2258
+ @media (max-width: 860px) {
2259
+ .panelBare .sceneWorkspace {
2260
+ grid-template-rows: var(--castle-hotbar-row-full) minmax(0, 1fr);
2261
+ }
2262
+ .panelBare .sceneTools {
2263
+ display: grid;
2264
+ }
2265
+ .panelBare .stageWrap {
2266
+ grid-row: 2;
2267
+ }
2268
+ }
2269
+
2270
+ /* PxArt compact layout — MUST stay after .drawingToolsPanel / .paintColumn base
2271
+ rules so these container-query overrides win the cascade (same element carries
2272
+ pxArtInspector + drawingToolsPanel; a later bare .drawingToolsPanel{display:flex}
2273
+ was keeping the sidebar visible beside the paint strip). */
2274
+ @container editor (max-width: 620px) {
2275
+ .drawingEditor {
2276
+ grid-template-columns: 1fr;
2277
+ }
2278
+
2279
+ .drawingEditor > .pxArtInspector.drawingToolsPanel {
2280
+ display: none;
2281
+ }
2282
+
2283
+ .paintColumn {
2284
+ display: flex;
2285
+ }
2286
+ }
2287
+