castle-web-cli 0.4.104 → 0.4.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-failures.d.ts +6 -0
- package/dist/agent-failures.js +39 -7
- package/dist/agent.js +79 -74
- package/dist/shell/assets/{index-BoH7rGKy.js → index-B-RU1Sa9.js} +1 -1
- package/dist/shell/assets/{index-DIcWN-RS.css → index-B2quTIav.css} +1 -1
- package/dist/shell/index.html +2 -2
- package/kits/physics-2d/castle.json +1 -1
- package/kits/physics-2d/editors/PxArtEditor.jsx +71 -1
- package/kits/physics-2d/editors/SceneEditor.jsx +72 -1
- package/kits/physics-2d/editors/editorHistory.js +18 -0
- package/kits/physics-2d/editors/pxArtTimeline.jsx +41 -8
- package/kits/physics-2d/editors/pxArtTimeline.module.css +18 -0
- package/kits/physics-2d/engine/gestureZoomPan.js +145 -0
- package/kits/physics-2d/engine/ui.jsx +208 -71
- package/kits/physics-2d/engine/ui.module.css +162 -0
- package/package.json +1 -1
|
@@ -286,87 +286,222 @@ export function TextField({ label, value, onChange, overridden, defaultValue, on
|
|
|
286
286
|
</FieldRow>
|
|
287
287
|
);
|
|
288
288
|
}
|
|
289
|
+
function numberText(v) {
|
|
290
|
+
return Number.isFinite(v) ? String(v) : '0';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Keys in reading order for a 4-col grid: 0 spans the bottom three, Done the last.
|
|
294
|
+
const NUMPAD_KEYS = [
|
|
295
|
+
'7', '8', '9', 'back',
|
|
296
|
+
'4', '5', '6', 'sign',
|
|
297
|
+
'1', '2', '3', 'dot',
|
|
298
|
+
'0', 'done',
|
|
299
|
+
];
|
|
300
|
+
const NUMPAD_GLYPH = { back: '⌫', sign: '±', dot: '.', done: 'Done' };
|
|
301
|
+
|
|
302
|
+
// A custom numeric pad shown as a bottom sheet — the ONLY reliable way to offer a
|
|
303
|
+
// minus on mobile (the native numpad has none, and there's no web API to add one).
|
|
304
|
+
// The field has no <input>, so no native keyboard ever appears; this edits a draft
|
|
305
|
+
// string and commits on Done / dismiss.
|
|
306
|
+
function NumpadSheet({ label, value, min, max, step, onCommit, onClose }) {
|
|
307
|
+
const [draft, setDraft] = useState(() => numberText(value));
|
|
308
|
+
const pristineRef = useRef(true); // first digit replaces the seeded value
|
|
309
|
+
function finish() {
|
|
310
|
+
let v = Number(draft);
|
|
311
|
+
if (!Number.isFinite(v)) v = value;
|
|
312
|
+
if (min != null && v < min) v = min;
|
|
313
|
+
if (max != null && v > max) v = max;
|
|
314
|
+
onCommit(v);
|
|
315
|
+
onClose();
|
|
316
|
+
}
|
|
317
|
+
function press(key) {
|
|
318
|
+
if (key === 'done') return finish();
|
|
319
|
+
if (key === 'back') {
|
|
320
|
+
pristineRef.current = false;
|
|
321
|
+
return setDraft((d) => d.slice(0, -1));
|
|
322
|
+
}
|
|
323
|
+
if (key === 'sign') {
|
|
324
|
+
pristineRef.current = false;
|
|
325
|
+
return setDraft((d) => (d.startsWith('-') ? d.slice(1) : '-' + (d === '0' ? '' : d)));
|
|
326
|
+
}
|
|
327
|
+
// The first digit/dot after opening replaces the seeded value (calculator-style).
|
|
328
|
+
const fresh = pristineRef.current;
|
|
329
|
+
pristineRef.current = false;
|
|
330
|
+
if (key === 'dot') {
|
|
331
|
+
return setDraft((d) =>
|
|
332
|
+
fresh ? '0.' : d.includes('.') ? d : d === '' || d === '-' ? d + '0.' : d + '.'
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
setDraft((d) => {
|
|
336
|
+
if (fresh) return key;
|
|
337
|
+
if (d === '0') return key;
|
|
338
|
+
if (d === '-0') return '-' + key;
|
|
339
|
+
return d + key;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return createPortal(
|
|
343
|
+
<div className={styles.numpadBackdrop} onPointerDown={finish}>
|
|
344
|
+
<div
|
|
345
|
+
className={styles.numpadSheet}
|
|
346
|
+
role="dialog"
|
|
347
|
+
aria-label={`Edit ${label}`}
|
|
348
|
+
onPointerDown={(event) => event.stopPropagation()}>
|
|
349
|
+
<div className={styles.numpadValueRow}>
|
|
350
|
+
<span className={styles.numpadValueLabel}>{label}</span>
|
|
351
|
+
<span className={styles.numpadValueNum}>{draft === '' || draft === '-' ? '0' : draft}</span>
|
|
352
|
+
</div>
|
|
353
|
+
<div className={styles.numpadGrid}>
|
|
354
|
+
{NUMPAD_KEYS.map((key) => (
|
|
355
|
+
<button
|
|
356
|
+
key={key}
|
|
357
|
+
type="button"
|
|
358
|
+
className={cx(
|
|
359
|
+
styles.numpadKey,
|
|
360
|
+
key === 'done' && styles.numpadKeyDone,
|
|
361
|
+
key === '0' && styles.numpadKeyZero
|
|
362
|
+
)}
|
|
363
|
+
onClick={() => press(key)}>
|
|
364
|
+
{NUMPAD_GLYPH[key] ?? key}
|
|
365
|
+
</button>
|
|
366
|
+
))}
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
</div>,
|
|
370
|
+
document.body
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Scrub tuning: horizontal travel before a drag counts as a scrub (vs a tap), the
|
|
375
|
+
// base pixels-per-step, and how fast the per-pixel step grows with drag distance
|
|
376
|
+
// (acceleration, so a big range is reachable without running off the screen edge).
|
|
377
|
+
const SCRUB_THRESHOLD = 12;
|
|
378
|
+
const SCRUB_PX_PER_STEP = 12;
|
|
379
|
+
const SCRUB_ACCEL_RANGE = 240;
|
|
380
|
+
|
|
289
381
|
export function NumberField({ label, value, onChange, min, max, step = 1, overridden, defaultValue, onReset }) {
|
|
290
382
|
const current = Number.isFinite(value) ? (value ?? 0) : 0;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
383
|
+
// A field with BOTH a min and max is a true slider (fill + thumb), the pointer's X
|
|
384
|
+
// mapped onto [min,max]. Everything else is a horizontal scrubber that can pass
|
|
385
|
+
// into negatives, marked with a grip so it reads as draggable.
|
|
386
|
+
const bounded = min != null && max != null && max > min;
|
|
387
|
+
const [padOpen, setPadOpen] = useState(false);
|
|
388
|
+
// While scrubbing, a value bubble floats above the finger (which occludes the
|
|
389
|
+
// field itself) — { x, y } client coords, or null when not scrubbing.
|
|
390
|
+
const [scrubPos, setScrubPos] = useState(null);
|
|
391
|
+
const boxRef = useRef(null);
|
|
392
|
+
const dragRef = useRef(null);
|
|
393
|
+
function clampVal(v) {
|
|
394
|
+
if (min != null && v < min) v = min;
|
|
395
|
+
if (max != null && v > max) v = max;
|
|
396
|
+
return v;
|
|
297
397
|
}
|
|
298
|
-
function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
if (max != null && next > max) next = max;
|
|
303
|
-
setDraft(String(next));
|
|
304
|
-
onChange(next);
|
|
398
|
+
function scrubTo(v) {
|
|
399
|
+
if (step) v = Math.round(v / step) * step;
|
|
400
|
+
v = clampVal(v);
|
|
401
|
+
if (v !== current) onChange(v);
|
|
305
402
|
}
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
403
|
+
function onPointerDown(event) {
|
|
404
|
+
if (padOpen) return;
|
|
405
|
+
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
|
406
|
+
// Don't capture or preventDefault yet: `touch-action: pan-y` lets the browser
|
|
407
|
+
// own a VERTICAL drag (scroll the field list); we only claim a HORIZONTAL one.
|
|
408
|
+
dragRef.current = {
|
|
409
|
+
id: event.pointerId,
|
|
410
|
+
startX: event.clientX,
|
|
411
|
+
startY: event.clientY,
|
|
412
|
+
startValue: current,
|
|
413
|
+
mode: 'pending',
|
|
414
|
+
};
|
|
312
415
|
}
|
|
416
|
+
function onPointerMove(event) {
|
|
417
|
+
const drag = dragRef.current;
|
|
418
|
+
if (!drag || drag.id !== event.pointerId) return;
|
|
419
|
+
const dx = event.clientX - drag.startX;
|
|
420
|
+
const dy = event.clientY - drag.startY;
|
|
421
|
+
if (drag.mode === 'pending') {
|
|
422
|
+
if (Math.abs(dx) < SCRUB_THRESHOLD && Math.abs(dy) < SCRUB_THRESHOLD) return;
|
|
423
|
+
if (Math.abs(dy) >= Math.abs(dx)) {
|
|
424
|
+
drag.mode = 'scroll'; // vertical intent — leave it to the list scroll
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
drag.mode = 'scrub';
|
|
428
|
+
boxRef.current?.setPointerCapture?.(event.pointerId);
|
|
429
|
+
}
|
|
430
|
+
if (drag.mode !== 'scrub') return;
|
|
431
|
+
event.preventDefault();
|
|
432
|
+
setScrubPos({ x: event.clientX, y: event.clientY });
|
|
433
|
+
if (bounded) {
|
|
434
|
+
const rect = boxRef.current.getBoundingClientRect();
|
|
435
|
+
const frac = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
|
|
436
|
+
scrubTo(min + frac * (max - min));
|
|
437
|
+
} else {
|
|
438
|
+
// Acceleration: each pixel counts for more the farther you've dragged.
|
|
439
|
+
const accel = 1 + Math.abs(dx) / SCRUB_ACCEL_RANGE;
|
|
440
|
+
scrubTo(drag.startValue + Math.round((dx / SCRUB_PX_PER_STEP) * accel) * step);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function onPointerUp(event) {
|
|
444
|
+
const drag = dragRef.current;
|
|
445
|
+
if (!drag || drag.id !== event.pointerId) return;
|
|
446
|
+
if (drag.mode === 'scrub') boxRef.current?.releasePointerCapture?.(event.pointerId);
|
|
447
|
+
const wasTap = drag.mode === 'pending';
|
|
448
|
+
dragRef.current = null;
|
|
449
|
+
setScrubPos(null);
|
|
450
|
+
if (wasTap) setPadOpen(true); // a plain tap opens the numpad
|
|
451
|
+
}
|
|
452
|
+
// A pointercancel = the browser took the gesture (a vertical scroll under pan-y).
|
|
453
|
+
// Just drop the drag; it must NOT be treated as a tap.
|
|
454
|
+
function onPointerCancel() {
|
|
455
|
+
dragRef.current = null;
|
|
456
|
+
setScrubPos(null);
|
|
457
|
+
}
|
|
458
|
+
const fillPct = bounded ? Math.min(100, Math.max(0, ((current - min) / (max - min)) * 100)) : 0;
|
|
313
459
|
return (
|
|
314
460
|
<FieldRow label={label} overridden={overridden} defaultValue={defaultValue} onReset={onReset}>
|
|
315
|
-
<div
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
461
|
+
<div
|
|
462
|
+
ref={boxRef}
|
|
463
|
+
className={cx(styles.input, styles.numberScrub, bounded ? styles.numberSlider : styles.numberGrip)}
|
|
464
|
+
style={{ touchAction: 'pan-y' }}
|
|
465
|
+
onPointerDown={onPointerDown}
|
|
466
|
+
onPointerMove={onPointerMove}
|
|
467
|
+
onPointerUp={onPointerUp}
|
|
468
|
+
onPointerCancel={onPointerCancel}>
|
|
469
|
+
{bounded ? (
|
|
470
|
+
<>
|
|
471
|
+
<div className={styles.sliderFill} style={{ width: `${fillPct}%` }} aria-hidden="true" />
|
|
472
|
+
<div className={styles.sliderThumb} style={{ left: `${fillPct}%` }} aria-hidden="true" />
|
|
473
|
+
</>
|
|
474
|
+
) : (
|
|
475
|
+
<span className={styles.scrubGrip} aria-hidden="true">
|
|
476
|
+
⋮⋮
|
|
477
|
+
</span>
|
|
478
|
+
)}
|
|
479
|
+
<span className={styles.numberValue}>{numberText(current)}</span>
|
|
480
|
+
</div>
|
|
481
|
+
{scrubPos
|
|
482
|
+
? createPortal(
|
|
483
|
+
<div
|
|
484
|
+
className={styles.scrubBubble}
|
|
485
|
+
style={{ left: scrubPos.x, top: scrubPos.y - 40 }}
|
|
486
|
+
aria-hidden="true">
|
|
487
|
+
{numberText(current)}
|
|
488
|
+
</div>,
|
|
489
|
+
document.body
|
|
490
|
+
)
|
|
491
|
+
: null}
|
|
492
|
+
{padOpen ? (
|
|
493
|
+
<NumpadSheet
|
|
494
|
+
label={label}
|
|
495
|
+
value={current}
|
|
319
496
|
min={min}
|
|
320
497
|
max={max}
|
|
321
498
|
step={step}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
editingRef.current = true;
|
|
325
|
-
}}
|
|
326
|
-
onChange={(event) => setDraft(event.target.value)}
|
|
327
|
-
onBlur={(event) => {
|
|
328
|
-
editingRef.current = false;
|
|
329
|
-
if (cancelRef.current) {
|
|
330
|
-
cancelRef.current = false;
|
|
331
|
-
setDraft(String(current));
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
commit(event.target.value);
|
|
335
|
-
}}
|
|
336
|
-
onKeyDown={(event) => {
|
|
337
|
-
if (event.key === 'Enter') {
|
|
338
|
-
event.preventDefault();
|
|
339
|
-
commit(event.currentTarget.value);
|
|
340
|
-
event.currentTarget.blur();
|
|
341
|
-
} else if (event.key === 'Escape') {
|
|
342
|
-
event.preventDefault();
|
|
343
|
-
cancelRef.current = true;
|
|
344
|
-
setDraft(String(current));
|
|
345
|
-
event.currentTarget.blur();
|
|
346
|
-
}
|
|
499
|
+
onCommit={(v) => {
|
|
500
|
+
if (v !== current) onChange(v);
|
|
347
501
|
}}
|
|
502
|
+
onClose={() => setPadOpen(false)}
|
|
348
503
|
/>
|
|
349
|
-
|
|
350
|
-
<button
|
|
351
|
-
type="button"
|
|
352
|
-
className={styles.numberStepper}
|
|
353
|
-
tabIndex={-1}
|
|
354
|
-
aria-label="Increment"
|
|
355
|
-
disabled={max != null && current >= max}
|
|
356
|
-
onClick={() => stepBy(step)}>
|
|
357
|
-
<Icon name="chevron-up" />
|
|
358
|
-
</button>
|
|
359
|
-
<button
|
|
360
|
-
type="button"
|
|
361
|
-
className={styles.numberStepper}
|
|
362
|
-
tabIndex={-1}
|
|
363
|
-
aria-label="Decrement"
|
|
364
|
-
disabled={min != null && current <= min}
|
|
365
|
-
onClick={() => stepBy(-step)}>
|
|
366
|
-
<Icon name="chevron-down" />
|
|
367
|
-
</button>
|
|
368
|
-
</div>
|
|
369
|
-
</div>
|
|
504
|
+
) : null}
|
|
370
505
|
</FieldRow>
|
|
371
506
|
);
|
|
372
507
|
}
|
|
@@ -494,9 +629,11 @@ function saveStoredSheetHeight(key, px) {
|
|
|
494
629
|
// storage full/unavailable -- height still works in-memory this session
|
|
495
630
|
}
|
|
496
631
|
}
|
|
497
|
-
export function useMobileSheet({ open = true, baseClassName, storageKey }) {
|
|
632
|
+
export function useMobileSheet({ open = true, baseClassName, storageKey, defaultPeek = false }) {
|
|
498
633
|
const [height, setHeightState] = useState(() =>
|
|
499
|
-
clampSheetHeight(
|
|
634
|
+
clampSheetHeight(
|
|
635
|
+
loadStoredSheetHeight(storageKey) ?? (defaultPeek ? sheetSizes().peek : sheetSizes().expanded)
|
|
636
|
+
)
|
|
500
637
|
);
|
|
501
638
|
const [dragging, setDragging] = useState(false);
|
|
502
639
|
const heightRef = useRef(height);
|
|
@@ -1191,6 +1191,160 @@
|
|
|
1191
1191
|
border-bottom: 1px solid var(--castle-inspector-divider);
|
|
1192
1192
|
}
|
|
1193
1193
|
|
|
1194
|
+
/* Number fields are drag scrubbers: drag the field to change the value — a bounded
|
|
1195
|
+
field (min AND max) maps the pointer across [min,max] like a slider and shows a
|
|
1196
|
+
fill; an unbounded field scrubs relatively and into negatives — tap to type. The
|
|
1197
|
+
input sits transparent on top and only takes pointer events while editing, so a
|
|
1198
|
+
drag reaches the box; a tap focuses it (JS) to raise the keyboard. */
|
|
1199
|
+
/* Number fields are drag scrubbers with a tap-to-open custom numpad (no native
|
|
1200
|
+
keyboard). A bounded field (min AND max) is a slider with fill + thumb; an
|
|
1201
|
+
unbounded one shows a grip and scrubs into negatives. touch-action: pan-y (set
|
|
1202
|
+
inline) lets a vertical drag scroll the field list while horizontal scrubs. */
|
|
1203
|
+
.numberScrub {
|
|
1204
|
+
position: relative;
|
|
1205
|
+
display: flex;
|
|
1206
|
+
align-items: center;
|
|
1207
|
+
gap: 6px;
|
|
1208
|
+
overflow: hidden;
|
|
1209
|
+
padding: 5px 8px;
|
|
1210
|
+
cursor: ew-resize;
|
|
1211
|
+
user-select: none;
|
|
1212
|
+
-webkit-user-select: none;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
.numberSlider {
|
|
1216
|
+
cursor: pointer;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
.numberValue {
|
|
1220
|
+
position: relative;
|
|
1221
|
+
z-index: 1;
|
|
1222
|
+
flex: 1 1 auto;
|
|
1223
|
+
min-width: 0;
|
|
1224
|
+
text-align: right;
|
|
1225
|
+
font-variant-numeric: tabular-nums;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
.scrubGrip {
|
|
1229
|
+
position: relative;
|
|
1230
|
+
z-index: 1;
|
|
1231
|
+
flex: 0 0 auto;
|
|
1232
|
+
color: var(--castle-inspector-muted);
|
|
1233
|
+
font-size: 12px;
|
|
1234
|
+
line-height: 1;
|
|
1235
|
+
letter-spacing: -2px;
|
|
1236
|
+
opacity: 0.4;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/* Value bubble that floats above the finger while scrubbing (the finger covers the
|
|
1240
|
+
field, so the live number needs to show somewhere visible). */
|
|
1241
|
+
.scrubBubble {
|
|
1242
|
+
position: fixed;
|
|
1243
|
+
z-index: 1001;
|
|
1244
|
+
transform: translate(-50%, -100%);
|
|
1245
|
+
padding: 3px 10px;
|
|
1246
|
+
border-radius: 8px;
|
|
1247
|
+
background: rgba(20, 24, 30, 0.96);
|
|
1248
|
+
border: 1px solid var(--castle-inspector-border);
|
|
1249
|
+
color: var(--castle-inspector-text);
|
|
1250
|
+
font-size: 15px;
|
|
1251
|
+
font-variant-numeric: tabular-nums;
|
|
1252
|
+
pointer-events: none;
|
|
1253
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.sliderFill {
|
|
1257
|
+
position: absolute;
|
|
1258
|
+
left: 0;
|
|
1259
|
+
top: 0;
|
|
1260
|
+
bottom: 0;
|
|
1261
|
+
background: rgba(90, 140, 210, 0.3);
|
|
1262
|
+
pointer-events: none;
|
|
1263
|
+
z-index: 0;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.sliderThumb {
|
|
1267
|
+
position: absolute;
|
|
1268
|
+
top: 50%;
|
|
1269
|
+
width: 3px;
|
|
1270
|
+
height: 60%;
|
|
1271
|
+
border-radius: 2px;
|
|
1272
|
+
background: rgba(150, 190, 240, 0.95);
|
|
1273
|
+
transform: translate(-50%, -50%);
|
|
1274
|
+
pointer-events: none;
|
|
1275
|
+
z-index: 1;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/* Custom numeric pad, shown as a bottom sheet. */
|
|
1279
|
+
.numpadBackdrop {
|
|
1280
|
+
position: fixed;
|
|
1281
|
+
inset: 0;
|
|
1282
|
+
z-index: 1000;
|
|
1283
|
+
display: flex;
|
|
1284
|
+
flex-direction: column;
|
|
1285
|
+
justify-content: flex-end;
|
|
1286
|
+
background: rgba(0, 0, 0, 0.35);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.numpadSheet {
|
|
1290
|
+
background: var(--castle-inspector-bg, #16181d);
|
|
1291
|
+
border-top: 1px solid var(--castle-inspector-border);
|
|
1292
|
+
border-radius: 14px 14px 0 0;
|
|
1293
|
+
padding: 10px 10px calc(10px + env(safe-area-inset-bottom, 0px));
|
|
1294
|
+
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.4);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
.numpadValueRow {
|
|
1298
|
+
display: flex;
|
|
1299
|
+
align-items: baseline;
|
|
1300
|
+
justify-content: space-between;
|
|
1301
|
+
gap: 12px;
|
|
1302
|
+
padding: 6px 8px 12px;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
.numpadValueLabel {
|
|
1306
|
+
color: var(--castle-inspector-muted);
|
|
1307
|
+
font-size: 13px;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
.numpadValueNum {
|
|
1311
|
+
color: var(--castle-inspector-text);
|
|
1312
|
+
font-size: 22px;
|
|
1313
|
+
font-variant-numeric: tabular-nums;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
.numpadGrid {
|
|
1317
|
+
display: grid;
|
|
1318
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1319
|
+
gap: 8px;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
.numpadKey {
|
|
1323
|
+
height: 52px;
|
|
1324
|
+
border: 1px solid var(--castle-inspector-border);
|
|
1325
|
+
border-radius: 10px;
|
|
1326
|
+
background: var(--castle-inspector-input-bg);
|
|
1327
|
+
color: var(--castle-inspector-text);
|
|
1328
|
+
font-size: 20px;
|
|
1329
|
+
cursor: pointer;
|
|
1330
|
+
touch-action: manipulation;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.numpadKey:active {
|
|
1334
|
+
background: var(--castle-inspector-divider);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.numpadKeyZero {
|
|
1338
|
+
grid-column: span 3;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
.numpadKeyDone {
|
|
1342
|
+
background: rgba(90, 140, 210, 0.9);
|
|
1343
|
+
border-color: transparent;
|
|
1344
|
+
color: #fff;
|
|
1345
|
+
font-size: 16px;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1194
1348
|
.numberStepper:hover {
|
|
1195
1349
|
background: var(--castle-inspector-input-bg);
|
|
1196
1350
|
color: var(--castle-inspector-text);
|
|
@@ -1358,6 +1512,14 @@
|
|
|
1358
1512
|
display: none;
|
|
1359
1513
|
}
|
|
1360
1514
|
|
|
1515
|
+
/* Stack the tool rail and the undo/redo rail vertically (the shared rule above is
|
|
1516
|
+
a flex row, which pushed the second rail out of the 50px column). */
|
|
1517
|
+
.artboardToolColumn {
|
|
1518
|
+
flex-direction: column;
|
|
1519
|
+
align-items: center;
|
|
1520
|
+
gap: 8px;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1361
1523
|
.drawingToolColumn {
|
|
1362
1524
|
display: flex;
|
|
1363
1525
|
align-items: flex-start;
|