@loworbitstudio/visor-theme-engine 0.1.0

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.
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Font resolution types for the Visor theme engine.
3
+ */
4
+ /** Where a font is loaded from */
5
+ type FontSource = "google-fonts" | "visor-fonts" | "local";
6
+ /** CSS font-display strategy */
7
+ type FontDisplayStrategy = "swap" | "block" | "fallback" | "optional" | "auto";
8
+ /** A single resolved font */
9
+ interface FontResolution {
10
+ /** The font family name as specified */
11
+ family: string;
12
+ /** Where this font comes from */
13
+ source: FontSource;
14
+ /** Google Fonts CSS URL (only for google-fonts source) */
15
+ cssUrl: string | null;
16
+ /** Weights available/requested for this font */
17
+ weights: number[];
18
+ /** Whether italic styles are available/requested */
19
+ italic: boolean;
20
+ /** The font-display strategy to use */
21
+ display: FontDisplayStrategy;
22
+ /** Font category (sans-serif, serif, monospace, etc.) for fallback selection */
23
+ category: string;
24
+ /** Human-readable message for local fonts needing manual setup */
25
+ guidance: string | null;
26
+ /** Organization namespace (only for visor-fonts source) */
27
+ org: string | null;
28
+ }
29
+ /** Options for resolving a single font */
30
+ interface FontResolveOptions {
31
+ /** Weights to include (default: [400, 700]) */
32
+ weights?: number[];
33
+ /** Include italic variants (default: false) */
34
+ italic?: boolean;
35
+ /** font-display strategy (default: "swap") */
36
+ display?: FontDisplayStrategy;
37
+ /** Font category override for custom fonts (default: "sans-serif") */
38
+ category?: string;
39
+ /** Font source override — skips Google Fonts lookup when set */
40
+ source?: FontSource;
41
+ /** Organization namespace for visor-fonts CDN (required when source is "visor-fonts") */
42
+ org?: string;
43
+ }
44
+ /** Typography section from a .visor.yaml file */
45
+ interface VisorTypography {
46
+ heading?: {
47
+ family: string;
48
+ weight?: number;
49
+ source?: FontSource;
50
+ org?: string;
51
+ };
52
+ display?: {
53
+ family: string;
54
+ weight?: number;
55
+ source?: FontSource;
56
+ org?: string;
57
+ };
58
+ body?: {
59
+ family: string;
60
+ weight?: number;
61
+ source?: FontSource;
62
+ org?: string;
63
+ };
64
+ mono?: {
65
+ family: string;
66
+ weight?: number;
67
+ source?: FontSource;
68
+ org?: string;
69
+ };
70
+ "letter-spacing"?: {
71
+ tight?: string;
72
+ normal?: string;
73
+ wide?: string;
74
+ };
75
+ }
76
+ /** Result of resolving all fonts for a theme */
77
+ interface ThemeFontResult {
78
+ /** Resolved heading font (if specified) */
79
+ heading: FontResolution | null;
80
+ /** Resolved display font (if specified) */
81
+ display: FontResolution | null;
82
+ /** Resolved body font (if specified) */
83
+ body: FontResolution | null;
84
+ /** Resolved monospace font (if specified) */
85
+ mono: FontResolution | null;
86
+ /** Preconnect and preload link tags */
87
+ preloadLinks: string[];
88
+ /** CSS @font-face + custom property overrides */
89
+ css: string;
90
+ /** Warnings for fonts needing manual setup */
91
+ warnings: string[];
92
+ }
93
+ /** Entry in the Google Fonts catalog */
94
+ interface GoogleFontEntry {
95
+ family: string;
96
+ weights: number[];
97
+ styles: string[];
98
+ category: string;
99
+ }
100
+
101
+ /**
102
+ * Types for the Visor Theme Engine
103
+ *
104
+ * Covers the full pipeline: .visor.yaml config → shade scales → semantic tokens → CSS output.
105
+ */
106
+
107
+ type ShadeStep = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
108
+ type ColorRole = "primary" | "accent" | "neutral" | "success" | "warning" | "error" | "info";
109
+ type FullShadeScale = Record<ShadeStep, string>;
110
+ type SelectiveShadeScale = Record<50 | 100 | 500 | 600 | 700 | 900, string>;
111
+ type RGB = [number, number, number];
112
+ type OKLCH = [number, number, number];
113
+ type RGBA = [number, number, number, number];
114
+ type ColorFormat = "hex" | "rgba" | "hsla" | "oklch";
115
+ interface ParsedColor {
116
+ rgb: RGB;
117
+ alpha?: number;
118
+ format: ColorFormat;
119
+ original: string;
120
+ }
121
+ interface VisorThemeConfig {
122
+ name: string;
123
+ version: 1;
124
+ /** Theme group for the docs site theme switcher (e.g. 'Visor', 'Client', 'Low Orbit'). Used by `visor theme sync`. */
125
+ group?: string;
126
+ colors: {
127
+ primary: string;
128
+ accent?: string;
129
+ neutral?: string;
130
+ background?: string;
131
+ surface?: string;
132
+ success?: string;
133
+ warning?: string;
134
+ error?: string;
135
+ info?: string;
136
+ };
137
+ "colors-dark"?: {
138
+ primary?: string;
139
+ accent?: string;
140
+ neutral?: string;
141
+ background?: string;
142
+ surface?: string;
143
+ success?: string;
144
+ warning?: string;
145
+ error?: string;
146
+ info?: string;
147
+ };
148
+ typography?: {
149
+ scale?: number;
150
+ heading?: {
151
+ family?: string;
152
+ weight?: number;
153
+ source?: FontSource;
154
+ org?: string;
155
+ };
156
+ display?: {
157
+ family?: string;
158
+ weight?: number;
159
+ source?: FontSource;
160
+ org?: string;
161
+ };
162
+ body?: {
163
+ family?: string;
164
+ weight?: number;
165
+ source?: FontSource;
166
+ org?: string;
167
+ };
168
+ mono?: {
169
+ family?: string;
170
+ weight?: number;
171
+ source?: FontSource;
172
+ org?: string;
173
+ };
174
+ "letter-spacing"?: {
175
+ tight?: string;
176
+ normal?: string;
177
+ wide?: string;
178
+ };
179
+ };
180
+ spacing?: {
181
+ base?: number;
182
+ };
183
+ radius?: {
184
+ sm?: number;
185
+ md?: number;
186
+ lg?: number;
187
+ xl?: number;
188
+ pill?: number;
189
+ };
190
+ shadows?: {
191
+ xs?: string;
192
+ sm?: string;
193
+ md?: string;
194
+ lg?: string;
195
+ xl?: string;
196
+ };
197
+ motion?: {
198
+ "duration-fast"?: string;
199
+ "duration-normal"?: string;
200
+ "duration-slow"?: string;
201
+ easing?: string;
202
+ };
203
+ overrides?: {
204
+ light?: Record<string, string>;
205
+ dark?: Record<string, string>;
206
+ };
207
+ }
208
+ /** Config with all defaults resolved */
209
+ interface ResolvedThemeConfig {
210
+ name: string;
211
+ version: 1;
212
+ colors: {
213
+ primary: string;
214
+ accent: string;
215
+ neutral: string | null;
216
+ background: string;
217
+ surface: string;
218
+ success: string;
219
+ warning: string;
220
+ error: string;
221
+ info: string;
222
+ };
223
+ "colors-dark"?: VisorThemeConfig["colors-dark"];
224
+ typography: {
225
+ scale: number;
226
+ heading: {
227
+ family: string;
228
+ weight: number;
229
+ source?: FontSource;
230
+ org?: string;
231
+ };
232
+ display: {
233
+ family: string;
234
+ weight: number;
235
+ source?: FontSource;
236
+ org?: string;
237
+ };
238
+ body: {
239
+ family: string;
240
+ weight: number;
241
+ source?: FontSource;
242
+ org?: string;
243
+ };
244
+ mono: {
245
+ family: string;
246
+ };
247
+ };
248
+ spacing: {
249
+ base: number;
250
+ };
251
+ radius: {
252
+ sm: number;
253
+ md: number;
254
+ lg: number;
255
+ xl: number;
256
+ pill: number;
257
+ };
258
+ shadows: {
259
+ xs: string;
260
+ sm: string;
261
+ md: string;
262
+ lg: string;
263
+ xl: string;
264
+ };
265
+ motion: {
266
+ "duration-fast": string;
267
+ "duration-normal": string;
268
+ "duration-slow": string;
269
+ easing: string;
270
+ };
271
+ overrides?: {
272
+ light?: Record<string, string>;
273
+ dark?: Record<string, string>;
274
+ };
275
+ /** Original color strings from user input, for round-trip export. */
276
+ originalColors?: Record<string, string>;
277
+ /** Color format of each user-provided color, keyed by field name. */
278
+ colorFormats?: Record<string, ColorFormat>;
279
+ }
280
+ interface GeneratedPrimitives {
281
+ primary: FullShadeScale;
282
+ accent: FullShadeScale;
283
+ neutral: FullShadeScale;
284
+ success: SelectiveShadeScale;
285
+ warning: SelectiveShadeScale;
286
+ error: SelectiveShadeScale;
287
+ info: SelectiveShadeScale;
288
+ }
289
+ interface SemanticTokenValue {
290
+ light: string;
291
+ dark: string;
292
+ }
293
+ interface SemanticTokens {
294
+ text: Record<string, SemanticTokenValue>;
295
+ surface: Record<string, SemanticTokenValue>;
296
+ border: Record<string, SemanticTokenValue>;
297
+ interactive: Record<string, SemanticTokenValue>;
298
+ }
299
+ interface ThemeOutput {
300
+ primitivesCss: string;
301
+ semanticCss: string;
302
+ lightCss: string;
303
+ darkCss: string;
304
+ fullBundleCss: string;
305
+ }
306
+ /** Full pipeline result including intermediate artifacts for adapter consumption. */
307
+ interface ThemeData {
308
+ config: ResolvedThemeConfig;
309
+ primitives: GeneratedPrimitives;
310
+ tokens: SemanticTokens;
311
+ output: ThemeOutput;
312
+ }
313
+
314
+ export type { ColorRole as C, FontResolveOptions as F, GoogleFontEntry as G, OKLCH as O, ParsedColor as P, ResolvedThemeConfig as R, SelectiveShadeScale as S, ThemeFontResult as T, VisorTypography as V, FontResolution as a, FontDisplayStrategy as b, GeneratedPrimitives as c, ThemeOutput as d, ThemeData as e, VisorThemeConfig as f, FullShadeScale as g, RGB as h, SemanticTokens as i, ShadeStep as j, ColorFormat as k, FontSource as l, RGBA as m, SemanticTokenValue as n };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@loworbitstudio/visor-theme-engine",
3
+ "version": "0.1.0",
4
+ "description": "Theme engine for the Visor design system — shade generation, token mapping, font resolution, and import/export for .visor.yaml themes.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./adapters": {
14
+ "import": "./dist/adapters/index.js",
15
+ "types": "./dist/adapters/index.d.ts"
16
+ },
17
+ "./fowt": {
18
+ "import": "./dist/fowt.js",
19
+ "types": "./dist/fowt.d.ts"
20
+ },
21
+ "./schema": "./src/visor-theme.schema.json"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "vitest run",
27
+ "update-catalog": "tsx scripts/update-google-fonts-catalog.ts"
28
+ },
29
+ "files": [
30
+ "dist/",
31
+ "src/visor-theme.schema.json"
32
+ ],
33
+ "keywords": [
34
+ "visor",
35
+ "design-system",
36
+ "theme",
37
+ "oklch",
38
+ "tokens"
39
+ ],
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/low-orbit-studio/visor.git"
44
+ },
45
+ "homepage": "https://visor.loworbit.studio",
46
+ "bugs": {
47
+ "url": "https://github.com/low-orbit-studio/visor/issues"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "dependencies": {
53
+ "yaml": "^2.8.3"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.0.0",
57
+ "tsup": "^8.4.0",
58
+ "tsx": "^4.19.2",
59
+ "typescript": "^5.7.2",
60
+ "vitest": "^4.1.2"
61
+ }
62
+ }
@@ -0,0 +1,282 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://visor.loworbit.studio/schemas/visor-theme.schema.json",
4
+ "title": "Visor Theme",
5
+ "description": "Schema for .visor.yaml theme files — portable visual identity definitions for the Visor design system.",
6
+ "type": "object",
7
+ "required": ["name", "version", "colors"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "name": {
11
+ "type": "string",
12
+ "minLength": 1,
13
+ "description": "Human-readable theme name."
14
+ },
15
+ "version": {
16
+ "const": 1,
17
+ "description": "Schema version. Currently always 1."
18
+ },
19
+ "group": {
20
+ "type": "string",
21
+ "description": "Theme group for the docs site theme switcher (e.g. 'Visor', 'Client', 'Low Orbit'). Used by `visor theme sync`. Defaults to folder-based grouping when omitted."
22
+ },
23
+ "colors": {
24
+ "type": "object",
25
+ "description": "Color definitions for light mode. Only primary is required — all others have sensible defaults.",
26
+ "required": ["primary"],
27
+ "additionalProperties": false,
28
+ "properties": {
29
+ "primary": {
30
+ "$ref": "#/$defs/cssColor",
31
+ "description": "Brand color. Drives interactive-*, accent-*, text-link, border-focus tokens. Anchored at shade 600."
32
+ },
33
+ "accent": {
34
+ "$ref": "#/$defs/cssColor",
35
+ "description": "Secondary brand color. Defaults to primary if omitted."
36
+ },
37
+ "neutral": {
38
+ "$ref": "#/$defs/cssColor",
39
+ "description": "Base neutral color for generating the gray scale (50-950). Defaults to Tailwind Gray if omitted."
40
+ },
41
+ "background": {
42
+ "$ref": "#/$defs/cssColor",
43
+ "description": "Page background for light mode. Default: #FFFFFF."
44
+ },
45
+ "surface": {
46
+ "$ref": "#/$defs/cssColor",
47
+ "description": "Card/panel background for light mode. Default: #FFFFFF."
48
+ },
49
+ "success": {
50
+ "$ref": "#/$defs/cssColor",
51
+ "description": "Success status color. Default: #22C55E (Tailwind green-500)."
52
+ },
53
+ "warning": {
54
+ "$ref": "#/$defs/cssColor",
55
+ "description": "Warning status color. Default: #F59E0B (Tailwind amber-500)."
56
+ },
57
+ "error": {
58
+ "$ref": "#/$defs/cssColor",
59
+ "description": "Error status color. Default: #EF4444 (Tailwind red-500)."
60
+ },
61
+ "info": {
62
+ "$ref": "#/$defs/cssColor",
63
+ "description": "Info status color. Default: #0EA5E9 (Tailwind sky-500)."
64
+ }
65
+ }
66
+ },
67
+ "colors-dark": {
68
+ "type": "object",
69
+ "description": "Dark mode color overrides. Any key from colors can be overridden. Omitted keys inherit from the derived dark mode values (not from light mode).",
70
+ "additionalProperties": false,
71
+ "properties": {
72
+ "primary": { "$ref": "#/$defs/cssColor" },
73
+ "accent": { "$ref": "#/$defs/cssColor" },
74
+ "neutral": { "$ref": "#/$defs/cssColor" },
75
+ "background": { "$ref": "#/$defs/cssColor" },
76
+ "surface": { "$ref": "#/$defs/cssColor" },
77
+ "success": { "$ref": "#/$defs/cssColor" },
78
+ "warning": { "$ref": "#/$defs/cssColor" },
79
+ "error": { "$ref": "#/$defs/cssColor" },
80
+ "info": { "$ref": "#/$defs/cssColor" }
81
+ }
82
+ },
83
+ "typography": {
84
+ "type": "object",
85
+ "description": "Typography configuration. Defaults to system font stacks if omitted.",
86
+ "additionalProperties": false,
87
+ "properties": {
88
+ "heading": {
89
+ "type": "object",
90
+ "additionalProperties": false,
91
+ "properties": {
92
+ "family": {
93
+ "type": "string",
94
+ "description": "Google Fonts family name or CSS font stack."
95
+ },
96
+ "weight": {
97
+ "type": "integer",
98
+ "minimum": 100,
99
+ "maximum": 900,
100
+ "description": "Font weight for headings. Default: 600."
101
+ },
102
+ "source": {
103
+ "type": "string",
104
+ "enum": ["google-fonts", "visor-fonts", "local"],
105
+ "description": "Font source. Defaults to Google Fonts lookup, then local."
106
+ },
107
+ "org": {
108
+ "type": "string",
109
+ "description": "Organization namespace for visor-fonts CDN (required when source is visor-fonts)."
110
+ }
111
+ }
112
+ },
113
+ "display": {
114
+ "type": "object",
115
+ "description": "Display/decorative font for hero text and splash screens. Falls back to heading font if omitted.",
116
+ "additionalProperties": false,
117
+ "properties": {
118
+ "family": {
119
+ "type": "string",
120
+ "description": "Google Fonts family name or CSS font stack."
121
+ },
122
+ "weight": {
123
+ "type": "integer",
124
+ "minimum": 100,
125
+ "maximum": 900,
126
+ "description": "Font weight for display text. Default: 400."
127
+ },
128
+ "source": {
129
+ "type": "string",
130
+ "enum": ["google-fonts", "visor-fonts", "local"],
131
+ "description": "Font source. Defaults to Google Fonts lookup, then local."
132
+ },
133
+ "org": {
134
+ "type": "string",
135
+ "description": "Organization namespace for visor-fonts CDN (required when source is visor-fonts)."
136
+ }
137
+ }
138
+ },
139
+ "body": {
140
+ "type": "object",
141
+ "additionalProperties": false,
142
+ "properties": {
143
+ "family": {
144
+ "type": "string",
145
+ "description": "Google Fonts family name or CSS font stack."
146
+ },
147
+ "weight": {
148
+ "type": "integer",
149
+ "minimum": 100,
150
+ "maximum": 900,
151
+ "description": "Font weight for body text. Default: 400."
152
+ },
153
+ "source": {
154
+ "type": "string",
155
+ "enum": ["google-fonts", "visor-fonts", "local"],
156
+ "description": "Font source. Defaults to Google Fonts lookup, then local."
157
+ },
158
+ "org": {
159
+ "type": "string",
160
+ "description": "Organization namespace for visor-fonts CDN (required when source is visor-fonts)."
161
+ }
162
+ }
163
+ },
164
+ "mono": {
165
+ "type": "object",
166
+ "additionalProperties": false,
167
+ "properties": {
168
+ "family": {
169
+ "type": "string",
170
+ "description": "Google Fonts family name or CSS font stack for monospace."
171
+ }
172
+ }
173
+ },
174
+ "scale": {
175
+ "type": "number",
176
+ "description": "Type scale multiplier applied to the font-size ramp. Default: 1."
177
+ },
178
+ "letter-spacing": {
179
+ "type": "object",
180
+ "description": "Letter spacing scale.",
181
+ "additionalProperties": false,
182
+ "properties": {
183
+ "tight": { "type": "string" },
184
+ "normal": { "type": "string" },
185
+ "wide": { "type": "string" }
186
+ }
187
+ }
188
+ }
189
+ },
190
+ "spacing": {
191
+ "type": "object",
192
+ "description": "Spacing configuration.",
193
+ "additionalProperties": false,
194
+ "properties": {
195
+ "base": {
196
+ "type": "number",
197
+ "minimum": 1,
198
+ "description": "Base spacing unit in pixels. Default: 4. Generates the full spacing scale."
199
+ }
200
+ }
201
+ },
202
+ "radius": {
203
+ "type": "object",
204
+ "description": "Border radius scale in pixels.",
205
+ "additionalProperties": false,
206
+ "properties": {
207
+ "sm": { "type": "number", "minimum": 0 },
208
+ "md": { "type": "number", "minimum": 0 },
209
+ "lg": { "type": "number", "minimum": 0 },
210
+ "xl": { "type": "number", "minimum": 0 },
211
+ "pill": { "type": "number", "minimum": 0, "description": "Default: 9999." }
212
+ }
213
+ },
214
+ "shadows": {
215
+ "type": "object",
216
+ "description": "Named shadow definitions as CSS box-shadow values.",
217
+ "additionalProperties": false,
218
+ "properties": {
219
+ "xs": { "type": "string" },
220
+ "sm": { "type": "string" },
221
+ "md": { "type": "string" },
222
+ "lg": { "type": "string" },
223
+ "xl": { "type": "string" }
224
+ }
225
+ },
226
+ "motion": {
227
+ "type": "object",
228
+ "description": "Motion/animation configuration.",
229
+ "additionalProperties": false,
230
+ "properties": {
231
+ "duration-fast": {
232
+ "type": "string",
233
+ "pattern": "^\\d+ms$",
234
+ "description": "Fast duration for micro-interactions. Default: 100ms."
235
+ },
236
+ "duration-normal": {
237
+ "type": "string",
238
+ "pattern": "^\\d+ms$",
239
+ "description": "Normal duration for standard transitions. Default: 200ms."
240
+ },
241
+ "duration-slow": {
242
+ "type": "string",
243
+ "pattern": "^\\d+ms$",
244
+ "description": "Slow duration for larger animations. Default: 500ms."
245
+ },
246
+ "easing": {
247
+ "type": "string",
248
+ "description": "Default easing curve as a CSS timing function."
249
+ }
250
+ }
251
+ },
252
+ "overrides": {
253
+ "type": "object",
254
+ "description": "Per-token escape hatch. Values replace derived tokens. Use CSS custom property names without the -- prefix as keys.",
255
+ "additionalProperties": false,
256
+ "properties": {
257
+ "light": {
258
+ "type": "object",
259
+ "description": "Token overrides applied in light mode.",
260
+ "additionalProperties": { "type": "string" }
261
+ },
262
+ "dark": {
263
+ "type": "object",
264
+ "description": "Token overrides applied in dark mode.",
265
+ "additionalProperties": { "type": "string" }
266
+ }
267
+ }
268
+ }
269
+ },
270
+ "$defs": {
271
+ "cssColor": {
272
+ "type": "string",
273
+ "description": "CSS color value. Accepts hex (#RGB, #RRGGBB, #RRGGBBAA), rgb()/rgba(), hsl()/hsla(), or oklch() formats.",
274
+ "anyOf": [
275
+ { "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$" },
276
+ { "pattern": "^rgba?\\(" },
277
+ { "pattern": "^hsla?\\(" },
278
+ { "pattern": "^oklch\\(" }
279
+ ]
280
+ }
281
+ }
282
+ }