castle-web-cli 0.4.123 → 0.4.124
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/castleJson.d.ts +2 -0
- package/dist/castleJson.js +39 -0
- package/dist/headlessCover.d.ts +13 -0
- package/dist/headlessCover.js +99 -0
- package/dist/ide.js +12 -42
- package/dist/imports.js +42 -2
- package/dist/index.js +1 -9
- package/dist/init.d.ts +1 -0
- package/dist/init.js +15 -1
- package/dist/preview.d.ts +0 -1
- package/dist/preview.js +0 -58
- package/dist/save-deck.js +52 -1
- package/dist/serve.js +16 -0
- package/dist/shell/assets/Basteleur-Bold-CK8LF7Pt.woff +0 -0
- package/dist/shell/assets/Basteleur-Bold-DKFKedNb.woff2 +0 -0
- package/dist/shell/assets/index-BfOPkSej.css +1 -0
- package/dist/shell/assets/index-u0nYFqbF.js +434 -0
- package/dist/shell/index.html +2 -2
- package/kits/physics-2d/CLAUDE.md +2 -2
- package/kits/physics-2d/castle.json +10 -2
- package/kits/physics-2d/docs/pxart-format.md +33 -26
- package/kits/physics-2d/editors/PxArtEditor.jsx +120 -49
- package/kits/physics-2d/editors/SingleEditor.jsx +6 -3
- package/kits/physics-2d/editors/StyleEditor.jsx +95 -0
- package/kits/physics-2d/editors/pathOverlay.js +1 -1
- package/kits/physics-2d/editors/pathTools.js +9 -1
- package/kits/physics-2d/editors/pixelGeometry.js +14 -13
- package/kits/physics-2d/editors/pixelInspector.jsx +202 -53
- package/kits/physics-2d/editors/pxArtEditorModel.js +8 -63
- package/kits/physics-2d/editors/pxArtTools.js +3 -43
- package/kits/physics-2d/editors/styleEditor.module.css +105 -0
- package/kits/physics-2d/editors/styleTheme.js +16 -0
- package/kits/physics-2d/engine/files.js +2 -1
- package/kits/physics-2d/engine/liveReload.js +4 -3
- package/kits/physics-2d/engine/palettes.js +636 -0
- package/kits/physics-2d/engine/pxart.js +6 -6
- package/kits/physics-2d/engine/svgImport.js +1056 -0
- package/kits/physics-2d/engine/ui.jsx +2 -0
- package/kits/physics-2d/engine/ui.module.css +54 -9
- package/kits/physics-2d/package-lock.json +1 -1
- package/kits/physics-2d/package.json +1 -0
- package/kits/physics-2d/scripts/deckTheme.mjs +25 -0
- package/kits/physics-2d/scripts/draw.mjs +5 -3
- package/kits/physics-2d/scripts/import-svg.mjs +16 -1069
- package/kits/physics-2d/scripts/palette.mjs +10 -0
- package/kits/physics-2d/scripts/svg-emission-guide.md +5 -3
- package/kits/physics-2d/theme.style +3 -0
- package/package.json +1 -1
- package/dist/shell/assets/index-CARLHafh.css +0 -1
- package/dist/shell/assets/index-DWgt4KzC.js +0 -434
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TRANSPARENT } from '../engine/pxart';
|
|
2
2
|
import {
|
|
3
3
|
forEachDab,
|
|
4
4
|
forEachEllipseCells,
|
|
@@ -7,54 +7,14 @@ import {
|
|
|
7
7
|
forEachTriangleCells,
|
|
8
8
|
} from './pixelGeometry';
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// distinct keys); the transparent cell is the reserved "." (TRANSPARENT).
|
|
13
|
-
export const EDITOR_KEYS = EDG64.map((_, index) => KEY_ALPHABET[index]);
|
|
14
|
-
|
|
15
|
-
// key -> "#rrggbb" record, in palette order. Passed to the SDK's fromCells so
|
|
16
|
-
// serialized files carry only the palette entries actually used.
|
|
17
|
-
export const EDITOR_PALETTE = Object.fromEntries(
|
|
18
|
-
EDG64.map((hex, index) => [EDITOR_KEYS[index], hex])
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
// hex -> palette key, for mapping a loaded sprite's colors onto the swatch.
|
|
22
|
-
const KEY_BY_HEX = new Map(EDG64.map((hex, index) => [hex, EDITOR_KEYS[index]]));
|
|
10
|
+
// Paint helpers are key-agnostic: the editor allocates palette keys on the
|
|
11
|
+
// sprite and passes them in. The reserved "." (TRANSPARENT) is the erase key.
|
|
23
12
|
|
|
24
13
|
// Build a `height x width` matrix of "." (transparent) cells.
|
|
25
14
|
export function blankCells(width, height) {
|
|
26
15
|
return Array.from({ length: height }, () => Array.from({ length: width }, () => TRANSPARENT));
|
|
27
16
|
}
|
|
28
17
|
|
|
29
|
-
// Snap an arbitrary hex color to the nearest palette key by RGB distance, so a
|
|
30
|
-
// loaded sprite whose palette isn't exactly the Endesga-64 set still maps onto
|
|
31
|
-
// the fixed swatch. Returns "." for invalid/transparent input.
|
|
32
|
-
export function editorKeyForHex(hex) {
|
|
33
|
-
const norm = typeof hex === 'string' ? normalizeHex(hex) : null;
|
|
34
|
-
if (!norm) return TRANSPARENT;
|
|
35
|
-
const exact = KEY_BY_HEX.get('#' + norm.slice(1, 7));
|
|
36
|
-
if (exact) return exact;
|
|
37
|
-
const r = parseInt(norm.slice(1, 3), 16);
|
|
38
|
-
const g = parseInt(norm.slice(3, 5), 16);
|
|
39
|
-
const b = parseInt(norm.slice(5, 7), 16);
|
|
40
|
-
let bestKey = EDITOR_KEYS[0];
|
|
41
|
-
let bestDist = Infinity;
|
|
42
|
-
EDG64.forEach((swatch, index) => {
|
|
43
|
-
const dist =
|
|
44
|
-
(r - parseInt(swatch.slice(1, 3), 16)) ** 2 +
|
|
45
|
-
(g - parseInt(swatch.slice(3, 5), 16)) ** 2 +
|
|
46
|
-
(b - parseInt(swatch.slice(5, 7), 16)) ** 2;
|
|
47
|
-
if (dist < bestDist) {
|
|
48
|
-
bestDist = dist;
|
|
49
|
-
bestKey = EDITOR_KEYS[index];
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
return bestKey;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// The swatch the editor opens on: Endesga-64's lead bright red (#ff0040).
|
|
56
|
-
export const DEFAULT_KEY = editorKeyForHex('#ff0040');
|
|
57
|
-
|
|
58
18
|
function isInside(cells, x, y) {
|
|
59
19
|
return y >= 0 && y < cells.length && x >= 0 && x < (cells[0]?.length ?? 0);
|
|
60
20
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* Palette picker for theme.style. Tokens match engine/ui.module.css. */
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
flex: 1 1 auto;
|
|
5
|
+
min-width: 0;
|
|
6
|
+
min-height: 0;
|
|
7
|
+
width: 100%;
|
|
8
|
+
overflow-x: hidden;
|
|
9
|
+
overflow-y: auto;
|
|
10
|
+
overscroll-behavior: contain;
|
|
11
|
+
padding: 16px 18px 24px;
|
|
12
|
+
color: var(--castle-inspector-text, #e6e6e6);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.replaceNote {
|
|
16
|
+
margin: 8px 0 16px;
|
|
17
|
+
font-size: 12px;
|
|
18
|
+
line-height: 1.4;
|
|
19
|
+
color: var(--castle-inspector-muted, #888);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.list {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: 10px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.card {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
gap: 8px;
|
|
32
|
+
width: 100%;
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 12px;
|
|
35
|
+
border: 1px solid var(--castle-inspector-border, #2a2a2a);
|
|
36
|
+
border-radius: var(--castle-radius, 4px);
|
|
37
|
+
background: var(--castle-card, #121213);
|
|
38
|
+
color: inherit;
|
|
39
|
+
text-align: left;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.card:hover {
|
|
44
|
+
background: var(--castle-panel-raised, #101010);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.cardSelected {
|
|
48
|
+
border-color: var(--castle-selected, #e6e6e6);
|
|
49
|
+
box-shadow: inset 0 0 0 1px var(--castle-selected, #e6e6e6);
|
|
50
|
+
background: var(--castle-panel-raised, #101010);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.cardStatic {
|
|
54
|
+
cursor: default;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.cardStatic:hover {
|
|
58
|
+
background: var(--castle-card, #121213);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.cardSelected.cardStatic:hover {
|
|
62
|
+
background: var(--castle-panel-raised, #101010);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.cardHead {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: baseline;
|
|
68
|
+
justify-content: space-between;
|
|
69
|
+
gap: 12px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.cardName {
|
|
73
|
+
font-size: 14px;
|
|
74
|
+
line-height: 1.2;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.cardMeta {
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 8px;
|
|
81
|
+
font-size: 11px;
|
|
82
|
+
color: var(--castle-inspector-muted, #888);
|
|
83
|
+
white-space: nowrap;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.badge {
|
|
87
|
+
padding: 1px 6px;
|
|
88
|
+
border: 1px solid var(--castle-inspector-border, #2a2a2a);
|
|
89
|
+
border-radius: 999px;
|
|
90
|
+
color: var(--castle-inspector-text, #e6e6e6);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.strip {
|
|
94
|
+
display: flex;
|
|
95
|
+
height: 18px;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
border: 1px solid var(--castle-inspector-border, #2a2a2a);
|
|
98
|
+
border-radius: 2px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.chip {
|
|
102
|
+
flex: 1 1 0;
|
|
103
|
+
min-width: 2px;
|
|
104
|
+
height: 100%;
|
|
105
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Read-modify-write for theme.style. Parse the existing JSON, set `palette`
|
|
2
|
+
// to an official id, and serialize — unknown top-level keys survive.
|
|
3
|
+
|
|
4
|
+
import { parseThemeData } from '../engine/palettes.js';
|
|
5
|
+
|
|
6
|
+
export function formatThemeJson(value) {
|
|
7
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Replace `palette` with an official id. Empty / unparseable text starts `{}`. */
|
|
11
|
+
export function applyOfficialPalette(text, paletteId) {
|
|
12
|
+
const parsed = parseThemeData(text);
|
|
13
|
+
const data = parsed ? { ...parsed } : {};
|
|
14
|
+
data.palette = paletteId;
|
|
15
|
+
return formatThemeJson(data);
|
|
16
|
+
}
|
|
@@ -25,7 +25,7 @@ const rawModules = {
|
|
|
25
25
|
{ query: '?raw', import: 'default', eager: true }
|
|
26
26
|
),
|
|
27
27
|
...import.meta.glob(
|
|
28
|
-
['/scenes/**/*.scene', '/blueprints/**/*.scene', '/drawings/**/*.pxart', '/drawings/**/*.sprite', '/behaviors/**/*.jsx'],
|
|
28
|
+
['/scenes/**/*.scene', '/blueprints/**/*.scene', '/drawings/**/*.pxart', '/drawings/**/*.sprite', '/behaviors/**/*.jsx', '/theme.style'],
|
|
29
29
|
{ query: '?raw', import: 'default', eager: true }
|
|
30
30
|
),
|
|
31
31
|
};
|
|
@@ -102,6 +102,7 @@ export function getFileKind(path) {
|
|
|
102
102
|
if (path.endsWith('.scene')) return 'scene';
|
|
103
103
|
if (path.endsWith('.sprite')) return 'pxart';
|
|
104
104
|
if (path.endsWith('.pxart')) return 'pxart';
|
|
105
|
+
if (path.endsWith('.style')) return 'style';
|
|
105
106
|
if (path.endsWith('.png')) return 'image';
|
|
106
107
|
if (path.endsWith('.jpg')) return 'image';
|
|
107
108
|
if (path.endsWith('.jpeg')) return 'image';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Consumer side of the serve's `files_changed` broadcast. The engine decides
|
|
2
2
|
// what a change means for this bundle:
|
|
3
|
-
// - data files (.scene / .pxart / .drawing) are re-read
|
|
4
|
-
// files API and folded into React state — live re-render,
|
|
3
|
+
// - data files (.scene / .pxart / .sprite / .style / .drawing) are re-read
|
|
4
|
+
// over the serve's files API and folded into React state — live re-render,
|
|
5
|
+
// no reload.
|
|
5
6
|
// - code the bundle imports (per the event's module-graph closure), html,
|
|
6
7
|
// or added/deleted code files (import.meta.glob may pick them up) mean
|
|
7
8
|
// this context is stale — requestReload() (save-state hooks run first).
|
|
@@ -9,7 +10,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
9
10
|
import { onFilesChanged, requestReload } from 'castle-web-sdk';
|
|
10
11
|
import { initialFiles } from './files';
|
|
11
12
|
|
|
12
|
-
const DATA_EXTS = ['.scene', '.pxart', '.sprite', '.drawing'];
|
|
13
|
+
const DATA_EXTS = ['.scene', '.pxart', '.sprite', '.style', '.drawing'];
|
|
13
14
|
const CODE_EXTS = ['.js', '.jsx', '.ts', '.tsx', '.css'];
|
|
14
15
|
|
|
15
16
|
export function isDataFile(path) {
|