/** Design tokens for the canvas UI. Sizes only one widget uses may stay local to it. */ import { TextStyle } from 'pixi.js'; export const Theme = { font: 'Inter, "Segoe UI", Roboto, Helvetica, Arial, sans-serif', monoFont: '"JetBrains Mono", Consolas, "Courier New", monospace', fontSize: 13, smallFontSize: 11, viewportLabelSize: 10, rowHeight: 26, controlHeight: 24, padding: 8, gap: 6, radius: 4, fieldPadding: 6, checkboxSize: 16, swatchSize: 12, scrollbarWidth: 6, /** Horizontal indent per nesting level in tree lists. */ treeIndent: 14, hoverCursor: 'default', colors: { background: 0x1b1d22, panel: 0x24272e, panelHeader: 0x2c3038, border: 0x383c46, control: 0x16181d, controlHover: 0x1e2127, controlActive: 0x16181d, accent: 0x4c8dff, accentText: 0xffffff, text: 0xe6e8ee, textMuted: 0x8a90a0, selection: 0x2b4a80, danger: 0xe0553f, viewport: 0x121316, gizmo: 0x4c8dff, /** Behavior symbols and owner links. */ behavior: 0xb066ff, /** Collider outlines: solid (blocking) and trigger (events only). */ collider: 0x4fd18b, trigger: 0xf2c84b, container: 0x3fb8c9, /** Particle source icon and owner links; the same orange the graph gives the category. */ particles: 0xc26a1f, marquee: 0x4c8dff, /** A value deviating from what it follows: an override on a placed object, a non-default on an asset. */ override: 0xf2c84b, /** Node graph: canvas, node chrome, per-category title colors, port colors, live highlight. */ graphBackground: 0x15171b, node: 0x262a32, nodeBorder: 0x3b404b, nodeSelected: 0x4c8dff, categoryEvent: 0xc0392b, categoryFlow: 0x7f8c8d, categoryData: 0x2e8b57, categoryVariable: 0xb8860b, categoryAction: 0x2f6fd6, categoryState: 0x8e5ad6, categoryShader: 0x1f8f8f, categoryParticles: 0xc26a1f, portExec: 0xffffff, portNumber: 0x6fcf97, portBoolean: 0xe57373, portString: 0xe6a8ff, portObject: 0x56ccf2, portList: 0xf2a75c, portStruct: 0xd4c25a, portVec3: 0xff9ad5, portAny: 0xbdbdbd, /** Ring around the port a running edge drag would connect to. */ portTarget: 0xffffff, /** Groups and comments that were never given a colour of their own. */ annotation: 0x3c4352, hot: 0xffc857, }, } as const; const styleCache = new Map(); export type TextStyleToken = | 'body' | 'bodyBold' | 'muted' | 'override' | 'small' | 'warning' | 'live' | 'title' | 'mono' | 'monoMuted' | 'monoWarn' | 'monoError' | 'accent' | 'viewportLabel' | 'viewportLabelMuted'; /** 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 'override': style = new TextStyle({ ...base, fill: Theme.colors.override }); break; case 'small': style = new TextStyle({ ...base, fontSize: Theme.smallFontSize, fill: Theme.colors.textMuted }); break; case 'warning': style = new TextStyle({ ...base, fontSize: Theme.smallFontSize, fill: Theme.colors.danger }); break; case 'live': style = new TextStyle({ ...base, fontSize: Theme.smallFontSize, fill: Theme.colors.hot }); 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 'monoMuted': style = new TextStyle({ ...base, fontFamily: Theme.monoFont, fill: Theme.colors.textMuted }); break; case 'monoWarn': style = new TextStyle({ ...base, fontFamily: Theme.monoFont, fill: Theme.colors.override }); break; case 'monoError': style = new TextStyle({ ...base, fontFamily: Theme.monoFont, fill: Theme.colors.danger }); break; case 'accent': style = new TextStyle({ ...base, fill: Theme.colors.accentText, fontWeight: '600' }); break; case 'viewportLabel': style = new TextStyle({ ...base, fontSize: Theme.viewportLabelSize }); break; case 'viewportLabelMuted': style = new TextStyle({ ...base, fontSize: Theme.viewportLabelSize, fill: Theme.colors.textMuted }); break; } styleCache.set(token, style); return style; }