Canvas-only 2D game editor/runtime built on PixiJS v8 + Vite + TypeScript. - core: asset model (Scene/Camera/Sprite), scene objects with sparse overrides, property schema + resolve, command stack (undo/redo), serialization and project store interface (16 unit tests) - ui: custom in-canvas widget toolkit (panels, fields, color picker, list/scroll, popup) with a hidden <input> keyboard bridge - editor: asset/hierarchy panels, schema-driven inspector, toolbar - viewport: box rendering, camera frame, selection gizmo, drag/zoom/pan - runtime: world/entity instantiation for play mode, input, camera follow - api: PHP JSON project store with revision counter (409 on conflict) - tools: CDP-based e2e smoke test Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
71 lines
2.1 KiB
TypeScript
71 lines
2.1 KiB
TypeScript
import { TextStyle } from 'pixi.js';
|
|
|
|
/** Design tokens for the canvas UI. Widgets never hard-code colors or sizes. */
|
|
export const Theme = {
|
|
font: 'Inter, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
monoFont: '"JetBrains Mono", Consolas, "Courier New", monospace',
|
|
fontSize: 13,
|
|
smallFontSize: 11,
|
|
rowHeight: 26,
|
|
controlHeight: 24,
|
|
padding: 8,
|
|
gap: 6,
|
|
radius: 4,
|
|
colors: {
|
|
background: 0x1b1d22,
|
|
panel: 0x24272e,
|
|
panelHeader: 0x2c3038,
|
|
border: 0x383c46,
|
|
control: 0x16181d,
|
|
controlHover: 0x1e2127,
|
|
controlActive: 0x2a2e37,
|
|
accent: 0x4c8dff,
|
|
accentText: 0xffffff,
|
|
text: 0xe6e8ee,
|
|
textMuted: 0x8a90a0,
|
|
textDisabled: 0x5a5f6d,
|
|
selection: 0x2b4a80,
|
|
override: 0xf0b13d,
|
|
danger: 0xe0553f,
|
|
viewport: 0x121316,
|
|
grid: 0x1f2126,
|
|
gizmo: 0x4c8dff,
|
|
},
|
|
} as const;
|
|
|
|
const styleCache = new Map<string, TextStyle>();
|
|
|
|
export type TextStyleToken = 'body' | 'bodyBold' | 'muted' | 'small' | 'title' | 'mono' | 'accent';
|
|
|
|
/** Shared TextStyle instances so Pixi can cache glyph measurements per token. */
|
|
export function textStyle(token: TextStyleToken = 'body'): TextStyle {
|
|
let style = styleCache.get(token);
|
|
if (style) return style;
|
|
const base = { fontFamily: Theme.font, fontSize: Theme.fontSize, fill: Theme.colors.text };
|
|
switch (token) {
|
|
case 'body':
|
|
style = new TextStyle(base);
|
|
break;
|
|
case 'bodyBold':
|
|
style = new TextStyle({ ...base, fontWeight: '600' });
|
|
break;
|
|
case 'muted':
|
|
style = new TextStyle({ ...base, fill: Theme.colors.textMuted });
|
|
break;
|
|
case 'small':
|
|
style = new TextStyle({ ...base, fontSize: Theme.smallFontSize, fill: Theme.colors.textMuted });
|
|
break;
|
|
case 'title':
|
|
style = new TextStyle({ ...base, fontSize: Theme.smallFontSize, fontWeight: '700', letterSpacing: 0.5, fill: Theme.colors.textMuted });
|
|
break;
|
|
case 'mono':
|
|
style = new TextStyle({ ...base, fontFamily: Theme.monoFont });
|
|
break;
|
|
case 'accent':
|
|
style = new TextStyle({ ...base, fill: Theme.colors.accentText, fontWeight: '600' });
|
|
break;
|
|
}
|
|
styleCache.set(token, style);
|
|
return style;
|
|
}
|