@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.
- package/dist/adapters/index.d.ts +150 -0
- package/dist/adapters/index.js +537 -0
- package/dist/chunk-ZLXFCNYF.js +1399 -0
- package/dist/fowt.d.ts +37 -0
- package/dist/fowt.js +23 -0
- package/dist/index.d.ts +799 -0
- package/dist/index.js +2414 -0
- package/dist/types-r7ae3WP2.d.ts +314 -0
- package/package.json +62 -0
- package/src/visor-theme.schema.json +282 -0
|
@@ -0,0 +1,1399 @@
|
|
|
1
|
+
// src/fonts/google-fonts-catalog.ts
|
|
2
|
+
var googleFontsCatalog = [
|
|
3
|
+
{ family: "Roboto", weights: [100, 300, 400, 500, 700, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
4
|
+
{ family: "Open Sans", weights: [300, 400, 500, 600, 700, 800], styles: ["italic", "normal"], category: "sans-serif" },
|
|
5
|
+
{ family: "Noto Sans", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
6
|
+
{ family: "Lato", weights: [100, 300, 400, 700, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
7
|
+
{ family: "Montserrat", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
8
|
+
{ family: "Poppins", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
9
|
+
{ family: "Roboto Condensed", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
10
|
+
{ family: "Source Sans 3", weights: [200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
11
|
+
{ family: "Inter", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
12
|
+
{ family: "Oswald", weights: [200, 300, 400, 500, 600, 700], styles: ["normal"], category: "sans-serif" },
|
|
13
|
+
{ family: "Roboto Mono", weights: [100, 200, 300, 400, 500, 600, 700], styles: ["italic", "normal"], category: "monospace" },
|
|
14
|
+
{ family: "Raleway", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
15
|
+
{ family: "Nunito", weights: [200, 300, 400, 500, 600, 700, 800, 900, 1e3], styles: ["italic", "normal"], category: "sans-serif" },
|
|
16
|
+
{ family: "Nunito Sans", weights: [200, 300, 400, 500, 600, 700, 800, 900, 1e3], styles: ["italic", "normal"], category: "sans-serif" },
|
|
17
|
+
{ family: "Ubuntu", weights: [300, 400, 500, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
18
|
+
{ family: "Rubik", weights: [300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
19
|
+
{ family: "Playfair Display", weights: [400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "serif" },
|
|
20
|
+
{ family: "Noto Sans JP", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
21
|
+
{ family: "PT Sans", weights: [400, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
22
|
+
{ family: "Merriweather", weights: [300, 400, 700, 900], styles: ["italic", "normal"], category: "serif" },
|
|
23
|
+
{ family: "Roboto Slab", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "serif" },
|
|
24
|
+
{ family: "Noto Sans KR", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
25
|
+
{ family: "Kanit", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
26
|
+
{ family: "Work Sans", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
27
|
+
{ family: "Lora", weights: [400, 500, 600, 700], styles: ["italic", "normal"], category: "serif" },
|
|
28
|
+
{ family: "Fira Sans", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
29
|
+
{ family: "Noto Serif", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "serif" },
|
|
30
|
+
{ family: "Quicksand", weights: [300, 400, 500, 600, 700], styles: ["normal"], category: "sans-serif" },
|
|
31
|
+
{ family: "Barlow", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
32
|
+
{ family: "Mulish", weights: [200, 300, 400, 500, 600, 700, 800, 900, 1e3], styles: ["italic", "normal"], category: "sans-serif" },
|
|
33
|
+
{ family: "IBM Plex Sans", weights: [100, 200, 300, 400, 500, 600, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
34
|
+
{ family: "Manrope", weights: [200, 300, 400, 500, 600, 700, 800], styles: ["normal"], category: "sans-serif" },
|
|
35
|
+
{ family: "PT Serif", weights: [400, 700], styles: ["italic", "normal"], category: "serif" },
|
|
36
|
+
{ family: "Karla", weights: [200, 300, 400, 500, 600, 700, 800], styles: ["italic", "normal"], category: "sans-serif" },
|
|
37
|
+
{ family: "Heebo", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
38
|
+
{ family: "Noto Sans TC", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
39
|
+
{ family: "Libre Franklin", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
40
|
+
{ family: "Josefin Sans", weights: [100, 200, 300, 400, 500, 600, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
41
|
+
{ family: "Hind", weights: [300, 400, 500, 600, 700], styles: ["normal"], category: "sans-serif" },
|
|
42
|
+
{ family: "Inconsolata", weights: [200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "monospace" },
|
|
43
|
+
{ family: "DM Sans", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900, 1e3], styles: ["italic", "normal"], category: "sans-serif" },
|
|
44
|
+
{ family: "Libre Baskerville", weights: [400, 700], styles: ["italic", "normal"], category: "serif" },
|
|
45
|
+
{ family: "Source Code Pro", weights: [200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "monospace" },
|
|
46
|
+
{ family: "Mukta", weights: [200, 300, 400, 500, 600, 700, 800], styles: ["normal"], category: "sans-serif" },
|
|
47
|
+
{ family: "Oxygen", weights: [300, 400, 700], styles: ["normal"], category: "sans-serif" },
|
|
48
|
+
{ family: "Cabin", weights: [400, 500, 600, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
49
|
+
{ family: "Source Serif 4", weights: [200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "serif" },
|
|
50
|
+
{ family: "Bitter", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "serif" },
|
|
51
|
+
{ family: "Abel", weights: [400], styles: ["normal"], category: "sans-serif" },
|
|
52
|
+
{ family: "Exo 2", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
53
|
+
{ family: "Titillium Web", weights: [200, 300, 400, 600, 700, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
54
|
+
{ family: "EB Garamond", weights: [400, 500, 600, 700, 800], styles: ["italic", "normal"], category: "serif" },
|
|
55
|
+
{ family: "Anton", weights: [400], styles: ["normal"], category: "sans-serif" },
|
|
56
|
+
{ family: "Archivo", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
57
|
+
{ family: "Cairo", weights: [200, 300, 400, 500, 600, 700, 800, 900, 1e3], styles: ["normal"], category: "sans-serif" },
|
|
58
|
+
{ family: "Overpass", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
59
|
+
{ family: "Varela Round", weights: [400], styles: ["normal"], category: "sans-serif" },
|
|
60
|
+
{ family: "Cormorant Garamond", weights: [300, 400, 500, 600, 700], styles: ["italic", "normal"], category: "serif" },
|
|
61
|
+
{ family: "Crimson Text", weights: [400, 600, 700], styles: ["italic", "normal"], category: "serif" },
|
|
62
|
+
{ family: "Outfit", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
63
|
+
{ family: "Dosis", weights: [200, 300, 400, 500, 600, 700, 800], styles: ["normal"], category: "sans-serif" },
|
|
64
|
+
{ family: "Comfortaa", weights: [300, 400, 500, 600, 700], styles: ["normal"], category: "display" },
|
|
65
|
+
{ family: "Assistant", weights: [200, 300, 400, 500, 600, 700, 800], styles: ["normal"], category: "sans-serif" },
|
|
66
|
+
{ family: "IBM Plex Mono", weights: [100, 200, 300, 400, 500, 600, 700], styles: ["italic", "normal"], category: "monospace" },
|
|
67
|
+
{ family: "Signika", weights: [300, 400, 500, 600, 700], styles: ["normal"], category: "sans-serif" },
|
|
68
|
+
{ family: "Space Grotesk", weights: [300, 400, 500, 600, 700], styles: ["normal"], category: "sans-serif" },
|
|
69
|
+
{ family: "Space Mono", weights: [400, 700], styles: ["italic", "normal"], category: "monospace" },
|
|
70
|
+
{ family: "Catamaran", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
71
|
+
{ family: "Arimo", weights: [400, 500, 600, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
72
|
+
{ family: "Figtree", weights: [300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
73
|
+
{ family: "Teko", weights: [300, 400, 500, 600, 700], styles: ["normal"], category: "sans-serif" },
|
|
74
|
+
{ family: "Nanum Gothic", weights: [400, 700, 800], styles: ["normal"], category: "sans-serif" },
|
|
75
|
+
{ family: "Lexend", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
76
|
+
{ family: "Prompt", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
77
|
+
{ family: "Fira Code", weights: [300, 400, 500, 600, 700], styles: ["normal"], category: "monospace" },
|
|
78
|
+
{ family: "Plus Jakarta Sans", weights: [200, 300, 400, 500, 600, 700, 800], styles: ["italic", "normal"], category: "sans-serif" },
|
|
79
|
+
{ family: "Barlow Condensed", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
80
|
+
{ family: "Crimson Pro", weights: [200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "serif" },
|
|
81
|
+
{ family: "Fjalla One", weights: [400], styles: ["normal"], category: "sans-serif" },
|
|
82
|
+
{ family: "Maven Pro", weights: [400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
83
|
+
{ family: "Sora", weights: [100, 200, 300, 400, 500, 600, 700, 800], styles: ["normal"], category: "sans-serif" },
|
|
84
|
+
{ family: "Asap", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
85
|
+
{ family: "Red Hat Display", weights: [300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
86
|
+
{ family: "Yanone Kaffeesatz", weights: [200, 300, 400, 500, 600, 700], styles: ["normal"], category: "sans-serif" },
|
|
87
|
+
{ family: "Bebas Neue", weights: [400], styles: ["normal"], category: "sans-serif" },
|
|
88
|
+
{ family: "Philosopher", weights: [400, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
89
|
+
{ family: "Edu Australia VIC WA NT Hand", weights: [400, 500, 600, 700], styles: ["normal"], category: "handwriting" },
|
|
90
|
+
{ family: "Jost", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
91
|
+
{ family: "Albert Sans", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
92
|
+
{ family: "Spectral", weights: [200, 300, 400, 500, 600, 700, 800], styles: ["italic", "normal"], category: "serif" },
|
|
93
|
+
{ family: "Urbanist", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
94
|
+
{ family: "Vollkorn", weights: [400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "serif" },
|
|
95
|
+
{ family: "Gelasio", weights: [400, 500, 600, 700], styles: ["italic", "normal"], category: "serif" },
|
|
96
|
+
{ family: "JetBrains Mono", weights: [100, 200, 300, 400, 500, 600, 700, 800], styles: ["italic", "normal"], category: "monospace" },
|
|
97
|
+
{ family: "Arvo", weights: [400, 700], styles: ["italic", "normal"], category: "serif" },
|
|
98
|
+
{ family: "Rajdhani", weights: [300, 400, 500, 600, 700], styles: ["normal"], category: "sans-serif" },
|
|
99
|
+
{ family: "Archivo Narrow", weights: [400, 500, 600, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
100
|
+
{ family: "Public Sans", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
101
|
+
{ family: "Noto Sans SC", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
102
|
+
{ family: "Prata", weights: [400], styles: ["normal"], category: "serif" },
|
|
103
|
+
{ family: "Questrial", weights: [400], styles: ["normal"], category: "sans-serif" },
|
|
104
|
+
{ family: "Saira", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["italic", "normal"], category: "sans-serif" },
|
|
105
|
+
{ family: "Red Hat Text", weights: [300, 400, 500, 600, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
106
|
+
{ family: "Atkinson Hyperlegible", weights: [400, 700], styles: ["italic", "normal"], category: "sans-serif" },
|
|
107
|
+
{ family: "Bricolage Grotesque", weights: [200, 300, 400, 500, 600, 700, 800], styles: ["normal"], category: "sans-serif" },
|
|
108
|
+
{ family: "Geist", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "sans-serif" },
|
|
109
|
+
{ family: "Geist Mono", weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], styles: ["normal"], category: "monospace" }
|
|
110
|
+
];
|
|
111
|
+
var catalogMap = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const entry of googleFontsCatalog) {
|
|
113
|
+
catalogMap.set(entry.family.toLowerCase(), entry);
|
|
114
|
+
}
|
|
115
|
+
function lookupGoogleFont(family) {
|
|
116
|
+
return catalogMap.get(family.toLowerCase());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/fonts/resolve.ts
|
|
120
|
+
var DEFAULT_WEIGHTS = [400, 700];
|
|
121
|
+
var DEFAULT_DISPLAY = "swap";
|
|
122
|
+
function buildGoogleFontsCssUrl(family, weights, italic, display) {
|
|
123
|
+
const encodedFamily = family.replace(/ /g, "+");
|
|
124
|
+
const tuples = [];
|
|
125
|
+
const sortedWeights = [...weights].sort((a, b) => a - b);
|
|
126
|
+
if (italic) {
|
|
127
|
+
for (const w of sortedWeights) {
|
|
128
|
+
tuples.push(`0,${w}`);
|
|
129
|
+
}
|
|
130
|
+
for (const w of sortedWeights) {
|
|
131
|
+
tuples.push(`1,${w}`);
|
|
132
|
+
}
|
|
133
|
+
return `https://fonts.googleapis.com/css2?family=${encodedFamily}:ital,wght@${tuples.join(";")}&display=${display}`;
|
|
134
|
+
}
|
|
135
|
+
const wghtValues = sortedWeights.join(";");
|
|
136
|
+
return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@${wghtValues}&display=${display}`;
|
|
137
|
+
}
|
|
138
|
+
var VISOR_FONTS_CDN = "https://fonts.visor.design";
|
|
139
|
+
function buildFamilySlug(family) {
|
|
140
|
+
return family.toLowerCase().replace(/ /g, "-");
|
|
141
|
+
}
|
|
142
|
+
function buildFamilyPrefix(family) {
|
|
143
|
+
return family.replace(/ /g, "");
|
|
144
|
+
}
|
|
145
|
+
var WEIGHT_NAMES = {
|
|
146
|
+
100: "Thin",
|
|
147
|
+
200: "ExtraLight",
|
|
148
|
+
300: "Light",
|
|
149
|
+
400: "Regular",
|
|
150
|
+
500: "Medium",
|
|
151
|
+
600: "SemiBold",
|
|
152
|
+
700: "Bold",
|
|
153
|
+
800: "ExtraBold",
|
|
154
|
+
900: "Black"
|
|
155
|
+
};
|
|
156
|
+
function buildVisorFontUrl(org, family, weight) {
|
|
157
|
+
const slug = buildFamilySlug(family);
|
|
158
|
+
const prefix = buildFamilyPrefix(family);
|
|
159
|
+
const weightName = WEIGHT_NAMES[weight] ?? `W${weight}`;
|
|
160
|
+
return `${VISOR_FONTS_CDN}/${org}/${slug}/${prefix}-${weightName}.woff2`;
|
|
161
|
+
}
|
|
162
|
+
function resolveFont(family, options = {}) {
|
|
163
|
+
const display = options.display ?? DEFAULT_DISPLAY;
|
|
164
|
+
const requestedWeights = options.weights ?? DEFAULT_WEIGHTS;
|
|
165
|
+
const italic = options.italic ?? false;
|
|
166
|
+
const explicitSource = options.source;
|
|
167
|
+
if (explicitSource === "visor-fonts") {
|
|
168
|
+
return {
|
|
169
|
+
family,
|
|
170
|
+
source: "visor-fonts",
|
|
171
|
+
cssUrl: null,
|
|
172
|
+
weights: requestedWeights,
|
|
173
|
+
italic,
|
|
174
|
+
display,
|
|
175
|
+
category: options.category ?? "sans-serif",
|
|
176
|
+
guidance: null,
|
|
177
|
+
org: options.org ?? null
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (explicitSource === "local") {
|
|
181
|
+
return {
|
|
182
|
+
family,
|
|
183
|
+
source: "local",
|
|
184
|
+
cssUrl: null,
|
|
185
|
+
weights: requestedWeights,
|
|
186
|
+
italic,
|
|
187
|
+
display,
|
|
188
|
+
category: options.category ?? "sans-serif",
|
|
189
|
+
guidance: `"${family}" is a local font. To use this font:
|
|
190
|
+
1. Add the font files (.woff2) to your project's public/fonts/ directory
|
|
191
|
+
2. Create @font-face declarations in your theme CSS
|
|
192
|
+
3. Reference the font family in your theme's --font-display or --font-body token`,
|
|
193
|
+
org: null
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const catalogEntry = lookupGoogleFont(family);
|
|
197
|
+
if (catalogEntry) {
|
|
198
|
+
const availableWeights = requestedWeights.filter(
|
|
199
|
+
(w) => catalogEntry.weights.includes(w)
|
|
200
|
+
);
|
|
201
|
+
const weights = availableWeights.length > 0 ? availableWeights : catalogEntry.weights;
|
|
202
|
+
const hasItalic = catalogEntry.styles.includes("italic");
|
|
203
|
+
const resolvedItalic = italic && hasItalic;
|
|
204
|
+
const cssUrl = buildGoogleFontsCssUrl(
|
|
205
|
+
catalogEntry.family,
|
|
206
|
+
weights,
|
|
207
|
+
resolvedItalic,
|
|
208
|
+
display
|
|
209
|
+
);
|
|
210
|
+
return {
|
|
211
|
+
family: catalogEntry.family,
|
|
212
|
+
// Use canonical casing from catalog
|
|
213
|
+
source: "google-fonts",
|
|
214
|
+
cssUrl,
|
|
215
|
+
weights,
|
|
216
|
+
italic: resolvedItalic,
|
|
217
|
+
display,
|
|
218
|
+
category: catalogEntry.category,
|
|
219
|
+
guidance: null,
|
|
220
|
+
org: null
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
family,
|
|
225
|
+
source: "local",
|
|
226
|
+
cssUrl: null,
|
|
227
|
+
weights: requestedWeights,
|
|
228
|
+
italic,
|
|
229
|
+
display,
|
|
230
|
+
category: options.category ?? "sans-serif",
|
|
231
|
+
guidance: `"${family}" is not available on Google Fonts. To use this font:
|
|
232
|
+
1. Add the font files (.woff2) to your project's public/fonts/ directory
|
|
233
|
+
2. Create @font-face declarations in your theme CSS
|
|
234
|
+
3. Reference the font family in your theme's --font-display or --font-body token`,
|
|
235
|
+
org: null
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/fonts/preload.ts
|
|
240
|
+
var GOOGLE_FONTS_ORIGIN = "https://fonts.googleapis.com";
|
|
241
|
+
var GOOGLE_FONTS_STATIC_ORIGIN = "https://fonts.gstatic.com";
|
|
242
|
+
function generatePreloadLinks(resolutions, customFontPaths) {
|
|
243
|
+
const links = [];
|
|
244
|
+
const hasGoogleFonts = resolutions.some((r) => r.source === "google-fonts");
|
|
245
|
+
if (hasGoogleFonts) {
|
|
246
|
+
links.push(
|
|
247
|
+
`<link rel="preconnect" href="${GOOGLE_FONTS_ORIGIN}">`
|
|
248
|
+
);
|
|
249
|
+
links.push(
|
|
250
|
+
`<link rel="preconnect" href="${GOOGLE_FONTS_STATIC_ORIGIN}" crossorigin>`
|
|
251
|
+
);
|
|
252
|
+
for (const resolution of resolutions) {
|
|
253
|
+
if (resolution.source === "google-fonts" && resolution.cssUrl) {
|
|
254
|
+
links.push(
|
|
255
|
+
`<link rel="preload" as="style" href="${resolution.cssUrl}">`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const hasVisorFonts = resolutions.some((r) => r.source === "visor-fonts");
|
|
261
|
+
if (hasVisorFonts) {
|
|
262
|
+
links.push(
|
|
263
|
+
`<link rel="preconnect" href="${VISOR_FONTS_CDN}" crossorigin>`
|
|
264
|
+
);
|
|
265
|
+
for (const resolution of resolutions) {
|
|
266
|
+
if (resolution.source === "visor-fonts" && resolution.org) {
|
|
267
|
+
for (const weight of resolution.weights) {
|
|
268
|
+
const url = buildVisorFontUrl(resolution.org, resolution.family, weight);
|
|
269
|
+
links.push(
|
|
270
|
+
`<link rel="preload" as="font" type="font/woff2" href="${url}" crossorigin>`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (customFontPaths) {
|
|
277
|
+
for (const resolution of resolutions) {
|
|
278
|
+
if (resolution.source === "local") {
|
|
279
|
+
const paths = customFontPaths.get(resolution.family);
|
|
280
|
+
if (paths) {
|
|
281
|
+
for (const path of paths) {
|
|
282
|
+
links.push(
|
|
283
|
+
`<link rel="preload" as="font" type="font/woff2" href="${path}" crossorigin>`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return links;
|
|
291
|
+
}
|
|
292
|
+
function generateStylesheetLinks(resolutions) {
|
|
293
|
+
const links = [];
|
|
294
|
+
for (const resolution of resolutions) {
|
|
295
|
+
if (resolution.source === "google-fonts" && resolution.cssUrl) {
|
|
296
|
+
links.push(
|
|
297
|
+
`<link rel="stylesheet" href="${resolution.cssUrl}">`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return links;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/fonts/pipeline.ts
|
|
305
|
+
function generateFontCSS(heading, displayFont, body, mono, typography) {
|
|
306
|
+
const lines = [];
|
|
307
|
+
const allSlots = [heading, displayFont, body, mono];
|
|
308
|
+
const googleFonts = allSlots.filter(
|
|
309
|
+
(r) => r !== null && r.source === "google-fonts"
|
|
310
|
+
);
|
|
311
|
+
if (googleFonts.length > 0) {
|
|
312
|
+
lines.push("/* Google Fonts \u2014 load these stylesheets in your HTML <head> */");
|
|
313
|
+
for (const font of googleFonts) {
|
|
314
|
+
lines.push(`/* ${font.cssUrl} */`);
|
|
315
|
+
}
|
|
316
|
+
lines.push("");
|
|
317
|
+
}
|
|
318
|
+
const visorFonts = allSlots.filter(
|
|
319
|
+
(r) => r !== null && r.source === "visor-fonts"
|
|
320
|
+
);
|
|
321
|
+
const seenVisorFonts = /* @__PURE__ */ new Set();
|
|
322
|
+
if (visorFonts.length > 0) {
|
|
323
|
+
lines.push("/* Visor Fonts \u2014 CDN-hosted @font-face declarations */");
|
|
324
|
+
for (const font of visorFonts) {
|
|
325
|
+
if (seenVisorFonts.has(font.family)) continue;
|
|
326
|
+
seenVisorFonts.add(font.family);
|
|
327
|
+
for (const weight of font.weights) {
|
|
328
|
+
const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
|
|
329
|
+
lines.push(`@font-face {`);
|
|
330
|
+
lines.push(` font-family: "${font.family}";`);
|
|
331
|
+
lines.push(` src: url("${url}") format("woff2");`);
|
|
332
|
+
lines.push(` font-weight: ${weight};`);
|
|
333
|
+
lines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
|
|
334
|
+
lines.push(` font-display: ${font.display};`);
|
|
335
|
+
lines.push(`}`);
|
|
336
|
+
lines.push("");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const localFonts = allSlots.filter(
|
|
341
|
+
(r) => r !== null && r.source === "local"
|
|
342
|
+
);
|
|
343
|
+
if (localFonts.length > 0) {
|
|
344
|
+
lines.push(
|
|
345
|
+
"/* Local fonts \u2014 add your @font-face declarations below */"
|
|
346
|
+
);
|
|
347
|
+
for (const font of localFonts) {
|
|
348
|
+
lines.push(`/* @font-face {`);
|
|
349
|
+
lines.push(` font-family: "${font.family}";`);
|
|
350
|
+
lines.push(` src: url("/fonts/${font.family.replace(/ /g, "-").toLowerCase()}.woff2") format("woff2");`);
|
|
351
|
+
lines.push(` font-weight: ${font.weights.length === 1 ? font.weights[0] : `${font.weights[0]} ${font.weights[font.weights.length - 1]}`};`);
|
|
352
|
+
lines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
|
|
353
|
+
lines.push(` font-display: ${font.display};`);
|
|
354
|
+
lines.push(` } */`);
|
|
355
|
+
lines.push("");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const allFonts = allSlots.filter(
|
|
359
|
+
(r) => r !== null
|
|
360
|
+
);
|
|
361
|
+
const seenFamilies = /* @__PURE__ */ new Set();
|
|
362
|
+
for (const font of allFonts) {
|
|
363
|
+
if (!seenFamilies.has(font.family)) {
|
|
364
|
+
seenFamilies.add(font.family);
|
|
365
|
+
lines.push(generateFallbackFontFace(font));
|
|
366
|
+
lines.push("");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const overrides = [];
|
|
370
|
+
if (heading) {
|
|
371
|
+
const fallbackName = `${heading.family} Fallback`;
|
|
372
|
+
const fallback = getFallbackStack(heading);
|
|
373
|
+
overrides.push(
|
|
374
|
+
` --font-heading: "${heading.family}", "${fallbackName}", ${fallback};`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (displayFont) {
|
|
378
|
+
const fallbackName = `${displayFont.family} Fallback`;
|
|
379
|
+
const fallback = getFallbackStack(displayFont);
|
|
380
|
+
overrides.push(
|
|
381
|
+
` --font-display: "${displayFont.family}", "${fallbackName}", ${fallback};`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
if (body) {
|
|
385
|
+
const fallbackName = `${body.family} Fallback`;
|
|
386
|
+
const fallback = getFallbackStack(body);
|
|
387
|
+
overrides.push(` --font-body: "${body.family}", "${fallbackName}", ${fallback};`);
|
|
388
|
+
overrides.push(` --font-sans: "${body.family}", "${fallbackName}", ${fallback};`);
|
|
389
|
+
}
|
|
390
|
+
if (mono) {
|
|
391
|
+
const fallbackName = `${mono.family} Fallback`;
|
|
392
|
+
const fallback = getFallbackStack(mono);
|
|
393
|
+
overrides.push(` --font-mono: "${mono.family}", "${fallbackName}", ${fallback};`);
|
|
394
|
+
}
|
|
395
|
+
if (typography.heading?.weight) {
|
|
396
|
+
overrides.push(
|
|
397
|
+
` --weight-heading: ${typography.heading.weight};`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
if (typography.display?.weight) {
|
|
401
|
+
overrides.push(
|
|
402
|
+
` --weight-display: ${typography.display.weight};`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
if (typography.body?.weight) {
|
|
406
|
+
overrides.push(
|
|
407
|
+
` --weight-body: ${typography.body.weight};`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
if (overrides.length > 0) {
|
|
411
|
+
lines.push(":root {");
|
|
412
|
+
lines.push(...overrides);
|
|
413
|
+
lines.push("}");
|
|
414
|
+
}
|
|
415
|
+
return lines.join("\n");
|
|
416
|
+
}
|
|
417
|
+
function getFallbackStack(resolution) {
|
|
418
|
+
switch (resolution.category) {
|
|
419
|
+
case "serif":
|
|
420
|
+
return 'Georgia, "Times New Roman", Times, serif';
|
|
421
|
+
case "monospace":
|
|
422
|
+
return '"SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace';
|
|
423
|
+
case "display":
|
|
424
|
+
case "handwriting":
|
|
425
|
+
case "sans-serif":
|
|
426
|
+
default:
|
|
427
|
+
return '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function generateFallbackFontFace(resolution) {
|
|
431
|
+
const fallbackName = `${resolution.family} Fallback`;
|
|
432
|
+
const systemFont = getSystemFallbackFont(resolution.category);
|
|
433
|
+
const sizeAdjust = getSizeAdjust(resolution.category);
|
|
434
|
+
return [
|
|
435
|
+
`@font-face {`,
|
|
436
|
+
` font-family: "${fallbackName}";`,
|
|
437
|
+
` src: local("${systemFont}");`,
|
|
438
|
+
` size-adjust: ${sizeAdjust};`,
|
|
439
|
+
` ascent-override: 100%;`,
|
|
440
|
+
` descent-override: 20%;`,
|
|
441
|
+
` line-gap-override: 0%;`,
|
|
442
|
+
`}`
|
|
443
|
+
].join("\n");
|
|
444
|
+
}
|
|
445
|
+
function getSystemFallbackFont(category) {
|
|
446
|
+
switch (category) {
|
|
447
|
+
case "serif":
|
|
448
|
+
return "Georgia";
|
|
449
|
+
case "monospace":
|
|
450
|
+
return "Courier New";
|
|
451
|
+
default:
|
|
452
|
+
return "Arial";
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function getSizeAdjust(category) {
|
|
456
|
+
switch (category) {
|
|
457
|
+
case "serif":
|
|
458
|
+
return "105%";
|
|
459
|
+
case "monospace":
|
|
460
|
+
return "100%";
|
|
461
|
+
default:
|
|
462
|
+
return "107%";
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function resolveThemeFonts(typography, options) {
|
|
466
|
+
const display = options?.display ?? "swap";
|
|
467
|
+
const warnings = [];
|
|
468
|
+
let headingResolution = null;
|
|
469
|
+
if (typography.heading?.family) {
|
|
470
|
+
const weights = [];
|
|
471
|
+
if (typography.heading.weight) weights.push(typography.heading.weight);
|
|
472
|
+
headingResolution = resolveFont(typography.heading.family, {
|
|
473
|
+
weights: weights.length > 0 ? weights : void 0,
|
|
474
|
+
display,
|
|
475
|
+
source: typography.heading.source,
|
|
476
|
+
org: typography.heading.org
|
|
477
|
+
});
|
|
478
|
+
if (headingResolution.guidance) {
|
|
479
|
+
warnings.push(headingResolution.guidance);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
let bodyResolution = null;
|
|
483
|
+
if (typography.body?.family) {
|
|
484
|
+
const bodyWeights = [];
|
|
485
|
+
if (typography.body.weight) bodyWeights.push(typography.body.weight);
|
|
486
|
+
if (!bodyWeights.includes(400)) bodyWeights.push(400);
|
|
487
|
+
if (!bodyWeights.includes(700)) bodyWeights.push(700);
|
|
488
|
+
if (headingResolution && typography.body.family.toLowerCase() === headingResolution.family.toLowerCase()) {
|
|
489
|
+
const mergedWeights = Array.from(
|
|
490
|
+
/* @__PURE__ */ new Set([...headingResolution.weights, ...bodyWeights])
|
|
491
|
+
).sort((a, b) => a - b);
|
|
492
|
+
headingResolution = resolveFont(typography.heading.family, {
|
|
493
|
+
weights: mergedWeights,
|
|
494
|
+
display,
|
|
495
|
+
source: typography.heading.source,
|
|
496
|
+
org: typography.heading.org
|
|
497
|
+
});
|
|
498
|
+
bodyResolution = headingResolution;
|
|
499
|
+
} else {
|
|
500
|
+
bodyResolution = resolveFont(typography.body.family, {
|
|
501
|
+
weights: bodyWeights.length > 0 ? bodyWeights : void 0,
|
|
502
|
+
display,
|
|
503
|
+
source: typography.body.source,
|
|
504
|
+
org: typography.body.org
|
|
505
|
+
});
|
|
506
|
+
if (bodyResolution.guidance) {
|
|
507
|
+
warnings.push(bodyResolution.guidance);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
let displayResolution = null;
|
|
512
|
+
if (typography.display?.family) {
|
|
513
|
+
const displayWeights = [];
|
|
514
|
+
if (typography.display.weight) displayWeights.push(typography.display.weight);
|
|
515
|
+
if (headingResolution && typography.display.family.toLowerCase() === headingResolution.family.toLowerCase()) {
|
|
516
|
+
const mergedWeights = Array.from(
|
|
517
|
+
/* @__PURE__ */ new Set([...headingResolution.weights, ...displayWeights])
|
|
518
|
+
).sort((a, b) => a - b);
|
|
519
|
+
headingResolution = resolveFont(typography.heading.family, {
|
|
520
|
+
weights: mergedWeights,
|
|
521
|
+
display,
|
|
522
|
+
source: typography.heading.source,
|
|
523
|
+
org: typography.heading.org
|
|
524
|
+
});
|
|
525
|
+
displayResolution = headingResolution;
|
|
526
|
+
} else if (bodyResolution && typography.display.family.toLowerCase() === bodyResolution.family.toLowerCase()) {
|
|
527
|
+
const mergedWeights = Array.from(
|
|
528
|
+
/* @__PURE__ */ new Set([...bodyResolution.weights, ...displayWeights])
|
|
529
|
+
).sort((a, b) => a - b);
|
|
530
|
+
bodyResolution = resolveFont(typography.body.family, {
|
|
531
|
+
weights: mergedWeights,
|
|
532
|
+
display,
|
|
533
|
+
source: typography.body.source,
|
|
534
|
+
org: typography.body.org
|
|
535
|
+
});
|
|
536
|
+
displayResolution = bodyResolution;
|
|
537
|
+
} else {
|
|
538
|
+
displayResolution = resolveFont(typography.display.family, {
|
|
539
|
+
weights: displayWeights.length > 0 ? displayWeights : void 0,
|
|
540
|
+
display,
|
|
541
|
+
source: typography.display.source,
|
|
542
|
+
org: typography.display.org
|
|
543
|
+
});
|
|
544
|
+
if (displayResolution.guidance) {
|
|
545
|
+
warnings.push(displayResolution.guidance);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
let monoResolution = null;
|
|
550
|
+
if (typography.mono?.family) {
|
|
551
|
+
const monoWeights = [];
|
|
552
|
+
if (typography.mono.weight) monoWeights.push(typography.mono.weight);
|
|
553
|
+
monoResolution = resolveFont(typography.mono.family, {
|
|
554
|
+
weights: monoWeights.length > 0 ? monoWeights : void 0,
|
|
555
|
+
display,
|
|
556
|
+
source: typography.mono.source,
|
|
557
|
+
org: typography.mono.org,
|
|
558
|
+
category: "monospace"
|
|
559
|
+
});
|
|
560
|
+
if (monoResolution.guidance) {
|
|
561
|
+
warnings.push(monoResolution.guidance);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const allResolutions = [];
|
|
565
|
+
if (headingResolution) allResolutions.push(headingResolution);
|
|
566
|
+
if (displayResolution && displayResolution !== headingResolution) {
|
|
567
|
+
allResolutions.push(displayResolution);
|
|
568
|
+
}
|
|
569
|
+
if (bodyResolution && bodyResolution !== headingResolution && bodyResolution !== displayResolution) {
|
|
570
|
+
allResolutions.push(bodyResolution);
|
|
571
|
+
}
|
|
572
|
+
if (monoResolution) allResolutions.push(monoResolution);
|
|
573
|
+
const preloadLinks = generatePreloadLinks(allResolutions);
|
|
574
|
+
const stylesheetLinks = generateStylesheetLinks(allResolutions);
|
|
575
|
+
const allLinks = [...preloadLinks, ...stylesheetLinks];
|
|
576
|
+
const css = generateFontCSS(headingResolution, displayResolution, bodyResolution, monoResolution, typography);
|
|
577
|
+
return {
|
|
578
|
+
heading: headingResolution,
|
|
579
|
+
display: displayResolution,
|
|
580
|
+
body: bodyResolution,
|
|
581
|
+
mono: monoResolution,
|
|
582
|
+
preloadLinks: allLinks,
|
|
583
|
+
css,
|
|
584
|
+
warnings
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// src/color.ts
|
|
589
|
+
function normalizeHex(hex) {
|
|
590
|
+
let color = hex.replace(/^#/, "");
|
|
591
|
+
if (color.length === 3) {
|
|
592
|
+
color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
|
|
593
|
+
}
|
|
594
|
+
if (color.length === 8) {
|
|
595
|
+
color = color.slice(0, 6);
|
|
596
|
+
}
|
|
597
|
+
if (!/^[0-9a-fA-F]{6}$/.test(color)) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
return `#${color.toLowerCase()}`;
|
|
601
|
+
}
|
|
602
|
+
function isValidHex(hex) {
|
|
603
|
+
return normalizeHex(hex) !== null;
|
|
604
|
+
}
|
|
605
|
+
function hexToRgb(hex) {
|
|
606
|
+
const normalized = normalizeHex(hex);
|
|
607
|
+
if (!normalized) {
|
|
608
|
+
throw new Error(`Invalid hex color: ${hex}`);
|
|
609
|
+
}
|
|
610
|
+
return [
|
|
611
|
+
parseInt(normalized.slice(1, 3), 16),
|
|
612
|
+
parseInt(normalized.slice(3, 5), 16),
|
|
613
|
+
parseInt(normalized.slice(5, 7), 16)
|
|
614
|
+
];
|
|
615
|
+
}
|
|
616
|
+
function rgbToHex(rgb) {
|
|
617
|
+
const [r, g, b] = rgb.map((c) => Math.round(Math.max(0, Math.min(255, c))));
|
|
618
|
+
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
619
|
+
}
|
|
620
|
+
function toLinear(c) {
|
|
621
|
+
c /= 255;
|
|
622
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
623
|
+
}
|
|
624
|
+
function fromLinear(c) {
|
|
625
|
+
return c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
626
|
+
}
|
|
627
|
+
function rgbToOklch(r, g, b) {
|
|
628
|
+
const lr = toLinear(r);
|
|
629
|
+
const lg = toLinear(g);
|
|
630
|
+
const lb = toLinear(b);
|
|
631
|
+
const x = 0.4124564 * lr + 0.3575761 * lg + 0.1804375 * lb;
|
|
632
|
+
const y = 0.2126729 * lr + 0.7151522 * lg + 0.072175 * lb;
|
|
633
|
+
const z = 0.0193339 * lr + 0.119192 * lg + 0.9503041 * lb;
|
|
634
|
+
const l_ = 0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z;
|
|
635
|
+
const m_ = 0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z;
|
|
636
|
+
const s_ = 0.0482003018 * x + 0.2643662691 * y + 0.633851707 * z;
|
|
637
|
+
const lCbrt = Math.cbrt(l_);
|
|
638
|
+
const mCbrt = Math.cbrt(m_);
|
|
639
|
+
const sCbrt = Math.cbrt(s_);
|
|
640
|
+
const L = 0.2104542553 * lCbrt + 0.793617785 * mCbrt - 0.0040720468 * sCbrt;
|
|
641
|
+
const a = 1.9779984951 * lCbrt - 2.428592205 * mCbrt + 0.4505937099 * sCbrt;
|
|
642
|
+
const okb = 0.0259040371 * lCbrt + 0.7827717662 * mCbrt - 0.808675766 * sCbrt;
|
|
643
|
+
const C = Math.sqrt(a * a + okb * okb);
|
|
644
|
+
let H = Math.atan2(okb, a) * (180 / Math.PI);
|
|
645
|
+
if (H < 0) H += 360;
|
|
646
|
+
return [L, C, H];
|
|
647
|
+
}
|
|
648
|
+
function hexToOklch(hex) {
|
|
649
|
+
return rgbToOklch(...hexToRgb(hex));
|
|
650
|
+
}
|
|
651
|
+
function oklchToLinearRgb(L, C, H) {
|
|
652
|
+
const hRad = H * (Math.PI / 180);
|
|
653
|
+
const a = C * Math.cos(hRad);
|
|
654
|
+
const okb = C * Math.sin(hRad);
|
|
655
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * okb;
|
|
656
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * okb;
|
|
657
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * okb;
|
|
658
|
+
const l = l_ * l_ * l_;
|
|
659
|
+
const m = m_ * m_ * m_;
|
|
660
|
+
const s = s_ * s_ * s_;
|
|
661
|
+
const lr = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
662
|
+
const lg = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
663
|
+
const lb = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
|
|
664
|
+
return [lr, lg, lb];
|
|
665
|
+
}
|
|
666
|
+
function isInGamut(lr, lg, lb) {
|
|
667
|
+
const eps = 1e-4;
|
|
668
|
+
return lr >= -eps && lr <= 1 + eps && lg >= -eps && lg <= 1 + eps && lb >= -eps && lb <= 1 + eps;
|
|
669
|
+
}
|
|
670
|
+
function clampToSrgb(L, C, H) {
|
|
671
|
+
const [lr, lg, lb] = oklchToLinearRgb(L, C, H);
|
|
672
|
+
if (isInGamut(lr, lg, lb)) {
|
|
673
|
+
return [
|
|
674
|
+
Math.round(fromLinear(Math.max(0, Math.min(1, lr))) * 255),
|
|
675
|
+
Math.round(fromLinear(Math.max(0, Math.min(1, lg))) * 255),
|
|
676
|
+
Math.round(fromLinear(Math.max(0, Math.min(1, lb))) * 255)
|
|
677
|
+
];
|
|
678
|
+
}
|
|
679
|
+
let lo = 0;
|
|
680
|
+
let hi = C;
|
|
681
|
+
const tolerance = 1e-3;
|
|
682
|
+
while (hi - lo > tolerance) {
|
|
683
|
+
const mid = (lo + hi) / 2;
|
|
684
|
+
const [r2, g2, b2] = oklchToLinearRgb(L, mid, H);
|
|
685
|
+
if (isInGamut(r2, g2, b2)) {
|
|
686
|
+
lo = mid;
|
|
687
|
+
} else {
|
|
688
|
+
hi = mid;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const [r, g, b] = oklchToLinearRgb(L, lo, H);
|
|
692
|
+
return [
|
|
693
|
+
Math.round(fromLinear(Math.max(0, Math.min(1, r))) * 255),
|
|
694
|
+
Math.round(fromLinear(Math.max(0, Math.min(1, g))) * 255),
|
|
695
|
+
Math.round(fromLinear(Math.max(0, Math.min(1, b))) * 255)
|
|
696
|
+
];
|
|
697
|
+
}
|
|
698
|
+
function oklchToHex(L, C, H) {
|
|
699
|
+
return rgbToHex(clampToSrgb(L, C, H));
|
|
700
|
+
}
|
|
701
|
+
function getLuminance(r, g, b) {
|
|
702
|
+
const [rs, gs, bs] = [r, g, b].map((c) => {
|
|
703
|
+
c = c / 255;
|
|
704
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
705
|
+
});
|
|
706
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
707
|
+
}
|
|
708
|
+
function getContrastRatio(color1, color2, compositeBackground) {
|
|
709
|
+
const resolved1 = resolveContrastColor(color1, compositeBackground);
|
|
710
|
+
const resolved2 = resolveContrastColor(color2, compositeBackground);
|
|
711
|
+
const l1 = getLuminance(...resolved1);
|
|
712
|
+
const l2 = getLuminance(...resolved2);
|
|
713
|
+
const lighter = Math.max(l1, l2);
|
|
714
|
+
const darker = Math.min(l1, l2);
|
|
715
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
716
|
+
}
|
|
717
|
+
function resolveContrastColor(color, compositeBackground) {
|
|
718
|
+
if (typeof color === "string") {
|
|
719
|
+
const parsed = parseColor(color);
|
|
720
|
+
if (!parsed) {
|
|
721
|
+
throw new Error(`Invalid color: ${color}`);
|
|
722
|
+
}
|
|
723
|
+
if (parsed.alpha !== void 0 && parsed.alpha < 1 && compositeBackground) {
|
|
724
|
+
return compositeOverBackground(parsed, compositeBackground);
|
|
725
|
+
}
|
|
726
|
+
return parsed.rgb;
|
|
727
|
+
}
|
|
728
|
+
if (color.alpha !== void 0 && color.alpha < 1 && compositeBackground) {
|
|
729
|
+
return compositeOverBackground(color, compositeBackground);
|
|
730
|
+
}
|
|
731
|
+
return color.rgb;
|
|
732
|
+
}
|
|
733
|
+
function parseHex(str) {
|
|
734
|
+
const trimmed = str.trim();
|
|
735
|
+
const stripped = trimmed.replace(/^#/, "");
|
|
736
|
+
let alpha;
|
|
737
|
+
if (stripped.length === 8) {
|
|
738
|
+
alpha = parseInt(stripped.slice(6, 8), 16) / 255;
|
|
739
|
+
}
|
|
740
|
+
const normalized = normalizeHex(trimmed);
|
|
741
|
+
if (!normalized) return null;
|
|
742
|
+
return {
|
|
743
|
+
rgb: hexToRgb(normalized),
|
|
744
|
+
alpha,
|
|
745
|
+
format: "hex",
|
|
746
|
+
original: trimmed
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
var RGBA_RE = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*([01]?\.?\d*))?\s*\)$/;
|
|
750
|
+
function parseRgba(str) {
|
|
751
|
+
const trimmed = str.trim();
|
|
752
|
+
const m = RGBA_RE.exec(trimmed);
|
|
753
|
+
if (!m) return null;
|
|
754
|
+
const r = parseInt(m[1], 10);
|
|
755
|
+
const g = parseInt(m[2], 10);
|
|
756
|
+
const b = parseInt(m[3], 10);
|
|
757
|
+
if (r > 255 || g > 255 || b > 255) return null;
|
|
758
|
+
let alpha;
|
|
759
|
+
if (m[4] !== void 0) {
|
|
760
|
+
alpha = parseFloat(m[4]);
|
|
761
|
+
if (isNaN(alpha) || alpha < 0 || alpha > 1) return null;
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
rgb: [r, g, b],
|
|
765
|
+
alpha,
|
|
766
|
+
format: "rgba",
|
|
767
|
+
original: trimmed
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
var HSLA_RE = /^hsla?\(\s*(\d{1,3}(?:\.\d+)?)\s*,\s*(\d{1,3}(?:\.\d+)?)%\s*,\s*(\d{1,3}(?:\.\d+)?)%\s*(?:,\s*([01]?\.?\d*))?\s*\)$/;
|
|
771
|
+
function hslToRgb(h, s, l) {
|
|
772
|
+
const sNorm = s / 100;
|
|
773
|
+
const lNorm = l / 100;
|
|
774
|
+
const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
|
|
775
|
+
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
776
|
+
const m = lNorm - c / 2;
|
|
777
|
+
let r1, g1, b1;
|
|
778
|
+
if (h < 60) {
|
|
779
|
+
[r1, g1, b1] = [c, x, 0];
|
|
780
|
+
} else if (h < 120) {
|
|
781
|
+
[r1, g1, b1] = [x, c, 0];
|
|
782
|
+
} else if (h < 180) {
|
|
783
|
+
[r1, g1, b1] = [0, c, x];
|
|
784
|
+
} else if (h < 240) {
|
|
785
|
+
[r1, g1, b1] = [0, x, c];
|
|
786
|
+
} else if (h < 300) {
|
|
787
|
+
[r1, g1, b1] = [x, 0, c];
|
|
788
|
+
} else {
|
|
789
|
+
[r1, g1, b1] = [c, 0, x];
|
|
790
|
+
}
|
|
791
|
+
return [
|
|
792
|
+
Math.round((r1 + m) * 255),
|
|
793
|
+
Math.round((g1 + m) * 255),
|
|
794
|
+
Math.round((b1 + m) * 255)
|
|
795
|
+
];
|
|
796
|
+
}
|
|
797
|
+
function parseHsla(str) {
|
|
798
|
+
const trimmed = str.trim();
|
|
799
|
+
const m = HSLA_RE.exec(trimmed);
|
|
800
|
+
if (!m) return null;
|
|
801
|
+
const h = parseFloat(m[1]);
|
|
802
|
+
const s = parseFloat(m[2]);
|
|
803
|
+
const l = parseFloat(m[3]);
|
|
804
|
+
if (h > 360 || s > 100 || l > 100) return null;
|
|
805
|
+
let alpha;
|
|
806
|
+
if (m[4] !== void 0) {
|
|
807
|
+
alpha = parseFloat(m[4]);
|
|
808
|
+
if (isNaN(alpha) || alpha < 0 || alpha > 1) return null;
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
rgb: hslToRgb(h, s, l),
|
|
812
|
+
alpha,
|
|
813
|
+
format: "hsla",
|
|
814
|
+
original: trimmed
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
var OKLCH_RE = /^oklch\(\s*([01]?\.?\d+)\s+([0-9]*\.?\d+)\s+([0-9]*\.?\d+)\s*(?:\/\s*([01]?\.?\d*))?\s*\)$/;
|
|
818
|
+
function parseOklch(str) {
|
|
819
|
+
const trimmed = str.trim();
|
|
820
|
+
const m = OKLCH_RE.exec(trimmed);
|
|
821
|
+
if (!m) return null;
|
|
822
|
+
const L = parseFloat(m[1]);
|
|
823
|
+
const C = parseFloat(m[2]);
|
|
824
|
+
const H = parseFloat(m[3]);
|
|
825
|
+
if (isNaN(L) || isNaN(C) || isNaN(H)) return null;
|
|
826
|
+
if (L < 0 || L > 1) return null;
|
|
827
|
+
let alpha;
|
|
828
|
+
if (m[4] !== void 0) {
|
|
829
|
+
alpha = parseFloat(m[4]);
|
|
830
|
+
if (isNaN(alpha) || alpha < 0 || alpha > 1) return null;
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
rgb: clampToSrgb(L, C, H),
|
|
834
|
+
alpha,
|
|
835
|
+
format: "oklch",
|
|
836
|
+
original: trimmed
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function parseColor(str) {
|
|
840
|
+
if (!str || typeof str !== "string") return null;
|
|
841
|
+
const trimmed = str.trim();
|
|
842
|
+
if (trimmed.startsWith("#")) {
|
|
843
|
+
return parseHex(trimmed);
|
|
844
|
+
}
|
|
845
|
+
if (trimmed.startsWith("rgb")) {
|
|
846
|
+
return parseRgba(trimmed);
|
|
847
|
+
}
|
|
848
|
+
if (trimmed.startsWith("hsl")) {
|
|
849
|
+
return parseHsla(trimmed);
|
|
850
|
+
}
|
|
851
|
+
if (trimmed.startsWith("oklch")) {
|
|
852
|
+
return parseOklch(trimmed);
|
|
853
|
+
}
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
function isValidColor(str) {
|
|
857
|
+
return parseColor(str) !== null;
|
|
858
|
+
}
|
|
859
|
+
function compositeOverBackground(color, background) {
|
|
860
|
+
const a = color.alpha ?? 1;
|
|
861
|
+
return [
|
|
862
|
+
Math.round(a * color.rgb[0] + (1 - a) * background[0]),
|
|
863
|
+
Math.round(a * color.rgb[1] + (1 - a) * background[1]),
|
|
864
|
+
Math.round(a * color.rgb[2] + (1 - a) * background[2])
|
|
865
|
+
];
|
|
866
|
+
}
|
|
867
|
+
function rgbToRgbaString(rgb, alpha) {
|
|
868
|
+
if (alpha !== void 0) {
|
|
869
|
+
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha})`;
|
|
870
|
+
}
|
|
871
|
+
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
|
|
872
|
+
}
|
|
873
|
+
function rgbToHslaString(rgb, alpha) {
|
|
874
|
+
const r = rgb[0] / 255;
|
|
875
|
+
const g = rgb[1] / 255;
|
|
876
|
+
const b = rgb[2] / 255;
|
|
877
|
+
const max = Math.max(r, g, b);
|
|
878
|
+
const min = Math.min(r, g, b);
|
|
879
|
+
const l = (max + min) / 2;
|
|
880
|
+
const d = max - min;
|
|
881
|
+
let h = 0;
|
|
882
|
+
let s = 0;
|
|
883
|
+
if (d !== 0) {
|
|
884
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
885
|
+
if (max === r) {
|
|
886
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
|
|
887
|
+
} else if (max === g) {
|
|
888
|
+
h = ((b - r) / d + 2) * 60;
|
|
889
|
+
} else {
|
|
890
|
+
h = ((r - g) / d + 4) * 60;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const hRound = Math.round(h);
|
|
894
|
+
const sRound = Math.round(s * 100);
|
|
895
|
+
const lRound = Math.round(l * 100);
|
|
896
|
+
if (alpha !== void 0) {
|
|
897
|
+
return `hsla(${hRound}, ${sRound}%, ${lRound}%, ${alpha})`;
|
|
898
|
+
}
|
|
899
|
+
return `hsl(${hRound}, ${sRound}%, ${lRound}%)`;
|
|
900
|
+
}
|
|
901
|
+
function rgbToOklchString(rgb, alpha) {
|
|
902
|
+
const [L, C, H] = rgbToOklch(...rgb);
|
|
903
|
+
const lStr = L.toFixed(4);
|
|
904
|
+
const cStr = C.toFixed(4);
|
|
905
|
+
const hStr = H.toFixed(2);
|
|
906
|
+
if (alpha !== void 0) {
|
|
907
|
+
return `oklch(${lStr} ${cStr} ${hStr} / ${alpha})`;
|
|
908
|
+
}
|
|
909
|
+
return `oklch(${lStr} ${cStr} ${hStr})`;
|
|
910
|
+
}
|
|
911
|
+
function serializeColor(parsed) {
|
|
912
|
+
if (parsed.format !== "hex" && parsed.original) {
|
|
913
|
+
return parsed.original;
|
|
914
|
+
}
|
|
915
|
+
switch (parsed.format) {
|
|
916
|
+
case "hex": {
|
|
917
|
+
if (parsed.alpha !== void 0) {
|
|
918
|
+
const alphaHex = Math.round(parsed.alpha * 255).toString(16).padStart(2, "0");
|
|
919
|
+
return `${rgbToHex(parsed.rgb)}${alphaHex}`;
|
|
920
|
+
}
|
|
921
|
+
return rgbToHex(parsed.rgb);
|
|
922
|
+
}
|
|
923
|
+
case "rgba":
|
|
924
|
+
return rgbToRgbaString(parsed.rgb, parsed.alpha);
|
|
925
|
+
case "hsla":
|
|
926
|
+
return rgbToHslaString(parsed.rgb, parsed.alpha);
|
|
927
|
+
case "oklch":
|
|
928
|
+
return rgbToOklchString(parsed.rgb, parsed.alpha);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/shades.ts
|
|
933
|
+
var FULL_SHADE_STEPS = [
|
|
934
|
+
50,
|
|
935
|
+
100,
|
|
936
|
+
200,
|
|
937
|
+
300,
|
|
938
|
+
400,
|
|
939
|
+
500,
|
|
940
|
+
600,
|
|
941
|
+
700,
|
|
942
|
+
800,
|
|
943
|
+
900,
|
|
944
|
+
950
|
|
945
|
+
];
|
|
946
|
+
var SELECTIVE_SHADE_STEPS = [
|
|
947
|
+
50,
|
|
948
|
+
100,
|
|
949
|
+
500,
|
|
950
|
+
600,
|
|
951
|
+
700,
|
|
952
|
+
900
|
|
953
|
+
];
|
|
954
|
+
var LIGHTNESS_TARGETS = {
|
|
955
|
+
50: 0.97,
|
|
956
|
+
100: 0.93,
|
|
957
|
+
200: 0.87,
|
|
958
|
+
300: 0.78,
|
|
959
|
+
400: 0.65,
|
|
960
|
+
500: 0.55,
|
|
961
|
+
600: -1,
|
|
962
|
+
// placeholder — replaced by input L at anchor
|
|
963
|
+
700: 0.38,
|
|
964
|
+
800: 0.3,
|
|
965
|
+
900: 0.22,
|
|
966
|
+
950: 0.14
|
|
967
|
+
};
|
|
968
|
+
var CHROMA_MULTIPLIERS = {
|
|
969
|
+
50: 0.15,
|
|
970
|
+
100: 0.25,
|
|
971
|
+
200: 0.45,
|
|
972
|
+
300: 0.7,
|
|
973
|
+
400: 0.9,
|
|
974
|
+
500: 1,
|
|
975
|
+
600: 1,
|
|
976
|
+
700: 1,
|
|
977
|
+
800: 0.85,
|
|
978
|
+
900: 0.7,
|
|
979
|
+
950: 0.5
|
|
980
|
+
};
|
|
981
|
+
var ANCHOR_SHADE = {
|
|
982
|
+
primary: 600,
|
|
983
|
+
accent: 600,
|
|
984
|
+
neutral: 500,
|
|
985
|
+
success: 500,
|
|
986
|
+
warning: 500,
|
|
987
|
+
error: 500,
|
|
988
|
+
info: 500
|
|
989
|
+
};
|
|
990
|
+
var FULL_SCALE_ROLES = ["primary", "accent", "neutral"];
|
|
991
|
+
var TAILWIND_GRAY = {
|
|
992
|
+
50: "#f9fafb",
|
|
993
|
+
100: "#f3f4f6",
|
|
994
|
+
200: "#e5e7eb",
|
|
995
|
+
300: "#d1d5db",
|
|
996
|
+
400: "#9ca3af",
|
|
997
|
+
500: "#6b7280",
|
|
998
|
+
600: "#4b5563",
|
|
999
|
+
700: "#374151",
|
|
1000
|
+
800: "#1f2937",
|
|
1001
|
+
900: "#111827",
|
|
1002
|
+
950: "#030712"
|
|
1003
|
+
};
|
|
1004
|
+
function computeLightness(step, inputL, anchorShade) {
|
|
1005
|
+
if (step === anchorShade) {
|
|
1006
|
+
return inputL;
|
|
1007
|
+
}
|
|
1008
|
+
const anchorTarget = anchorShade === 600 ? inputL : LIGHTNESS_TARGETS[anchorShade];
|
|
1009
|
+
const stepTarget = LIGHTNESS_TARGETS[step];
|
|
1010
|
+
if (Math.abs(anchorTarget - inputL) < 0.01) {
|
|
1011
|
+
return stepTarget;
|
|
1012
|
+
}
|
|
1013
|
+
if (step < anchorShade) {
|
|
1014
|
+
const anchorDefaultL = anchorShade === 600 ? 0.45 : LIGHTNESS_TARGETS[anchorShade];
|
|
1015
|
+
const rawRange = 0.97 - anchorDefaultL;
|
|
1016
|
+
const newRange = 0.97 - inputL;
|
|
1017
|
+
if (rawRange <= 0) return stepTarget;
|
|
1018
|
+
const t = (stepTarget - anchorDefaultL) / rawRange;
|
|
1019
|
+
return inputL + t * newRange;
|
|
1020
|
+
} else {
|
|
1021
|
+
const anchorDefaultL = anchorShade === 600 ? 0.45 : LIGHTNESS_TARGETS[anchorShade];
|
|
1022
|
+
const rawRange = anchorDefaultL - 0.14;
|
|
1023
|
+
const newRange = inputL - 0.14;
|
|
1024
|
+
if (rawRange <= 0) return stepTarget;
|
|
1025
|
+
const t = (anchorDefaultL - stepTarget) / rawRange;
|
|
1026
|
+
return inputL - t * newRange;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
function generateShadeScale(color, role) {
|
|
1030
|
+
const parsed = parseColor(color);
|
|
1031
|
+
const [inputL, inputC, inputH] = parsed ? rgbToOklch(...parsed.rgb) : hexToOklch(color);
|
|
1032
|
+
const anchorShade = ANCHOR_SHADE[role];
|
|
1033
|
+
const steps = FULL_SCALE_ROLES.includes(role) ? FULL_SHADE_STEPS : SELECTIVE_SHADE_STEPS;
|
|
1034
|
+
const maxNeutralChroma = 0.02;
|
|
1035
|
+
const scale = {};
|
|
1036
|
+
for (const step of steps) {
|
|
1037
|
+
const targetL = computeLightness(step, inputL, anchorShade);
|
|
1038
|
+
let targetC = inputC * CHROMA_MULTIPLIERS[step];
|
|
1039
|
+
if (role === "neutral") {
|
|
1040
|
+
targetC = Math.min(targetC, maxNeutralChroma);
|
|
1041
|
+
}
|
|
1042
|
+
scale[step] = oklchToHex(targetL, targetC, inputH);
|
|
1043
|
+
}
|
|
1044
|
+
return scale;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/generate-css.ts
|
|
1048
|
+
function header(label) {
|
|
1049
|
+
return [
|
|
1050
|
+
"/* ============================================",
|
|
1051
|
+
` ${label}`,
|
|
1052
|
+
" Generated by @loworbitstudio/visor-theme-engine",
|
|
1053
|
+
" DO NOT EDIT \u2014 regenerate from .visor.yaml",
|
|
1054
|
+
" ============================================ */",
|
|
1055
|
+
""
|
|
1056
|
+
].join("\n");
|
|
1057
|
+
}
|
|
1058
|
+
function sectionComment(label) {
|
|
1059
|
+
return `
|
|
1060
|
+
/* --- ${label} --- */`;
|
|
1061
|
+
}
|
|
1062
|
+
function block(selector, declarations) {
|
|
1063
|
+
if (declarations.length === 0) return "";
|
|
1064
|
+
return [selector + " {", ...declarations.map((d) => ` ${d}`), "}", ""].join(
|
|
1065
|
+
"\n"
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
var FULL_SCALE_ROLES2 = ["primary", "accent", "neutral"];
|
|
1069
|
+
var SELECTIVE_SCALE_ROLES = [
|
|
1070
|
+
"success",
|
|
1071
|
+
"warning",
|
|
1072
|
+
"error",
|
|
1073
|
+
"info"
|
|
1074
|
+
];
|
|
1075
|
+
function generateColorPrimitives(primitives) {
|
|
1076
|
+
const decls = [];
|
|
1077
|
+
decls.push("--color-white: #ffffff;");
|
|
1078
|
+
decls.push("--color-black: #000000;");
|
|
1079
|
+
const allRoles = [...FULL_SCALE_ROLES2, ...SELECTIVE_SCALE_ROLES];
|
|
1080
|
+
for (const role of allRoles) {
|
|
1081
|
+
const scale = primitives[role];
|
|
1082
|
+
const steps = FULL_SCALE_ROLES2.includes(role) ? FULL_SHADE_STEPS : SELECTIVE_SHADE_STEPS;
|
|
1083
|
+
for (const step of steps) {
|
|
1084
|
+
const value = scale[step];
|
|
1085
|
+
decls.push(`--color-${role}-${step}: ${value};`);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return decls.join("\n ");
|
|
1089
|
+
}
|
|
1090
|
+
function generateSpacingPrimitives(config) {
|
|
1091
|
+
const base = config.spacing.base;
|
|
1092
|
+
const multipliers = [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24];
|
|
1093
|
+
return multipliers.map((m) => {
|
|
1094
|
+
const px = base * m;
|
|
1095
|
+
const rem = px === 0 ? "0" : `${px / 16}rem`;
|
|
1096
|
+
return `--spacing-${m}: ${rem}; /* ${px}px */`;
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
function generateRadiusPrimitives(config) {
|
|
1100
|
+
const decls = [];
|
|
1101
|
+
decls.push("--radius-none: 0;");
|
|
1102
|
+
decls.push(
|
|
1103
|
+
`--radius-sm: ${config.radius.sm / 16}rem; /* ${config.radius.sm}px */`
|
|
1104
|
+
);
|
|
1105
|
+
decls.push(
|
|
1106
|
+
`--radius-md: ${config.radius.md / 16}rem; /* ${config.radius.md}px */`
|
|
1107
|
+
);
|
|
1108
|
+
decls.push(
|
|
1109
|
+
`--radius-lg: ${config.radius.lg / 16}rem; /* ${config.radius.lg}px */`
|
|
1110
|
+
);
|
|
1111
|
+
decls.push(
|
|
1112
|
+
`--radius-xl: ${config.radius.xl / 16}rem; /* ${config.radius.xl}px */`
|
|
1113
|
+
);
|
|
1114
|
+
decls.push(
|
|
1115
|
+
`--radius-2xl: ${config.radius.xl * 1.333 / 16}rem; /* ${Math.round(config.radius.xl * 1.333)}px */`
|
|
1116
|
+
);
|
|
1117
|
+
decls.push(
|
|
1118
|
+
`--radius-3xl: ${config.radius.xl * 2 / 16}rem; /* ${config.radius.xl * 2}px */`
|
|
1119
|
+
);
|
|
1120
|
+
decls.push(`--radius-full: ${config.radius.pill}px;`);
|
|
1121
|
+
return decls;
|
|
1122
|
+
}
|
|
1123
|
+
function generateShadowPrimitives(config) {
|
|
1124
|
+
return [
|
|
1125
|
+
`--shadow-xs: ${config.shadows.xs};`,
|
|
1126
|
+
`--shadow-sm: ${config.shadows.sm};`,
|
|
1127
|
+
`--shadow-md: ${config.shadows.md};`,
|
|
1128
|
+
`--shadow-lg: ${config.shadows.lg};`,
|
|
1129
|
+
`--shadow-xl: ${config.shadows.xl};`
|
|
1130
|
+
];
|
|
1131
|
+
}
|
|
1132
|
+
function generateTypographyPrimitives(config) {
|
|
1133
|
+
const decls = [];
|
|
1134
|
+
const scale = config.typography.scale;
|
|
1135
|
+
decls.push(`font-size: ${scale === 1 ? "1rem" : `${scale}rem`};`);
|
|
1136
|
+
decls.push(`--font-heading: ${config.typography.heading.family};`);
|
|
1137
|
+
decls.push(`--font-display: ${config.typography.display.family};`);
|
|
1138
|
+
decls.push(`--font-sans: ${config.typography.body.family};`);
|
|
1139
|
+
decls.push(`--font-body: ${config.typography.body.family};`);
|
|
1140
|
+
decls.push(`--font-mono: ${config.typography.mono.family};`);
|
|
1141
|
+
const fontSizes = {
|
|
1142
|
+
xs: 12,
|
|
1143
|
+
sm: 14,
|
|
1144
|
+
base: 16,
|
|
1145
|
+
lg: 18,
|
|
1146
|
+
xl: 20,
|
|
1147
|
+
"2xl": 24,
|
|
1148
|
+
"3xl": 30,
|
|
1149
|
+
"4xl": 36
|
|
1150
|
+
};
|
|
1151
|
+
for (const [name, px] of Object.entries(fontSizes)) {
|
|
1152
|
+
decls.push(`--font-size-${name}: ${px / 16}rem; /* ${px}px */`);
|
|
1153
|
+
}
|
|
1154
|
+
decls.push(`--font-weight-normal: ${config.typography.body.weight};`);
|
|
1155
|
+
decls.push("--font-weight-medium: 500;");
|
|
1156
|
+
decls.push(`--font-weight-semibold: ${config.typography.heading.weight};`);
|
|
1157
|
+
decls.push("--font-weight-bold: 700;");
|
|
1158
|
+
decls.push(`--weight-display: ${config.typography.display.weight};`);
|
|
1159
|
+
const lineHeights = {
|
|
1160
|
+
none: 1,
|
|
1161
|
+
tight: 1.25,
|
|
1162
|
+
snug: 1.375,
|
|
1163
|
+
normal: 1.5,
|
|
1164
|
+
relaxed: 1.625,
|
|
1165
|
+
loose: 2
|
|
1166
|
+
};
|
|
1167
|
+
for (const [name, value] of Object.entries(lineHeights)) {
|
|
1168
|
+
decls.push(`--line-height-${name}: ${value};`);
|
|
1169
|
+
}
|
|
1170
|
+
return decls;
|
|
1171
|
+
}
|
|
1172
|
+
function generateMotionPrimitives(config) {
|
|
1173
|
+
const decls = [];
|
|
1174
|
+
decls.push(`--motion-duration-100: ${config.motion["duration-fast"]};`);
|
|
1175
|
+
decls.push("--motion-duration-150: 150ms;");
|
|
1176
|
+
decls.push(`--motion-duration-200: ${config.motion["duration-normal"]};`);
|
|
1177
|
+
decls.push("--motion-duration-300: 300ms;");
|
|
1178
|
+
decls.push(`--motion-duration-500: ${config.motion["duration-slow"]};`);
|
|
1179
|
+
decls.push("--motion-duration-800: 800ms;");
|
|
1180
|
+
decls.push("--motion-easing-linear: linear;");
|
|
1181
|
+
decls.push("--motion-easing-ease-in: cubic-bezier(0.4, 0, 1, 1);");
|
|
1182
|
+
decls.push("--motion-easing-ease-out: cubic-bezier(0, 0, 0.2, 1);");
|
|
1183
|
+
decls.push(`--motion-easing-ease-in-out: ${config.motion.easing};`);
|
|
1184
|
+
decls.push(
|
|
1185
|
+
"--motion-easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);"
|
|
1186
|
+
);
|
|
1187
|
+
return decls;
|
|
1188
|
+
}
|
|
1189
|
+
function generateMiscPrimitives() {
|
|
1190
|
+
return [
|
|
1191
|
+
// Border widths
|
|
1192
|
+
"--border-width-1: 1px;",
|
|
1193
|
+
"--border-width-2: 2px;",
|
|
1194
|
+
"--border-width-3: 3px;",
|
|
1195
|
+
"--border-width-4: 4px;",
|
|
1196
|
+
// Z-index
|
|
1197
|
+
"--z-base: 0;",
|
|
1198
|
+
"--z-raised: 1;",
|
|
1199
|
+
"--z-dropdown: 1000;",
|
|
1200
|
+
"--z-sticky: 1100;",
|
|
1201
|
+
"--z-modal: 1300;",
|
|
1202
|
+
"--z-popover: 1400;",
|
|
1203
|
+
"--z-toast: 1500;",
|
|
1204
|
+
// Overlay
|
|
1205
|
+
"--overlay-bg: rgba(0, 0, 0, 0.5);",
|
|
1206
|
+
// Focus ring
|
|
1207
|
+
"--focus-ring-width: 2px;",
|
|
1208
|
+
"--focus-ring-offset: 2px;"
|
|
1209
|
+
];
|
|
1210
|
+
}
|
|
1211
|
+
function generatePrimitivesCss(primitives, config) {
|
|
1212
|
+
const lines = [];
|
|
1213
|
+
lines.push(sectionComment("Primitive: Colors"));
|
|
1214
|
+
lines.push(
|
|
1215
|
+
block(":root", [generateColorPrimitives(primitives)])
|
|
1216
|
+
);
|
|
1217
|
+
lines.push(sectionComment("Primitive: Spacing"));
|
|
1218
|
+
lines.push(block(":root", generateSpacingPrimitives(config)));
|
|
1219
|
+
lines.push(sectionComment("Primitive: Border Radius"));
|
|
1220
|
+
lines.push(block(":root", generateRadiusPrimitives(config)));
|
|
1221
|
+
lines.push(sectionComment("Primitive: Typography"));
|
|
1222
|
+
lines.push(block(":root", generateTypographyPrimitives(config)));
|
|
1223
|
+
lines.push(sectionComment("Primitive: Shadows"));
|
|
1224
|
+
lines.push(block(":root", generateShadowPrimitives(config)));
|
|
1225
|
+
lines.push(sectionComment("Primitive: Motion"));
|
|
1226
|
+
lines.push(block(":root", generateMotionPrimitives(config)));
|
|
1227
|
+
lines.push(sectionComment("Primitive: Miscellaneous"));
|
|
1228
|
+
lines.push(block(":root", generateMiscPrimitives()));
|
|
1229
|
+
return header("Visor Theme \u2014 Primitives") + lines.join("\n");
|
|
1230
|
+
}
|
|
1231
|
+
function generateSemanticCss(tokens) {
|
|
1232
|
+
const lines = [];
|
|
1233
|
+
lines.push(sectionComment("Semantic: Text"));
|
|
1234
|
+
const textDecls = Object.entries(tokens.text).map(
|
|
1235
|
+
([name, { light }]) => `--text-${name}: ${light};`
|
|
1236
|
+
);
|
|
1237
|
+
lines.push(block(":root", textDecls));
|
|
1238
|
+
lines.push(sectionComment("Semantic: Surface"));
|
|
1239
|
+
const surfaceDecls = Object.entries(tokens.surface).map(
|
|
1240
|
+
([name, { light }]) => `--surface-${name}: ${light};`
|
|
1241
|
+
);
|
|
1242
|
+
lines.push(block(":root", surfaceDecls));
|
|
1243
|
+
lines.push(sectionComment("Semantic: Border"));
|
|
1244
|
+
const borderDecls = Object.entries(tokens.border).map(
|
|
1245
|
+
([name, { light }]) => `--border-${name}: ${light};`
|
|
1246
|
+
);
|
|
1247
|
+
lines.push(block(":root", borderDecls));
|
|
1248
|
+
lines.push(sectionComment("Semantic: Interactive"));
|
|
1249
|
+
const interactiveDecls = Object.entries(tokens.interactive).map(
|
|
1250
|
+
([name, { light }]) => `--interactive-${name}: ${light};`
|
|
1251
|
+
);
|
|
1252
|
+
lines.push(block(":root", interactiveDecls));
|
|
1253
|
+
return header("Visor Theme \u2014 Semantic") + lines.join("\n");
|
|
1254
|
+
}
|
|
1255
|
+
function buildAdaptiveDecls(tokens, theme) {
|
|
1256
|
+
const textDecls = Object.entries(tokens.text).map(
|
|
1257
|
+
([name, values]) => `--text-${name}: ${values[theme]};`
|
|
1258
|
+
);
|
|
1259
|
+
const surfaceDecls = Object.entries(tokens.surface).map(
|
|
1260
|
+
([name, values]) => `--surface-${name}: ${values[theme]};`
|
|
1261
|
+
);
|
|
1262
|
+
const borderDecls = Object.entries(tokens.border).map(
|
|
1263
|
+
([name, values]) => `--border-${name}: ${values[theme]};`
|
|
1264
|
+
);
|
|
1265
|
+
const interactiveDecls = Object.entries(tokens.interactive).map(
|
|
1266
|
+
([name, values]) => `--interactive-${name}: ${values[theme]};`
|
|
1267
|
+
);
|
|
1268
|
+
return { textDecls, surfaceDecls, borderDecls, interactiveDecls };
|
|
1269
|
+
}
|
|
1270
|
+
function generateLightCss(tokens) {
|
|
1271
|
+
const lines = [];
|
|
1272
|
+
const { textDecls, surfaceDecls, borderDecls, interactiveDecls } = buildAdaptiveDecls(tokens, "light");
|
|
1273
|
+
lines.push(sectionComment("Adaptive: Text (light)"));
|
|
1274
|
+
lines.push(block(":root", textDecls));
|
|
1275
|
+
lines.push(sectionComment("Adaptive: Surface (light)"));
|
|
1276
|
+
lines.push(block(":root", surfaceDecls));
|
|
1277
|
+
lines.push(sectionComment("Adaptive: Border (light)"));
|
|
1278
|
+
lines.push(block(":root", borderDecls));
|
|
1279
|
+
lines.push(sectionComment("Adaptive: Interactive (light)"));
|
|
1280
|
+
lines.push(block(":root", interactiveDecls));
|
|
1281
|
+
return header("Visor Theme \u2014 Light") + lines.join("\n");
|
|
1282
|
+
}
|
|
1283
|
+
function generateDarkCss(tokens) {
|
|
1284
|
+
const lines = [];
|
|
1285
|
+
const { textDecls, surfaceDecls, borderDecls, interactiveDecls } = buildAdaptiveDecls(tokens, "dark");
|
|
1286
|
+
const darkSelectors = [".dark", ".theme-dark", '[data-theme="dark"]'];
|
|
1287
|
+
const darkSelector = darkSelectors.join(",\n");
|
|
1288
|
+
const prefersSelector = ':root:not(.light):not(.theme-light):not([data-theme="light"])';
|
|
1289
|
+
lines.push(sectionComment("Adaptive: Text (dark) \u2014 manual toggle"));
|
|
1290
|
+
lines.push(block(darkSelector, textDecls));
|
|
1291
|
+
lines.push(sectionComment("Adaptive: Surface (dark) \u2014 manual toggle"));
|
|
1292
|
+
lines.push(block(darkSelector, surfaceDecls));
|
|
1293
|
+
lines.push(sectionComment("Adaptive: Border (dark) \u2014 manual toggle"));
|
|
1294
|
+
lines.push(block(darkSelector, borderDecls));
|
|
1295
|
+
lines.push(sectionComment("Adaptive: Interactive (dark) \u2014 manual toggle"));
|
|
1296
|
+
lines.push(block(darkSelector, interactiveDecls));
|
|
1297
|
+
lines.push(
|
|
1298
|
+
sectionComment("Adaptive: Text (dark) \u2014 prefers-color-scheme")
|
|
1299
|
+
);
|
|
1300
|
+
lines.push(
|
|
1301
|
+
`@media (prefers-color-scheme: dark) {
|
|
1302
|
+
${block(prefersSelector, textDecls)}}`
|
|
1303
|
+
);
|
|
1304
|
+
lines.push("");
|
|
1305
|
+
lines.push(
|
|
1306
|
+
sectionComment("Adaptive: Surface (dark) \u2014 prefers-color-scheme")
|
|
1307
|
+
);
|
|
1308
|
+
lines.push(
|
|
1309
|
+
`@media (prefers-color-scheme: dark) {
|
|
1310
|
+
${block(prefersSelector, surfaceDecls)}}`
|
|
1311
|
+
);
|
|
1312
|
+
lines.push("");
|
|
1313
|
+
lines.push(
|
|
1314
|
+
sectionComment("Adaptive: Border (dark) \u2014 prefers-color-scheme")
|
|
1315
|
+
);
|
|
1316
|
+
lines.push(
|
|
1317
|
+
`@media (prefers-color-scheme: dark) {
|
|
1318
|
+
${block(prefersSelector, borderDecls)}}`
|
|
1319
|
+
);
|
|
1320
|
+
lines.push("");
|
|
1321
|
+
lines.push(
|
|
1322
|
+
sectionComment("Adaptive: Interactive (dark) \u2014 prefers-color-scheme")
|
|
1323
|
+
);
|
|
1324
|
+
lines.push(
|
|
1325
|
+
`@media (prefers-color-scheme: dark) {
|
|
1326
|
+
${block(prefersSelector, interactiveDecls)}}`
|
|
1327
|
+
);
|
|
1328
|
+
lines.push("");
|
|
1329
|
+
return header("Visor Theme \u2014 Dark") + lines.join("\n");
|
|
1330
|
+
}
|
|
1331
|
+
function generateFullBundleCss(primitives, tokens, config) {
|
|
1332
|
+
const lines = [
|
|
1333
|
+
header("Visor Theme \u2014 Full Bundle"),
|
|
1334
|
+
"/* Import order: primitives \u2192 adaptive (light) \u2192 adaptive (dark) */",
|
|
1335
|
+
""
|
|
1336
|
+
];
|
|
1337
|
+
lines.push(
|
|
1338
|
+
"/* ============================================",
|
|
1339
|
+
" Tier 1: Primitives",
|
|
1340
|
+
" ============================================ */"
|
|
1341
|
+
);
|
|
1342
|
+
const primitivesBody = generatePrimitivesCss(primitives, config).split("\n").slice(6).join("\n");
|
|
1343
|
+
lines.push(primitivesBody);
|
|
1344
|
+
lines.push(
|
|
1345
|
+
"/* ============================================",
|
|
1346
|
+
" Tier 3: Adaptive \u2014 Light Theme (:root)",
|
|
1347
|
+
" ============================================ */"
|
|
1348
|
+
);
|
|
1349
|
+
const lightBody = generateLightCss(tokens).split("\n").slice(6).join("\n");
|
|
1350
|
+
lines.push(lightBody);
|
|
1351
|
+
lines.push(
|
|
1352
|
+
"/* ============================================",
|
|
1353
|
+
' Tier 3: Adaptive \u2014 Dark Theme (.dark, .theme-dark, [data-theme="dark"])',
|
|
1354
|
+
" and @media (prefers-color-scheme: dark)",
|
|
1355
|
+
" ============================================ */"
|
|
1356
|
+
);
|
|
1357
|
+
const darkBody = generateDarkCss(tokens).split("\n").slice(6).join("\n");
|
|
1358
|
+
lines.push(darkBody);
|
|
1359
|
+
return lines.join("\n");
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
export {
|
|
1363
|
+
googleFontsCatalog,
|
|
1364
|
+
lookupGoogleFont,
|
|
1365
|
+
VISOR_FONTS_CDN,
|
|
1366
|
+
buildVisorFontUrl,
|
|
1367
|
+
resolveFont,
|
|
1368
|
+
generatePreloadLinks,
|
|
1369
|
+
generateStylesheetLinks,
|
|
1370
|
+
resolveThemeFonts,
|
|
1371
|
+
normalizeHex,
|
|
1372
|
+
isValidHex,
|
|
1373
|
+
hexToRgb,
|
|
1374
|
+
rgbToHex,
|
|
1375
|
+
rgbToOklch,
|
|
1376
|
+
hexToOklch,
|
|
1377
|
+
clampToSrgb,
|
|
1378
|
+
oklchToHex,
|
|
1379
|
+
getContrastRatio,
|
|
1380
|
+
parseHex,
|
|
1381
|
+
parseRgba,
|
|
1382
|
+
parseHsla,
|
|
1383
|
+
parseOklch,
|
|
1384
|
+
parseColor,
|
|
1385
|
+
isValidColor,
|
|
1386
|
+
compositeOverBackground,
|
|
1387
|
+
serializeColor,
|
|
1388
|
+
FULL_SHADE_STEPS,
|
|
1389
|
+
SELECTIVE_SHADE_STEPS,
|
|
1390
|
+
TAILWIND_GRAY,
|
|
1391
|
+
generateShadeScale,
|
|
1392
|
+
header,
|
|
1393
|
+
sectionComment,
|
|
1394
|
+
generatePrimitivesCss,
|
|
1395
|
+
generateSemanticCss,
|
|
1396
|
+
generateLightCss,
|
|
1397
|
+
generateDarkCss,
|
|
1398
|
+
generateFullBundleCss
|
|
1399
|
+
};
|