emily-css 1.2.7 → 1.2.9
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/CHANGELOG.md +45 -1
- package/README.md +18 -0
- package/package.json +1 -1
- package/src/generators/background.js +11 -0
- package/src/generators/display.js +2 -0
- package/src/generators/effects.js +2 -0
- package/src/generators/overflow.js +9 -0
- package/src/generators/positioning.js +14 -0
- package/src/generators/sizing.js +1 -0
- package/src/generators/transforms.js +7 -1
- package/src/index.js +1890 -1849
- package/src/init.js +1011 -812
- package/src/migrate.js +6 -0
- package/src/purge.js +8 -0
- package/src/purgeConfig.js +9 -1
package/src/index.js
CHANGED
|
@@ -1,1849 +1,1890 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { generateManifest } = require('./manifest');
|
|
6
|
-
const { generateIntellisense } = require('./intellisense');
|
|
7
|
-
const {
|
|
8
|
-
getConfigPath,
|
|
9
|
-
getConfig,
|
|
10
|
-
getFullCssPath,
|
|
11
|
-
getProductionCssPath,
|
|
12
|
-
getManifestSettings,
|
|
13
|
-
getManifestOutputPath,
|
|
14
|
-
getIntellisenseSettings,
|
|
15
|
-
getIntellisenseOutputPath,
|
|
16
|
-
ensureDirectoryForFile,
|
|
17
|
-
getSourceDir,
|
|
18
|
-
} = require('./config');
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// COLOUR GENERATION
|
|
23
|
-
// ============================================================================
|
|
24
|
-
// Generate 10-shade colour scale using OKLCH (perceptually uniform colour space)
|
|
25
|
-
// OKLCH produces visually even steps across all hues — unlike HSL which creates
|
|
26
|
-
// muddy mid-tones on warm colours (yellows, greens, oranges).
|
|
27
|
-
//
|
|
28
|
-
// Conversion pipeline: Hex → sRGB → Linear RGB → OKLab → OKLCH → (modify L) → reverse
|
|
29
|
-
// No external dependencies. Maths from Björn Ottosson's OKLab specification.
|
|
30
|
-
//
|
|
31
|
-
// Input: #0077b6 → Output: { 10: '#...', 20: '#...', ..., 100: '#...' }
|
|
32
|
-
|
|
33
|
-
// sRGB component to linear light
|
|
34
|
-
function srgbToLinear(c) {
|
|
35
|
-
const val = c / 255;
|
|
36
|
-
return val <= 0.04045 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Linear light component to sRGB (clamped to 0–255)
|
|
40
|
-
function linearToSrgb(c) {
|
|
41
|
-
const clamped = Math.max(0, Math.min(1, c));
|
|
42
|
-
const out = clamped <= 0.0031308
|
|
43
|
-
? 12.92 * clamped
|
|
44
|
-
: 1.055 * Math.pow(clamped, 1 / 2.4) - 0.055;
|
|
45
|
-
return Math.round(Math.max(0, Math.min(1, out)) * 255);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Hex string → OKLCH { l, c, h }
|
|
49
|
-
function hexToOklch(hex) {
|
|
50
|
-
const r = srgbToLinear(parseInt(hex.slice(1, 3), 16));
|
|
51
|
-
const g = srgbToLinear(parseInt(hex.slice(3, 5), 16));
|
|
52
|
-
const b = srgbToLinear(parseInt(hex.slice(5, 7), 16));
|
|
53
|
-
|
|
54
|
-
// Linear RGB → OKLab (M1 matrix then cube-root then M2 matrix)
|
|
55
|
-
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
56
|
-
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
57
|
-
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
58
|
-
|
|
59
|
-
const L = 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s;
|
|
60
|
-
const a = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s;
|
|
61
|
-
const bv = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s;
|
|
62
|
-
|
|
63
|
-
// OKLab → OKLCH
|
|
64
|
-
const C = Math.sqrt(a * a + bv * bv);
|
|
65
|
-
const H = (Math.atan2(bv, a) * 180) / Math.PI;
|
|
66
|
-
|
|
67
|
-
return { l: L, c: C, h: H < 0 ? H + 360 : H };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// OKLCH { l, c, h } → hex string
|
|
71
|
-
function oklchToHex(l, c, h) {
|
|
72
|
-
// OKLCH → OKLab
|
|
73
|
-
const hRad = (h * Math.PI) / 180;
|
|
74
|
-
const a = c * Math.cos(hRad);
|
|
75
|
-
const bv = c * Math.sin(hRad);
|
|
76
|
-
|
|
77
|
-
// OKLab → Linear RGB (M2 inverse then cube then M1 inverse)
|
|
78
|
-
const l_ = l + 0.3963377774 * a + 0.2158037573 * bv;
|
|
79
|
-
const m_ = l - 0.1055613458 * a - 0.0638541728 * bv;
|
|
80
|
-
const s_ = l - 0.0894841775 * a - 1.2914855480 * bv;
|
|
81
|
-
|
|
82
|
-
const lc = l_ * l_ * l_;
|
|
83
|
-
const mc = m_ * m_ * m_;
|
|
84
|
-
const sc = s_ * s_ * s_;
|
|
85
|
-
|
|
86
|
-
const r = 4.0767416621 * lc - 3.3077115913 * mc + 0.2309699292 * sc;
|
|
87
|
-
const g = -1.2684380046 * lc + 2.6097574011 * mc - 0.3413193965 * sc;
|
|
88
|
-
const b = -0.0041960863 * lc - 0.7034186147 * mc + 1.7076147010 * sc;
|
|
89
|
-
|
|
90
|
-
const rOut = linearToSrgb(r).toString(16).padStart(2, '0');
|
|
91
|
-
const gOut = linearToSrgb(g).toString(16).padStart(2, '0');
|
|
92
|
-
const bOut = linearToSrgb(b).toString(16).padStart(2, '0');
|
|
93
|
-
|
|
94
|
-
return `#${rOut}${gOut}${bOut}`.toUpperCase();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function generateColourScale(baseHex) {
|
|
98
|
-
const { l: baseL, c: baseC, h: baseH } = hexToOklch(baseHex);
|
|
99
|
-
const scale = {};
|
|
100
|
-
|
|
101
|
-
// Shade scale: 10 = near-white, 80 = exact input colour, 100 = near-black
|
|
102
|
-
// Lightness targets in OKLCH (0–1 scale):
|
|
103
|
-
// shade 10 → L ≈ 0.97 (very light tint)
|
|
104
|
-
// shade 80 → L = baseL (exact input)
|
|
105
|
-
// shade 100 → L ≈ 0.15 (very dark tone)
|
|
106
|
-
//
|
|
107
|
-
// Chroma is preserved from the base colour throughout — hue is never shifted.
|
|
108
|
-
// At extreme lightness values chroma is gently reduced to stay in sRGB gamut.
|
|
109
|
-
|
|
110
|
-
const LIGHT_L = 0.97; // shade 10 lightness
|
|
111
|
-
const DARK_L = 0.15; // shade 100 lightness
|
|
112
|
-
|
|
113
|
-
const steps = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
|
|
114
|
-
|
|
115
|
-
steps.forEach(step => {
|
|
116
|
-
if (step === 80) {
|
|
117
|
-
scale[step] = baseHex.toUpperCase();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
let newL;
|
|
122
|
-
if (step < 80) {
|
|
123
|
-
// 10–70: interpolate from LIGHT_L down to baseL
|
|
124
|
-
const t = (step / 80); // 0 → 1 as step goes 0 → 80
|
|
125
|
-
newL = LIGHT_L - t * (LIGHT_L - baseL);
|
|
126
|
-
} else {
|
|
127
|
-
// 90–100: interpolate from baseL down to DARK_L
|
|
128
|
-
const t = (step - 80) / 20; // 0 → 1 as step goes 80 → 100
|
|
129
|
-
newL = baseL - t * (baseL - DARK_L);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Reduce chroma slightly at extremes to avoid out-of-gamut clipping
|
|
133
|
-
const chromaScale = 1 - Math.max(0, (newL - 0.90) / 0.07) * 0.5 // reduce near white
|
|
134
|
-
- Math.max(0, (0.25 - newL) / 0.10) * 0.5; // reduce near black
|
|
135
|
-
const newC = baseC * Math.max(0, chromaScale);
|
|
136
|
-
|
|
137
|
-
scale[step] = oklchToHex(newL, newC, baseH);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
return scale;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function generateAllColours(colourConfig) {
|
|
144
|
-
const allColours = {};
|
|
145
|
-
|
|
146
|
-
Object.entries(colourConfig).forEach(([name, baseHex]) => {
|
|
147
|
-
allColours[name] = generateColourScale(baseHex);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return allColours;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ============================================================================
|
|
154
|
-
// SPACING SCALE
|
|
155
|
-
// ============================================================================
|
|
156
|
-
|
|
157
|
-
function generateSpacing(baseUnit, scale) {
|
|
158
|
-
// Spacing values are defined explicitly in emily.config.json under spacing.scale.
|
|
159
|
-
// The baseUnit key in config is informational only — it documents the design intent
|
|
160
|
-
// (e.g. "this system is based on 8px") but does not drive generation.
|
|
161
|
-
return scale;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ============================================================================
|
|
165
|
-
// FONT PRESETS
|
|
166
|
-
// ============================================================================
|
|
167
|
-
|
|
168
|
-
// Font presets define the CSS font-family stack only.
|
|
169
|
-
// Loading the actual font files is the user's responsibility — link them in your HTML
|
|
170
|
-
// or use @fontsource packages in your build. emily-css does not generate @import rules
|
|
171
|
-
// for external CDNs so it stays self-contained and works offline.
|
|
172
|
-
// See docs: https://emilyui.com/docs/getting-started
|
|
173
|
-
const FONT_PRESETS = {
|
|
174
|
-
'system': {
|
|
175
|
-
stack: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
176
|
-
},
|
|
177
|
-
'inter': {
|
|
178
|
-
name: 'Inter',
|
|
179
|
-
stack: '"Inter", system-ui, sans-serif',
|
|
180
|
-
},
|
|
181
|
-
'lexend': {
|
|
182
|
-
name: 'Lexend',
|
|
183
|
-
stack: '"Lexend", system-ui, sans-serif',
|
|
184
|
-
},
|
|
185
|
-
'georgia': {
|
|
186
|
-
stack: 'Georgia, "Times New Roman", serif',
|
|
187
|
-
},
|
|
188
|
-
'dm-sans': {
|
|
189
|
-
name: 'DM Sans',
|
|
190
|
-
stack: '"DM Sans", system-ui, sans-serif',
|
|
191
|
-
},
|
|
192
|
-
'nunito': {
|
|
193
|
-
name: 'Nunito',
|
|
194
|
-
stack: '"Nunito", system-ui, sans-serif',
|
|
195
|
-
},
|
|
196
|
-
'atkinson': {
|
|
197
|
-
name: 'Atkinson Hyperlegible',
|
|
198
|
-
stack: '"Atkinson Hyperlegible", system-ui, sans-serif',
|
|
199
|
-
},
|
|
200
|
-
'mono': {
|
|
201
|
-
stack: '"Menlo", "Monaco", "Courier New", monospace',
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
function generateFontCSS(config) {
|
|
206
|
-
// Support both legacy string format and new { heading, body } object format
|
|
207
|
-
const fontConfig = config.fontFamily || 'system';
|
|
208
|
-
let headingKey, bodyKey;
|
|
209
|
-
|
|
210
|
-
if (typeof fontConfig === 'object') {
|
|
211
|
-
headingKey = (fontConfig.heading || 'system').toLowerCase();
|
|
212
|
-
bodyKey = (fontConfig.body || 'system').toLowerCase();
|
|
213
|
-
} else {
|
|
214
|
-
headingKey = fontConfig.toLowerCase();
|
|
215
|
-
bodyKey = fontConfig.toLowerCase();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const headingPreset = FONT_PRESETS[headingKey] || FONT_PRESETS['system'];
|
|
219
|
-
const bodyPreset = FONT_PRESETS[bodyKey] || FONT_PRESETS['system'];
|
|
220
|
-
|
|
221
|
-
let fontFace = '';
|
|
222
|
-
let bodyFont = '';
|
|
223
|
-
|
|
224
|
-
bodyFont += ` body {\n font-family: ${bodyPreset.stack};\n font-synthesis: style;\n }\n`;
|
|
225
|
-
bodyFont += ` h1, h2, h3, h4, h5, h6 {\n font-family: ${headingPreset.stack};\n }\n`;
|
|
226
|
-
|
|
227
|
-
return { fontFace, bodyFont };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// ============================================================================
|
|
231
|
-
// CSS VARIABLE GENERATION
|
|
232
|
-
// ============================================================================
|
|
233
|
-
|
|
234
|
-
function generateCSSVariables(colours, spacing, config) {
|
|
235
|
-
let css = `:root {\n`;
|
|
236
|
-
|
|
237
|
-
// Colour variables (full shade scale)
|
|
238
|
-
Object.entries(colours).forEach(([colourName, shades]) => {
|
|
239
|
-
Object.entries(shades).forEach(([shade, hex]) => {
|
|
240
|
-
css += ` --color-${colourName}-${shade}: ${hex};\n`;
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Semantic colour variables (single value, no shade scale)
|
|
245
|
-
if (config.semanticColours) {
|
|
246
|
-
Object.entries(config.semanticColours).forEach(([name, hex]) => {
|
|
247
|
-
css += ` --color-${name}: ${hex};\n`;
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
css += ` --focus-ring-glow: color-mix(in srgb, var(--color-brand-80) 12%, transparent);\n`;
|
|
251
|
-
|
|
252
|
-
// Spacing variables
|
|
253
|
-
Object.entries(spacing).forEach(([key, value]) => {
|
|
254
|
-
css += ` --space-${key}: ${value};\n`;
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// Font size variables with line-height
|
|
258
|
-
config.typography.fontSizes.forEach(fontSize => {
|
|
259
|
-
const sizeVal = parseInt(fontSize.value);
|
|
260
|
-
css += ` --text-${fontSize.name}: ${fontSize.value};\n`;
|
|
261
|
-
css += ` --leading-${fontSize.name}: ${fontSize.lineHeight};\n`;
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Font weight variables
|
|
265
|
-
Object.entries(config.typography.fontWeights).forEach(([name, weight]) => {
|
|
266
|
-
css += ` --font-weight-${name}: ${weight};\n`;
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Breakpoints
|
|
270
|
-
Object.entries(config.breakpoints).forEach(([name, value]) => {
|
|
271
|
-
css += ` --breakpoint-${name}: ${value};\n`;
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// Shadows
|
|
275
|
-
Object.entries(config.shadows).forEach(([name, shadow]) => {
|
|
276
|
-
css += ` --shadow-${name}: ${shadow};\n`;
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
// Z-index
|
|
280
|
-
Object.entries(config.zIndex).forEach(([name, value]) => {
|
|
281
|
-
css += ` --z-${name}: ${value};\n`;
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// Transitions
|
|
285
|
-
css += ` --transition-duration: ${config.transitions.base};\n`;
|
|
286
|
-
css += ` --transition-timing: ${config.transitions.timing};\n`;
|
|
287
|
-
|
|
288
|
-
css += `}\n\n`;
|
|
289
|
-
return css;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const {
|
|
293
|
-
displayUtilities,
|
|
294
|
-
sizingUtilities,
|
|
295
|
-
positioningUtilities,
|
|
296
|
-
overflowUtilities,
|
|
297
|
-
opacityUtilities,
|
|
298
|
-
transitionUtilities,
|
|
299
|
-
transformUtilities,
|
|
300
|
-
shadowUtilities,
|
|
301
|
-
ringUtilities,
|
|
302
|
-
objectUtilities,
|
|
303
|
-
tableListUtilities,
|
|
304
|
-
svgUtilities,
|
|
305
|
-
formUtilities,
|
|
306
|
-
verticalAlignUtilities,
|
|
307
|
-
contentScrollUtilities,
|
|
308
|
-
blendUtilities,
|
|
309
|
-
cursorUtilities,
|
|
310
|
-
accessibilityUtilities,
|
|
311
|
-
containerUtilities,
|
|
312
|
-
codeUtilities,
|
|
313
|
-
animationUtilities,
|
|
314
|
-
backdropUtilities,
|
|
315
|
-
spaceUtilities,
|
|
316
|
-
divideUtilities,
|
|
317
|
-
backgroundUtilities,
|
|
318
|
-
filterUtilities,
|
|
319
|
-
} = require('./generators');
|
|
320
|
-
|
|
321
|
-
// ============================================================================
|
|
322
|
-
// SPACING UTILITIES
|
|
323
|
-
// ============================================================================
|
|
324
|
-
|
|
325
|
-
function escapeClassName(key) {
|
|
326
|
-
// Escape dots in class names for CSS (e.g., "0.5" becomes "0\.5")
|
|
327
|
-
return key.replace(/\./g, '\\.');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function generateSpacingUtilities(spacing) {
|
|
331
|
-
let css = `/* Spacing: Padding & Margin */\n`;
|
|
332
|
-
|
|
333
|
-
// Padding
|
|
334
|
-
Object.entries(spacing).forEach(([key, value]) => {
|
|
335
|
-
const escaped = escapeClassName(key);
|
|
336
|
-
css += `.p-${escaped} { padding: ${value}; }\n`;
|
|
337
|
-
css += `.px-${escaped} { padding-left: ${value}; padding-right: ${value}; }\n`;
|
|
338
|
-
css += `.py-${escaped} { padding-top: ${value}; padding-bottom: ${value}; }\n`;
|
|
339
|
-
css += `.pt-${escaped} { padding-top: ${value}; }\n`;
|
|
340
|
-
css += `.pr-${escaped} { padding-right: ${value}; }\n`;
|
|
341
|
-
css += `.pb-${escaped} { padding-bottom: ${value}; }\n`;
|
|
342
|
-
css += `.pl-${escaped} { padding-left: ${value}; }\n`;
|
|
343
|
-
css += `.ps-${escaped} { padding-inline-start: ${value}; }\n`;
|
|
344
|
-
css += `.pe-${escaped} { padding-inline-end: ${value}; }\n`;
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// Margin
|
|
348
|
-
Object.entries(spacing).forEach(([key, value]) => {
|
|
349
|
-
const escaped = escapeClassName(key);
|
|
350
|
-
css += `.m-${escaped} { margin: ${value}; }\n`;
|
|
351
|
-
css += `.mx-${escaped} { margin-left: ${value}; margin-right: ${value}; }\n`;
|
|
352
|
-
css += `.my-${escaped} { margin-top: ${value}; margin-bottom: ${value}; }\n`;
|
|
353
|
-
css += `.mt-${escaped} { margin-top: ${value}; }\n`;
|
|
354
|
-
css += `.mr-${escaped} { margin-right: ${value}; }\n`;
|
|
355
|
-
css += `.mb-${escaped} { margin-bottom: ${value}; }\n`;
|
|
356
|
-
css += `.ml-${escaped} { margin-left: ${value}; }\n`;
|
|
357
|
-
css += `.ms-${escaped} { margin-inline-start: ${value}; }\n`;
|
|
358
|
-
css += `.me-${escaped} { margin-inline-end: ${value}; }\n`;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
css +=
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
css
|
|
386
|
-
|
|
387
|
-
css += `.flex
|
|
388
|
-
|
|
389
|
-
//
|
|
390
|
-
css += `.flex-
|
|
391
|
-
css += `.flex-
|
|
392
|
-
css += `.flex-
|
|
393
|
-
css += `.flex-
|
|
394
|
-
|
|
395
|
-
//
|
|
396
|
-
css += `.
|
|
397
|
-
css += `.
|
|
398
|
-
css += `.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
css += `.
|
|
403
|
-
css += `.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
css += `.basis-${
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
css += `.
|
|
433
|
-
css += `.
|
|
434
|
-
css += `.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
css += `.
|
|
441
|
-
css += `.
|
|
442
|
-
css += `.
|
|
443
|
-
css += `.
|
|
444
|
-
css += `.
|
|
445
|
-
css += `.
|
|
446
|
-
css += `.
|
|
447
|
-
css += `.
|
|
448
|
-
|
|
449
|
-
//
|
|
450
|
-
css += `.
|
|
451
|
-
css += `.
|
|
452
|
-
css += `.
|
|
453
|
-
css += `.
|
|
454
|
-
css += `.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
css += `.
|
|
458
|
-
css += `.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
css += `.
|
|
462
|
-
css += `.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
css += `.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
css += `.
|
|
469
|
-
css += `.
|
|
470
|
-
css += `.
|
|
471
|
-
css += `.
|
|
472
|
-
css += `.
|
|
473
|
-
css += `.
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
css += `.place-
|
|
477
|
-
css += `.place-
|
|
478
|
-
css += `.place-
|
|
479
|
-
css += `.place-
|
|
480
|
-
css += `.place-
|
|
481
|
-
css += `.place-
|
|
482
|
-
css += `.place-
|
|
483
|
-
|
|
484
|
-
css +=
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
css +=
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
css
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
css += `.
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
css += `.
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
css += `.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
css += `.
|
|
536
|
-
css += `.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
css += `.
|
|
542
|
-
css += `.
|
|
543
|
-
|
|
544
|
-
css += `.
|
|
545
|
-
css += `.
|
|
546
|
-
css += `.
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
css +=
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
css += `.
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
css
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
css
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
css += `.
|
|
612
|
-
css += `.
|
|
613
|
-
|
|
614
|
-
css += `.
|
|
615
|
-
css += `.
|
|
616
|
-
|
|
617
|
-
css += `.
|
|
618
|
-
css += `.
|
|
619
|
-
css += `.
|
|
620
|
-
|
|
621
|
-
css += `.
|
|
622
|
-
css += `.
|
|
623
|
-
css += `.
|
|
624
|
-
css += `.
|
|
625
|
-
css += `.
|
|
626
|
-
css += `.
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
css += `.
|
|
631
|
-
css += `.
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
css += `.
|
|
637
|
-
css += `.
|
|
638
|
-
|
|
639
|
-
css += `.
|
|
640
|
-
css += `.
|
|
641
|
-
css += `.
|
|
642
|
-
css += `.
|
|
643
|
-
css += `.
|
|
644
|
-
css += `.
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
css += `.
|
|
648
|
-
css += `.
|
|
649
|
-
css += `.normal
|
|
650
|
-
|
|
651
|
-
css += `.
|
|
652
|
-
css += `.
|
|
653
|
-
|
|
654
|
-
css += `.
|
|
655
|
-
css += `.
|
|
656
|
-
css += `.
|
|
657
|
-
css += `.
|
|
658
|
-
css += `.
|
|
659
|
-
|
|
660
|
-
css +=
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
css += `.
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
css
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
css += `.border
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
css +=
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
css
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
css
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
//
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
});
|
|
981
|
-
|
|
982
|
-
return
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
.prose
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
.prose-emily
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
.
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
.
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
.
|
|
1175
|
-
display: flex;
|
|
1176
|
-
flex-
|
|
1177
|
-
gap: var(--space-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
margin-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
input[type="
|
|
1250
|
-
input[type="
|
|
1251
|
-
input[type="
|
|
1252
|
-
input[type="
|
|
1253
|
-
input[type="
|
|
1254
|
-
input[type="
|
|
1255
|
-
input[type="
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
border
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
input[type="
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
display:
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
border-radius: 50%;
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
color: var(--color-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
font-size: var(--text-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
border-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
utilityCss =
|
|
1528
|
-
utilityCss
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
margin-bottom: var(--space-
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
font-size:
|
|
1654
|
-
line-height: 1.
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
:
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
const
|
|
1725
|
-
const
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
const
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
const
|
|
1767
|
-
const
|
|
1768
|
-
const
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
const
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { generateManifest } = require('./manifest');
|
|
6
|
+
const { generateIntellisense } = require('./intellisense');
|
|
7
|
+
const {
|
|
8
|
+
getConfigPath,
|
|
9
|
+
getConfig,
|
|
10
|
+
getFullCssPath,
|
|
11
|
+
getProductionCssPath,
|
|
12
|
+
getManifestSettings,
|
|
13
|
+
getManifestOutputPath,
|
|
14
|
+
getIntellisenseSettings,
|
|
15
|
+
getIntellisenseOutputPath,
|
|
16
|
+
ensureDirectoryForFile,
|
|
17
|
+
getSourceDir,
|
|
18
|
+
} = require('./config');
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// COLOUR GENERATION
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Generate 10-shade colour scale using OKLCH (perceptually uniform colour space)
|
|
25
|
+
// OKLCH produces visually even steps across all hues — unlike HSL which creates
|
|
26
|
+
// muddy mid-tones on warm colours (yellows, greens, oranges).
|
|
27
|
+
//
|
|
28
|
+
// Conversion pipeline: Hex → sRGB → Linear RGB → OKLab → OKLCH → (modify L) → reverse
|
|
29
|
+
// No external dependencies. Maths from Björn Ottosson's OKLab specification.
|
|
30
|
+
//
|
|
31
|
+
// Input: #0077b6 → Output: { 10: '#...', 20: '#...', ..., 100: '#...' }
|
|
32
|
+
|
|
33
|
+
// sRGB component to linear light
|
|
34
|
+
function srgbToLinear(c) {
|
|
35
|
+
const val = c / 255;
|
|
36
|
+
return val <= 0.04045 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Linear light component to sRGB (clamped to 0–255)
|
|
40
|
+
function linearToSrgb(c) {
|
|
41
|
+
const clamped = Math.max(0, Math.min(1, c));
|
|
42
|
+
const out = clamped <= 0.0031308
|
|
43
|
+
? 12.92 * clamped
|
|
44
|
+
: 1.055 * Math.pow(clamped, 1 / 2.4) - 0.055;
|
|
45
|
+
return Math.round(Math.max(0, Math.min(1, out)) * 255);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Hex string → OKLCH { l, c, h }
|
|
49
|
+
function hexToOklch(hex) {
|
|
50
|
+
const r = srgbToLinear(parseInt(hex.slice(1, 3), 16));
|
|
51
|
+
const g = srgbToLinear(parseInt(hex.slice(3, 5), 16));
|
|
52
|
+
const b = srgbToLinear(parseInt(hex.slice(5, 7), 16));
|
|
53
|
+
|
|
54
|
+
// Linear RGB → OKLab (M1 matrix then cube-root then M2 matrix)
|
|
55
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
56
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
57
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
58
|
+
|
|
59
|
+
const L = 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s;
|
|
60
|
+
const a = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s;
|
|
61
|
+
const bv = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s;
|
|
62
|
+
|
|
63
|
+
// OKLab → OKLCH
|
|
64
|
+
const C = Math.sqrt(a * a + bv * bv);
|
|
65
|
+
const H = (Math.atan2(bv, a) * 180) / Math.PI;
|
|
66
|
+
|
|
67
|
+
return { l: L, c: C, h: H < 0 ? H + 360 : H };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// OKLCH { l, c, h } → hex string
|
|
71
|
+
function oklchToHex(l, c, h) {
|
|
72
|
+
// OKLCH → OKLab
|
|
73
|
+
const hRad = (h * Math.PI) / 180;
|
|
74
|
+
const a = c * Math.cos(hRad);
|
|
75
|
+
const bv = c * Math.sin(hRad);
|
|
76
|
+
|
|
77
|
+
// OKLab → Linear RGB (M2 inverse then cube then M1 inverse)
|
|
78
|
+
const l_ = l + 0.3963377774 * a + 0.2158037573 * bv;
|
|
79
|
+
const m_ = l - 0.1055613458 * a - 0.0638541728 * bv;
|
|
80
|
+
const s_ = l - 0.0894841775 * a - 1.2914855480 * bv;
|
|
81
|
+
|
|
82
|
+
const lc = l_ * l_ * l_;
|
|
83
|
+
const mc = m_ * m_ * m_;
|
|
84
|
+
const sc = s_ * s_ * s_;
|
|
85
|
+
|
|
86
|
+
const r = 4.0767416621 * lc - 3.3077115913 * mc + 0.2309699292 * sc;
|
|
87
|
+
const g = -1.2684380046 * lc + 2.6097574011 * mc - 0.3413193965 * sc;
|
|
88
|
+
const b = -0.0041960863 * lc - 0.7034186147 * mc + 1.7076147010 * sc;
|
|
89
|
+
|
|
90
|
+
const rOut = linearToSrgb(r).toString(16).padStart(2, '0');
|
|
91
|
+
const gOut = linearToSrgb(g).toString(16).padStart(2, '0');
|
|
92
|
+
const bOut = linearToSrgb(b).toString(16).padStart(2, '0');
|
|
93
|
+
|
|
94
|
+
return `#${rOut}${gOut}${bOut}`.toUpperCase();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function generateColourScale(baseHex) {
|
|
98
|
+
const { l: baseL, c: baseC, h: baseH } = hexToOklch(baseHex);
|
|
99
|
+
const scale = {};
|
|
100
|
+
|
|
101
|
+
// Shade scale: 10 = near-white, 80 = exact input colour, 100 = near-black
|
|
102
|
+
// Lightness targets in OKLCH (0–1 scale):
|
|
103
|
+
// shade 10 → L ≈ 0.97 (very light tint)
|
|
104
|
+
// shade 80 → L = baseL (exact input)
|
|
105
|
+
// shade 100 → L ≈ 0.15 (very dark tone)
|
|
106
|
+
//
|
|
107
|
+
// Chroma is preserved from the base colour throughout — hue is never shifted.
|
|
108
|
+
// At extreme lightness values chroma is gently reduced to stay in sRGB gamut.
|
|
109
|
+
|
|
110
|
+
const LIGHT_L = 0.97; // shade 10 lightness
|
|
111
|
+
const DARK_L = 0.15; // shade 100 lightness
|
|
112
|
+
|
|
113
|
+
const steps = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
|
|
114
|
+
|
|
115
|
+
steps.forEach(step => {
|
|
116
|
+
if (step === 80) {
|
|
117
|
+
scale[step] = baseHex.toUpperCase();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let newL;
|
|
122
|
+
if (step < 80) {
|
|
123
|
+
// 10–70: interpolate from LIGHT_L down to baseL
|
|
124
|
+
const t = (step / 80); // 0 → 1 as step goes 0 → 80
|
|
125
|
+
newL = LIGHT_L - t * (LIGHT_L - baseL);
|
|
126
|
+
} else {
|
|
127
|
+
// 90–100: interpolate from baseL down to DARK_L
|
|
128
|
+
const t = (step - 80) / 20; // 0 → 1 as step goes 80 → 100
|
|
129
|
+
newL = baseL - t * (baseL - DARK_L);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Reduce chroma slightly at extremes to avoid out-of-gamut clipping
|
|
133
|
+
const chromaScale = 1 - Math.max(0, (newL - 0.90) / 0.07) * 0.5 // reduce near white
|
|
134
|
+
- Math.max(0, (0.25 - newL) / 0.10) * 0.5; // reduce near black
|
|
135
|
+
const newC = baseC * Math.max(0, chromaScale);
|
|
136
|
+
|
|
137
|
+
scale[step] = oklchToHex(newL, newC, baseH);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return scale;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function generateAllColours(colourConfig) {
|
|
144
|
+
const allColours = {};
|
|
145
|
+
|
|
146
|
+
Object.entries(colourConfig).forEach(([name, baseHex]) => {
|
|
147
|
+
allColours[name] = generateColourScale(baseHex);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return allColours;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// SPACING SCALE
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
function generateSpacing(baseUnit, scale) {
|
|
158
|
+
// Spacing values are defined explicitly in emily.config.json under spacing.scale.
|
|
159
|
+
// The baseUnit key in config is informational only — it documents the design intent
|
|
160
|
+
// (e.g. "this system is based on 8px") but does not drive generation.
|
|
161
|
+
return scale;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// FONT PRESETS
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
// Font presets define the CSS font-family stack only.
|
|
169
|
+
// Loading the actual font files is the user's responsibility — link them in your HTML
|
|
170
|
+
// or use @fontsource packages in your build. emily-css does not generate @import rules
|
|
171
|
+
// for external CDNs so it stays self-contained and works offline.
|
|
172
|
+
// See docs: https://emilyui.com/docs/getting-started
|
|
173
|
+
const FONT_PRESETS = {
|
|
174
|
+
'system': {
|
|
175
|
+
stack: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
176
|
+
},
|
|
177
|
+
'inter': {
|
|
178
|
+
name: 'Inter',
|
|
179
|
+
stack: '"Inter", system-ui, sans-serif',
|
|
180
|
+
},
|
|
181
|
+
'lexend': {
|
|
182
|
+
name: 'Lexend',
|
|
183
|
+
stack: '"Lexend", system-ui, sans-serif',
|
|
184
|
+
},
|
|
185
|
+
'georgia': {
|
|
186
|
+
stack: 'Georgia, "Times New Roman", serif',
|
|
187
|
+
},
|
|
188
|
+
'dm-sans': {
|
|
189
|
+
name: 'DM Sans',
|
|
190
|
+
stack: '"DM Sans", system-ui, sans-serif',
|
|
191
|
+
},
|
|
192
|
+
'nunito': {
|
|
193
|
+
name: 'Nunito',
|
|
194
|
+
stack: '"Nunito", system-ui, sans-serif',
|
|
195
|
+
},
|
|
196
|
+
'atkinson': {
|
|
197
|
+
name: 'Atkinson Hyperlegible',
|
|
198
|
+
stack: '"Atkinson Hyperlegible", system-ui, sans-serif',
|
|
199
|
+
},
|
|
200
|
+
'mono': {
|
|
201
|
+
stack: '"Menlo", "Monaco", "Courier New", monospace',
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
function generateFontCSS(config) {
|
|
206
|
+
// Support both legacy string format and new { heading, body } object format
|
|
207
|
+
const fontConfig = config.fontFamily || 'system';
|
|
208
|
+
let headingKey, bodyKey;
|
|
209
|
+
|
|
210
|
+
if (typeof fontConfig === 'object') {
|
|
211
|
+
headingKey = (fontConfig.heading || 'system').toLowerCase();
|
|
212
|
+
bodyKey = (fontConfig.body || 'system').toLowerCase();
|
|
213
|
+
} else {
|
|
214
|
+
headingKey = fontConfig.toLowerCase();
|
|
215
|
+
bodyKey = fontConfig.toLowerCase();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const headingPreset = FONT_PRESETS[headingKey] || FONT_PRESETS['system'];
|
|
219
|
+
const bodyPreset = FONT_PRESETS[bodyKey] || FONT_PRESETS['system'];
|
|
220
|
+
|
|
221
|
+
let fontFace = '';
|
|
222
|
+
let bodyFont = '';
|
|
223
|
+
|
|
224
|
+
bodyFont += ` body {\n font-family: ${bodyPreset.stack};\n font-synthesis: style;\n }\n`;
|
|
225
|
+
bodyFont += ` h1, h2, h3, h4, h5, h6 {\n font-family: ${headingPreset.stack};\n }\n`;
|
|
226
|
+
|
|
227
|
+
return { fontFace, bodyFont };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// CSS VARIABLE GENERATION
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
function generateCSSVariables(colours, spacing, config) {
|
|
235
|
+
let css = `:root {\n`;
|
|
236
|
+
|
|
237
|
+
// Colour variables (full shade scale)
|
|
238
|
+
Object.entries(colours).forEach(([colourName, shades]) => {
|
|
239
|
+
Object.entries(shades).forEach(([shade, hex]) => {
|
|
240
|
+
css += ` --color-${colourName}-${shade}: ${hex};\n`;
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Semantic colour variables (single value, no shade scale)
|
|
245
|
+
if (config.semanticColours) {
|
|
246
|
+
Object.entries(config.semanticColours).forEach(([name, hex]) => {
|
|
247
|
+
css += ` --color-${name}: ${hex};\n`;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
css += ` --focus-ring-glow: color-mix(in srgb, var(--color-brand-80) 12%, transparent);\n`;
|
|
251
|
+
|
|
252
|
+
// Spacing variables
|
|
253
|
+
Object.entries(spacing).forEach(([key, value]) => {
|
|
254
|
+
css += ` --space-${key}: ${value};\n`;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Font size variables with line-height
|
|
258
|
+
config.typography.fontSizes.forEach(fontSize => {
|
|
259
|
+
const sizeVal = parseInt(fontSize.value);
|
|
260
|
+
css += ` --text-${fontSize.name}: ${fontSize.value};\n`;
|
|
261
|
+
css += ` --leading-${fontSize.name}: ${fontSize.lineHeight};\n`;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Font weight variables
|
|
265
|
+
Object.entries(config.typography.fontWeights).forEach(([name, weight]) => {
|
|
266
|
+
css += ` --font-weight-${name}: ${weight};\n`;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Breakpoints
|
|
270
|
+
Object.entries(config.breakpoints).forEach(([name, value]) => {
|
|
271
|
+
css += ` --breakpoint-${name}: ${value};\n`;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Shadows
|
|
275
|
+
Object.entries(config.shadows).forEach(([name, shadow]) => {
|
|
276
|
+
css += ` --shadow-${name}: ${shadow};\n`;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Z-index
|
|
280
|
+
Object.entries(config.zIndex).forEach(([name, value]) => {
|
|
281
|
+
css += ` --z-${name}: ${value};\n`;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Transitions
|
|
285
|
+
css += ` --transition-duration: ${config.transitions.base};\n`;
|
|
286
|
+
css += ` --transition-timing: ${config.transitions.timing};\n`;
|
|
287
|
+
|
|
288
|
+
css += `}\n\n`;
|
|
289
|
+
return css;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const {
|
|
293
|
+
displayUtilities,
|
|
294
|
+
sizingUtilities,
|
|
295
|
+
positioningUtilities,
|
|
296
|
+
overflowUtilities,
|
|
297
|
+
opacityUtilities,
|
|
298
|
+
transitionUtilities,
|
|
299
|
+
transformUtilities,
|
|
300
|
+
shadowUtilities,
|
|
301
|
+
ringUtilities,
|
|
302
|
+
objectUtilities,
|
|
303
|
+
tableListUtilities,
|
|
304
|
+
svgUtilities,
|
|
305
|
+
formUtilities,
|
|
306
|
+
verticalAlignUtilities,
|
|
307
|
+
contentScrollUtilities,
|
|
308
|
+
blendUtilities,
|
|
309
|
+
cursorUtilities,
|
|
310
|
+
accessibilityUtilities,
|
|
311
|
+
containerUtilities,
|
|
312
|
+
codeUtilities,
|
|
313
|
+
animationUtilities,
|
|
314
|
+
backdropUtilities,
|
|
315
|
+
spaceUtilities,
|
|
316
|
+
divideUtilities,
|
|
317
|
+
backgroundUtilities,
|
|
318
|
+
filterUtilities,
|
|
319
|
+
} = require('./generators');
|
|
320
|
+
|
|
321
|
+
// ============================================================================
|
|
322
|
+
// SPACING UTILITIES
|
|
323
|
+
// ============================================================================
|
|
324
|
+
|
|
325
|
+
function escapeClassName(key) {
|
|
326
|
+
// Escape dots in class names for CSS (e.g., "0.5" becomes "0\.5")
|
|
327
|
+
return key.replace(/\./g, '\\.');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function generateSpacingUtilities(spacing) {
|
|
331
|
+
let css = `/* Spacing: Padding & Margin */\n`;
|
|
332
|
+
|
|
333
|
+
// Padding
|
|
334
|
+
Object.entries(spacing).forEach(([key, value]) => {
|
|
335
|
+
const escaped = escapeClassName(key);
|
|
336
|
+
css += `.p-${escaped} { padding: ${value}; }\n`;
|
|
337
|
+
css += `.px-${escaped} { padding-left: ${value}; padding-right: ${value}; }\n`;
|
|
338
|
+
css += `.py-${escaped} { padding-top: ${value}; padding-bottom: ${value}; }\n`;
|
|
339
|
+
css += `.pt-${escaped} { padding-top: ${value}; }\n`;
|
|
340
|
+
css += `.pr-${escaped} { padding-right: ${value}; }\n`;
|
|
341
|
+
css += `.pb-${escaped} { padding-bottom: ${value}; }\n`;
|
|
342
|
+
css += `.pl-${escaped} { padding-left: ${value}; }\n`;
|
|
343
|
+
css += `.ps-${escaped} { padding-inline-start: ${value}; }\n`;
|
|
344
|
+
css += `.pe-${escaped} { padding-inline-end: ${value}; }\n`;
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Margin
|
|
348
|
+
Object.entries(spacing).forEach(([key, value]) => {
|
|
349
|
+
const escaped = escapeClassName(key);
|
|
350
|
+
css += `.m-${escaped} { margin: ${value}; }\n`;
|
|
351
|
+
css += `.mx-${escaped} { margin-left: ${value}; margin-right: ${value}; }\n`;
|
|
352
|
+
css += `.my-${escaped} { margin-top: ${value}; margin-bottom: ${value}; }\n`;
|
|
353
|
+
css += `.mt-${escaped} { margin-top: ${value}; }\n`;
|
|
354
|
+
css += `.mr-${escaped} { margin-right: ${value}; }\n`;
|
|
355
|
+
css += `.mb-${escaped} { margin-bottom: ${value}; }\n`;
|
|
356
|
+
css += `.ml-${escaped} { margin-left: ${value}; }\n`;
|
|
357
|
+
css += `.ms-${escaped} { margin-inline-start: ${value}; }\n`;
|
|
358
|
+
css += `.me-${escaped} { margin-inline-end: ${value}; }\n`;
|
|
359
|
+
if (value !== '0' && value !== '0px') {
|
|
360
|
+
css += `.-m-${escaped} { margin: -${value}; }\n`;
|
|
361
|
+
css += `.-mx-${escaped} { margin-left: -${value}; margin-right: -${value}; }\n`;
|
|
362
|
+
css += `.-my-${escaped} { margin-top: -${value}; margin-bottom: -${value}; }\n`;
|
|
363
|
+
css += `.-mt-${escaped} { margin-top: -${value}; }\n`;
|
|
364
|
+
css += `.-mr-${escaped} { margin-right: -${value}; }\n`;
|
|
365
|
+
css += `.-mb-${escaped} { margin-bottom: -${value}; }\n`;
|
|
366
|
+
css += `.-ml-${escaped} { margin-left: -${value}; }\n`;
|
|
367
|
+
css += `.-ms-${escaped} { margin-inline-start: -${value}; }\n`;
|
|
368
|
+
css += `.-me-${escaped} { margin-inline-end: -${value}; }\n`;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Margin auto
|
|
373
|
+
css += `.mx-auto { margin-left: auto; margin-right: auto; }\n`;
|
|
374
|
+
css += `.my-auto { margin-top: auto; margin-bottom: auto; }\n`;
|
|
375
|
+
|
|
376
|
+
css += `\n`;
|
|
377
|
+
return css;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// FLEXBOX UTILITIES
|
|
382
|
+
// ============================================================================
|
|
383
|
+
|
|
384
|
+
function generateFlexboxUtilities(spacing) {
|
|
385
|
+
let css = `/* Flexbox */\n`;
|
|
386
|
+
|
|
387
|
+
css += `.inline-flex { display: inline-flex; }\n`;
|
|
388
|
+
|
|
389
|
+
// Direction
|
|
390
|
+
css += `.flex-row { flex-direction: row; }\n`;
|
|
391
|
+
css += `.flex-col { flex-direction: column; }\n`;
|
|
392
|
+
css += `.flex-row-reverse { flex-direction: row-reverse; }\n`;
|
|
393
|
+
css += `.flex-col-reverse { flex-direction: column-reverse; }\n`;
|
|
394
|
+
|
|
395
|
+
// Wrap
|
|
396
|
+
css += `.flex-wrap { flex-wrap: wrap; }\n`;
|
|
397
|
+
css += `.flex-nowrap { flex-wrap: nowrap; }\n`;
|
|
398
|
+
css += `.flex-wrap-reverse { flex-wrap: wrap-reverse; }\n`;
|
|
399
|
+
|
|
400
|
+
// Flex shorthand
|
|
401
|
+
css += `.flex-1 { flex: 1 1 0%; }\n`;
|
|
402
|
+
css += `.flex-auto { flex: 1 1 auto; }\n`;
|
|
403
|
+
css += `.flex-initial { flex: 0 1 auto; }\n`;
|
|
404
|
+
css += `.flex-none { flex: none; }\n`;
|
|
405
|
+
|
|
406
|
+
// Grow/shrink
|
|
407
|
+
css += `.grow { flex-grow: 1; }\n`;
|
|
408
|
+
css += `.grow-0 { flex-grow: 0; }\n`;
|
|
409
|
+
css += `.shrink { flex-shrink: 1; }\n`;
|
|
410
|
+
css += `.shrink-0 { flex-shrink: 0; }\n`;
|
|
411
|
+
|
|
412
|
+
// Flex basis
|
|
413
|
+
css += `.basis-auto { flex-basis: auto; }\n`;
|
|
414
|
+
css += `.basis-full { flex-basis: 100%; }\n`;
|
|
415
|
+
Object.entries(spacing).forEach(([key, value]) => {
|
|
416
|
+
const escaped = escapeClassName(key);
|
|
417
|
+
css += `.basis-${escaped} { flex-basis: ${value}; }\n`;
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const fractions = {
|
|
421
|
+
'1\\/2': '50%', '1\\/3': '33.333333%', '2\\/3': '66.666667%',
|
|
422
|
+
'1\\/4': '25%', '2\\/4': '50%', '3\\/4': '75%',
|
|
423
|
+
'1\\/5': '20%', '2\\/5': '40%', '3\\/5': '60%', '4\\/5': '80%',
|
|
424
|
+
'1\\/6': '16.666667%', '2\\/6': '33.333333%', '3\\/6': '50%', '4\\/6': '66.666667%', '5\\/6': '83.333333%',
|
|
425
|
+
'1\\/12': '8.333333%', '2\\/12': '16.666667%', '3\\/12': '25%', '4\\/12': '33.333333%', '5\\/12': '41.666667%', '6\\/12': '50%', '7\\/12': '58.333333%', '8\\/12': '66.666667%', '9\\/12': '75%', '10\\/12': '83.333333%', '11\\/12': '91.666667%'
|
|
426
|
+
};
|
|
427
|
+
Object.entries(fractions).forEach(([name, value]) => {
|
|
428
|
+
css += `.basis-${name} { flex-basis: ${value}; }\n`;
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Order
|
|
432
|
+
css += `.order-first { order: -9999; }\n`;
|
|
433
|
+
css += `.order-last { order: 9999; }\n`;
|
|
434
|
+
css += `.order-none { order: 0; }\n`;
|
|
435
|
+
for (let i = 1; i <= 12; i++) {
|
|
436
|
+
css += `.order-${i} { order: ${i}; }\n`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Justify (main axis)
|
|
440
|
+
css += `.justify-normal { justify-content: normal; }\n`;
|
|
441
|
+
css += `.justify-start { justify-content: flex-start; }\n`;
|
|
442
|
+
css += `.justify-end { justify-content: flex-end; }\n`;
|
|
443
|
+
css += `.justify-center { justify-content: center; }\n`;
|
|
444
|
+
css += `.justify-between { justify-content: space-between; }\n`;
|
|
445
|
+
css += `.justify-around { justify-content: space-around; }\n`;
|
|
446
|
+
css += `.justify-evenly { justify-content: space-evenly; }\n`;
|
|
447
|
+
css += `.justify-stretch { justify-content: stretch; }\n`;
|
|
448
|
+
|
|
449
|
+
// Content alignment
|
|
450
|
+
css += `.content-normal { align-content: normal; }\n`;
|
|
451
|
+
css += `.content-center { align-content: center; }\n`;
|
|
452
|
+
css += `.content-start { align-content: flex-start; }\n`;
|
|
453
|
+
css += `.content-end { align-content: flex-end; }\n`;
|
|
454
|
+
css += `.content-between { align-content: space-between; }\n`;
|
|
455
|
+
css += `.content-around { align-content: space-around; }\n`;
|
|
456
|
+
css += `.content-evenly { align-content: space-evenly; }\n`;
|
|
457
|
+
css += `.content-baseline { align-content: baseline; }\n`;
|
|
458
|
+
css += `.content-stretch { align-content: stretch; }\n`;
|
|
459
|
+
|
|
460
|
+
// Items (cross axis)
|
|
461
|
+
css += `.items-start { align-items: flex-start; }\n`;
|
|
462
|
+
css += `.items-end { align-items: flex-end; }\n`;
|
|
463
|
+
css += `.items-center { align-items: center; }\n`;
|
|
464
|
+
css += `.items-baseline { align-items: baseline; }\n`;
|
|
465
|
+
css += `.items-stretch { align-items: stretch; }\n`;
|
|
466
|
+
|
|
467
|
+
// Self alignment
|
|
468
|
+
css += `.self-auto { align-self: auto; }\n`;
|
|
469
|
+
css += `.self-start { align-self: flex-start; }\n`;
|
|
470
|
+
css += `.self-end { align-self: flex-end; }\n`;
|
|
471
|
+
css += `.self-center { align-self: center; }\n`;
|
|
472
|
+
css += `.self-stretch { align-self: stretch; }\n`;
|
|
473
|
+
css += `.self-baseline { align-self: baseline; }\n`;
|
|
474
|
+
|
|
475
|
+
// Place utilities
|
|
476
|
+
css += `.place-content-center { place-content: center; }\n`;
|
|
477
|
+
css += `.place-content-start { place-content: start; }\n`;
|
|
478
|
+
css += `.place-content-end { place-content: end; }\n`;
|
|
479
|
+
css += `.place-content-between { place-content: space-between; }\n`;
|
|
480
|
+
css += `.place-content-around { place-content: space-around; }\n`;
|
|
481
|
+
css += `.place-content-evenly { place-content: space-evenly; }\n`;
|
|
482
|
+
css += `.place-content-baseline { place-content: baseline; }\n`;
|
|
483
|
+
css += `.place-content-stretch { place-content: stretch; }\n`;
|
|
484
|
+
css += `.place-items-start { place-items: start; }\n`;
|
|
485
|
+
css += `.place-items-end { place-items: end; }\n`;
|
|
486
|
+
css += `.place-items-center { place-items: center; }\n`;
|
|
487
|
+
css += `.place-items-baseline { place-items: baseline; }\n`;
|
|
488
|
+
css += `.place-items-stretch { place-items: stretch; }\n`;
|
|
489
|
+
css += `.place-self-auto { place-self: auto; }\n`;
|
|
490
|
+
css += `.place-self-start { place-self: start; }\n`;
|
|
491
|
+
css += `.place-self-end { place-self: end; }\n`;
|
|
492
|
+
css += `.place-self-center { place-self: center; }\n`;
|
|
493
|
+
css += `.place-self-stretch { place-self: stretch; }\n`;
|
|
494
|
+
|
|
495
|
+
css += `\n`;
|
|
496
|
+
return css;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ============================================================================
|
|
500
|
+
// GRID UTILITIES
|
|
501
|
+
// ============================================================================
|
|
502
|
+
|
|
503
|
+
function generateGridUtilities(spacing) {
|
|
504
|
+
let css = `/* Grid */\n`;
|
|
505
|
+
|
|
506
|
+
css += `.inline-grid { display: inline-grid; }\n`;
|
|
507
|
+
|
|
508
|
+
css += `.grid-cols-none { grid-template-columns: none; }\n`;
|
|
509
|
+
css += `.grid-cols-subgrid { grid-template-columns: subgrid; }\n`;
|
|
510
|
+
for (let i = 1; i <= 12; i++) {
|
|
511
|
+
css += `.grid-cols-${i} { grid-template-columns: repeat(${i}, minmax(0, 1fr)); }\n`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
css += `.grid-rows-none { grid-template-rows: none; }\n`;
|
|
515
|
+
css += `.grid-rows-subgrid { grid-template-rows: subgrid; }\n`;
|
|
516
|
+
for (let i = 1; i <= 12; i++) {
|
|
517
|
+
css += `.grid-rows-${i} { grid-template-rows: repeat(${i}, minmax(0, 1fr)); }\n`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
for (let i = 1; i <= 12; i++) {
|
|
521
|
+
css += `.col-span-${i} { grid-column: span ${i} / span ${i}; }\n`;
|
|
522
|
+
}
|
|
523
|
+
css += `.col-span-full { grid-column: 1 / -1; }\n`;
|
|
524
|
+
css += `.col-auto { grid-column: auto; }\n`;
|
|
525
|
+
for (let i = 1; i <= 13; i++) {
|
|
526
|
+
css += `.col-start-${i} { grid-column-start: ${i}; }\n`;
|
|
527
|
+
css += `.col-end-${i} { grid-column-end: ${i}; }\n`;
|
|
528
|
+
}
|
|
529
|
+
css += `.col-start-auto { grid-column-start: auto; }\n`;
|
|
530
|
+
css += `.col-end-auto { grid-column-end: auto; }\n`;
|
|
531
|
+
|
|
532
|
+
for (let i = 1; i <= 12; i++) {
|
|
533
|
+
css += `.row-span-${i} { grid-row: span ${i} / span ${i}; }\n`;
|
|
534
|
+
}
|
|
535
|
+
css += `.row-span-full { grid-row: 1 / -1; }\n`;
|
|
536
|
+
css += `.row-auto { grid-row: auto; }\n`;
|
|
537
|
+
for (let i = 1; i <= 13; i++) {
|
|
538
|
+
css += `.row-start-${i} { grid-row-start: ${i}; }\n`;
|
|
539
|
+
css += `.row-end-${i} { grid-row-end: ${i}; }\n`;
|
|
540
|
+
}
|
|
541
|
+
css += `.row-start-auto { grid-row-start: auto; }\n`;
|
|
542
|
+
css += `.row-end-auto { grid-row-end: auto; }\n`;
|
|
543
|
+
|
|
544
|
+
css += `.grid-flow-row { grid-auto-flow: row; }\n`;
|
|
545
|
+
css += `.grid-flow-col { grid-auto-flow: column; }\n`;
|
|
546
|
+
css += `.grid-flow-dense { grid-auto-flow: dense; }\n`;
|
|
547
|
+
css += `.grid-flow-row-dense { grid-auto-flow: row dense; }\n`;
|
|
548
|
+
css += `.grid-flow-col-dense { grid-auto-flow: column dense; }\n`;
|
|
549
|
+
|
|
550
|
+
css += `.auto-cols-auto { grid-auto-columns: auto; }\n`;
|
|
551
|
+
css += `.auto-cols-min { grid-auto-columns: min-content; }\n`;
|
|
552
|
+
css += `.auto-cols-max { grid-auto-columns: max-content; }\n`;
|
|
553
|
+
css += `.auto-cols-fr { grid-auto-columns: minmax(0, 1fr); }\n`;
|
|
554
|
+
css += `.auto-rows-auto { grid-auto-rows: auto; }\n`;
|
|
555
|
+
css += `.auto-rows-min { grid-auto-rows: min-content; }\n`;
|
|
556
|
+
css += `.auto-rows-max { grid-auto-rows: max-content; }\n`;
|
|
557
|
+
css += `.auto-rows-fr { grid-auto-rows: minmax(0, 1fr); }\n`;
|
|
558
|
+
|
|
559
|
+
Object.entries(spacing).forEach(([key, value]) => {
|
|
560
|
+
const escaped = escapeClassName(key);
|
|
561
|
+
css += `.gap-${escaped} { gap: ${value}; }\n`;
|
|
562
|
+
css += `.gap-x-${escaped} { column-gap: ${value}; }\n`;
|
|
563
|
+
css += `.gap-y-${escaped} { row-gap: ${value}; }\n`;
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
css += `.justify-items-start { justify-items: start; }\n`;
|
|
567
|
+
css += `.justify-items-end { justify-items: end; }\n`;
|
|
568
|
+
css += `.justify-items-center { justify-items: center; }\n`;
|
|
569
|
+
css += `.justify-items-stretch { justify-items: stretch; }\n`;
|
|
570
|
+
css += `.justify-self-auto { justify-self: auto; }\n`;
|
|
571
|
+
css += `.justify-self-start { justify-self: start; }\n`;
|
|
572
|
+
css += `.justify-self-end { justify-self: end; }\n`;
|
|
573
|
+
css += `.justify-self-center { justify-self: center; }\n`;
|
|
574
|
+
css += `.justify-self-stretch { justify-self: stretch; }\n`;
|
|
575
|
+
|
|
576
|
+
css += `\n`;
|
|
577
|
+
return css;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ============================================================================
|
|
581
|
+
// TYPOGRAPHY UTILITIES
|
|
582
|
+
// ============================================================================
|
|
583
|
+
|
|
584
|
+
function generateTypographyUtilities(config) {
|
|
585
|
+
let css = `/* Typography */\n`;
|
|
586
|
+
|
|
587
|
+
config.typography.fontSizes.forEach(fontSize => {
|
|
588
|
+
css += `.text-${fontSize.name} { font-size: var(--text-${fontSize.name}); line-height: ${fontSize.lineHeight}; }\n`;
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const fontWeightDefaults = {
|
|
592
|
+
thin: 100,
|
|
593
|
+
extralight: 200,
|
|
594
|
+
light: 300,
|
|
595
|
+
normal: 400,
|
|
596
|
+
medium: 500,
|
|
597
|
+
semibold: 600,
|
|
598
|
+
bold: 700,
|
|
599
|
+
extrabold: 800,
|
|
600
|
+
black: 900,
|
|
601
|
+
};
|
|
602
|
+
const resolvedFontWeights = {
|
|
603
|
+
...fontWeightDefaults,
|
|
604
|
+
...(config.typography.fontWeights || {}),
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
Object.entries(resolvedFontWeights).forEach(([name, weight]) => {
|
|
608
|
+
css += `.font-${name} { font-weight: ${weight}; }\n`;
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
css += `.italic { font-style: italic; }\n`;
|
|
612
|
+
css += `.not-italic { font-style: normal; }\n`;
|
|
613
|
+
|
|
614
|
+
css += `.text-left { text-align: left; }\n`;
|
|
615
|
+
css += `.text-center { text-align: center; }\n`;
|
|
616
|
+
css += `.text-right { text-align: right; }\n`;
|
|
617
|
+
css += `.text-justify { text-align: justify; }\n`;
|
|
618
|
+
css += `.text-start { text-align: start; }\n`;
|
|
619
|
+
css += `.text-end { text-align: end; }\n`;
|
|
620
|
+
|
|
621
|
+
css += `.whitespace-normal { white-space: normal; }\n`;
|
|
622
|
+
css += `.whitespace-nowrap { white-space: nowrap; }\n`;
|
|
623
|
+
css += `.whitespace-pre { white-space: pre; }\n`;
|
|
624
|
+
css += `.whitespace-pre-line { white-space: pre-line; }\n`;
|
|
625
|
+
css += `.whitespace-pre-wrap { white-space: pre-wrap; }\n`;
|
|
626
|
+
css += `.whitespace-break-spaces { white-space: break-spaces; }\n`;
|
|
627
|
+
css += `.text-wrap { text-wrap: wrap; }\n`;
|
|
628
|
+
css += `.text-nowrap { text-wrap: nowrap; }\n`;
|
|
629
|
+
css += `.text-balance { text-wrap: balance; }\n`;
|
|
630
|
+
css += `.text-pretty { text-wrap: pretty; }\n`;
|
|
631
|
+
css += `.break-normal { overflow-wrap: normal; word-break: normal; }\n`;
|
|
632
|
+
css += `.break-words { overflow-wrap: break-word; }\n`;
|
|
633
|
+
css += `.break-all { word-break: break-all; }\n`;
|
|
634
|
+
css += `.break-keep { word-break: keep-all; }\n`;
|
|
635
|
+
css += `.hyphens-none { hyphens: none; }\n`;
|
|
636
|
+
css += `.hyphens-manual { hyphens: manual; }\n`;
|
|
637
|
+
css += `.hyphens-auto { hyphens: auto; }\n`;
|
|
638
|
+
|
|
639
|
+
css += `.leading-none { line-height: 1; }\n`;
|
|
640
|
+
css += `.leading-tight { line-height: 1.25; }\n`;
|
|
641
|
+
css += `.leading-snug { line-height: 1.375; }\n`;
|
|
642
|
+
css += `.leading-normal { line-height: 1.5; }\n`;
|
|
643
|
+
css += `.leading-relaxed { line-height: 1.625; }\n`;
|
|
644
|
+
css += `.leading-loose { line-height: 2; }\n`;
|
|
645
|
+
css += `.text-display { font-size: clamp(2.5rem, 6vw, 4rem); }\n`;
|
|
646
|
+
|
|
647
|
+
css += `.tracking-tighter { letter-spacing: -0.05em; }\n`;
|
|
648
|
+
css += `.tracking-tight { letter-spacing: -0.025em; }\n`;
|
|
649
|
+
css += `.tracking-normal { letter-spacing: 0em; }\n`;
|
|
650
|
+
css += `.tracking-wide { letter-spacing: 0.025em; }\n`;
|
|
651
|
+
css += `.tracking-wider { letter-spacing: 0.05em; }\n`;
|
|
652
|
+
css += `.tracking-widest { letter-spacing: 0.1em; }\n`;
|
|
653
|
+
|
|
654
|
+
css += `.underline { text-decoration-line: underline; }\n`;
|
|
655
|
+
css += `.overline { text-decoration-line: overline; }\n`;
|
|
656
|
+
css += `.line-through { text-decoration-line: line-through; }\n`;
|
|
657
|
+
css += `.no-underline { text-decoration-line: none; }\n`;
|
|
658
|
+
css += `.decoration-solid { text-decoration-style: solid; }\n`;
|
|
659
|
+
css += `.decoration-double { text-decoration-style: double; }\n`;
|
|
660
|
+
css += `.decoration-dotted { text-decoration-style: dotted; }\n`;
|
|
661
|
+
css += `.decoration-dashed { text-decoration-style: dashed; }\n`;
|
|
662
|
+
css += `.decoration-wavy { text-decoration-style: wavy; }\n`;
|
|
663
|
+
css += `.underline-offset-auto { text-underline-offset: auto; }\n`;
|
|
664
|
+
[0, 1, 2, 4, 8].forEach(value => {
|
|
665
|
+
css += `.underline-offset-${value} { text-underline-offset: ${value}px; }\n`;
|
|
666
|
+
});
|
|
667
|
+
css += `.decoration-auto { text-decoration-thickness: auto; }\n`;
|
|
668
|
+
css += `.decoration-from-font { text-decoration-thickness: from-font; }\n`;
|
|
669
|
+
[0, 1, 2, 4, 8].forEach(value => {
|
|
670
|
+
css += `.decoration-${value} { text-decoration-thickness: ${value}px; }\n`;
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
css += `.normal-nums { font-variant-numeric: normal; }\n`;
|
|
674
|
+
css += `.ordinal { font-variant-numeric: ordinal; }\n`;
|
|
675
|
+
css += `.slashed-zero { font-variant-numeric: slashed-zero; }\n`;
|
|
676
|
+
css += `.lining-nums { font-variant-numeric: lining-nums; }\n`;
|
|
677
|
+
css += `.oldstyle-nums { font-variant-numeric: oldstyle-nums; }\n`;
|
|
678
|
+
css += `.proportional-nums { font-variant-numeric: proportional-nums; }\n`;
|
|
679
|
+
css += `.tabular-nums { font-variant-numeric: tabular-nums; }\n`;
|
|
680
|
+
css += `.diagonal-fractions { font-variant-numeric: diagonal-fractions; }\n`;
|
|
681
|
+
css += `.stacked-fractions { font-variant-numeric: stacked-fractions; }\n`;
|
|
682
|
+
|
|
683
|
+
css += `.uppercase { text-transform: uppercase; }\n`;
|
|
684
|
+
css += `.lowercase { text-transform: lowercase; }\n`;
|
|
685
|
+
css += `.capitalize { text-transform: capitalize; }\n`;
|
|
686
|
+
css += `.normal-case { text-transform: none; }\n`;
|
|
687
|
+
|
|
688
|
+
css += `.font-sans { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }\n`;
|
|
689
|
+
css += `.font-serif { font-family: Georgia, "Times New Roman", serif; }\n`;
|
|
690
|
+
css += `.font-mono { font-family: "Menlo", "Monaco", "Courier New", monospace; }\n`;
|
|
691
|
+
css += `.font-inter { font-family: "Inter", system-ui, sans-serif; }\n`;
|
|
692
|
+
css += `.font-lexend { font-family: "Lexend", system-ui, sans-serif; }\n`;
|
|
693
|
+
css += `.font-dm-sans { font-family: "DM Sans", system-ui, sans-serif; }\n`;
|
|
694
|
+
css += `.font-nunito { font-family: "Nunito", system-ui, sans-serif; }\n`;
|
|
695
|
+
css += `.font-atkinson { font-family: "Atkinson Hyperlegible", system-ui, sans-serif; }\n`;
|
|
696
|
+
|
|
697
|
+
css += `\n`;
|
|
698
|
+
return css;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ============================================================================
|
|
702
|
+
// BORDER UTILITIES
|
|
703
|
+
// ============================================================================
|
|
704
|
+
|
|
705
|
+
function generateBorderUtilities(config) {
|
|
706
|
+
let css = `/* Borders & Radius */\n`;
|
|
707
|
+
|
|
708
|
+
const borderWidths = config.spacing.borderWidths || [0, 2, 4, 8];
|
|
709
|
+
|
|
710
|
+
css += `.border { border-width: 1px; border-style: solid; }\n`;
|
|
711
|
+
borderWidths.forEach(width => {
|
|
712
|
+
css += `.border-${width} { border-width: ${width}px; }\n`;
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
const sides = {
|
|
716
|
+
t: 'top', r: 'right', b: 'bottom', l: 'left',
|
|
717
|
+
x: ['left', 'right'], y: ['top', 'bottom'],
|
|
718
|
+
s: 'inline-start', e: 'inline-end'
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
Object.entries(sides).forEach(([side, value]) => {
|
|
722
|
+
if (Array.isArray(value)) {
|
|
723
|
+
css += `.border-${side} { border-${value[0]}-width: 1px; border-${value[1]}-width: 1px; border-${value[0]}-style: solid; border-${value[1]}-style: solid; }\n`;
|
|
724
|
+
} else {
|
|
725
|
+
css += `.border-${side} { border-${value}-width: 1px; border-${value}-style: solid; }\n`;
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
borderWidths.forEach(width => {
|
|
730
|
+
Object.entries(sides).forEach(([side, value]) => {
|
|
731
|
+
if (Array.isArray(value)) {
|
|
732
|
+
css += `.border-${side}-${width} { border-${value[0]}-width: ${width}px; border-${value[1]}-width: ${width}px; border-${value[0]}-style: solid; border-${value[1]}-style: solid; }\n`;
|
|
733
|
+
} else {
|
|
734
|
+
css += `.border-${side}-${width} { border-${value}-width: ${width}px; border-${value}-style: solid; }\n`;
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
css += `.border-solid { border-style: solid; }\n`;
|
|
740
|
+
css += `.border-dashed { border-style: dashed; }\n`;
|
|
741
|
+
css += `.border-dotted { border-style: dotted; }\n`;
|
|
742
|
+
css += `.border-double { border-style: double; }\n`;
|
|
743
|
+
css += `.border-hidden { border-style: hidden; }\n`;
|
|
744
|
+
css += `.border-none { border-style: none; }\n`;
|
|
745
|
+
css += `.border-transparent { border-color: transparent; }\n`;
|
|
746
|
+
css += `.border-current { border-color: currentColor; }\n`;
|
|
747
|
+
css += `.border-black { border-color: #111110; }\n`;
|
|
748
|
+
css += `.border-white { border-color: #FAFAFA; }\n`;
|
|
749
|
+
|
|
750
|
+
const baseRadius = config.spacing.borderRadius['base'] || '8px';
|
|
751
|
+
css += `.rounded { border-radius: ${baseRadius}; }\n`;
|
|
752
|
+
|
|
753
|
+
Object.entries(config.spacing.borderRadius).forEach(([name, value]) => {
|
|
754
|
+
css += `.rounded-${name} { border-radius: ${value}; }\n`;
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
const radiusTargets = {
|
|
758
|
+
t: ['top-left', 'top-right'], r: ['top-right', 'bottom-right'],
|
|
759
|
+
b: ['bottom-right', 'bottom-left'], l: ['top-left', 'bottom-left'],
|
|
760
|
+
tl: ['top-left'], tr: ['top-right'], br: ['bottom-right'], bl: ['bottom-left']
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
Object.entries(radiusTargets).forEach(([side, corners]) => {
|
|
764
|
+
corners.forEach(corner => {
|
|
765
|
+
css += `.rounded-${side} { border-${corner}-radius: ${baseRadius}; }\n`;
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
Object.entries(config.spacing.borderRadius).forEach(([name, value]) => {
|
|
770
|
+
Object.entries(radiusTargets).forEach(([side, corners]) => {
|
|
771
|
+
corners.forEach(corner => {
|
|
772
|
+
css += `.rounded-${side}-${name} { border-${corner}-radius: ${value}; }\n`;
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
css += `\n`;
|
|
778
|
+
return css;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// ============================================================================
|
|
782
|
+
// BASE ELEMENT STYLES
|
|
783
|
+
// ============================================================================
|
|
784
|
+
|
|
785
|
+
function generateBaseStyles(config) {
|
|
786
|
+
const baseStyles = config.baseStyles;
|
|
787
|
+
if (!baseStyles || Object.keys(baseStyles).length === 0) return '';
|
|
788
|
+
|
|
789
|
+
// Build a lookup map from font size name → CSS variable
|
|
790
|
+
const fontSizeMap = {};
|
|
791
|
+
(config.typography?.fontSizes || []).forEach(({ name }) => {
|
|
792
|
+
fontSizeMap[name] = `var(--text-${name})`;
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Line height hints per size — keeps headings tighter than body text
|
|
796
|
+
const lineHeightMap = {
|
|
797
|
+
xs: '1.5', sm: '1.5', base: '1.6', lg: '1.6',
|
|
798
|
+
xl: '1.5', '2xl': '1.4', '3xl': '1.35', '4xl': '1.3',
|
|
799
|
+
'5xl': '1.15', '6xl': '1.1', '7xl': '1.05', '8xl': '1', '9xl': '1'
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
let css = '\n /* Base element styles (from baseStyles in emily.config.json) */\n';
|
|
803
|
+
Object.entries(baseStyles).forEach(([element, sizeKey]) => {
|
|
804
|
+
const varRef = fontSizeMap[sizeKey];
|
|
805
|
+
if (!varRef) return;
|
|
806
|
+
const lh = lineHeightMap[sizeKey] || '1.5';
|
|
807
|
+
css += ` ${element} { font-size: ${varRef}; line-height: ${lh}; }\n`;
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
return css;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// ============================================================================
|
|
814
|
+
// COLOUR UTILITIES
|
|
815
|
+
// ============================================================================
|
|
816
|
+
|
|
817
|
+
function generateColourUtilities(colours) {
|
|
818
|
+
let css = `/* Colours: Background, Text, Borders, Accents */\n`;
|
|
819
|
+
// Uses CSS custom properties rather than hardcoded hex so colour utilities
|
|
820
|
+
// can be overridden via variable redefinition (e.g. dark mode, theme layers).
|
|
821
|
+
// The hex values are still declared as --color-* tokens in @layer theme.
|
|
822
|
+
|
|
823
|
+
Object.entries(colours).forEach(([colourName, shades]) => {
|
|
824
|
+
// Background colours
|
|
825
|
+
Object.entries(shades).forEach(([shade]) => {
|
|
826
|
+
css += `.bg-${colourName}-${shade} { background-color: var(--color-${colourName}-${shade}); }\n`;
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Text colours
|
|
830
|
+
Object.entries(shades).forEach(([shade]) => {
|
|
831
|
+
css += `.text-${colourName}-${shade} { color: var(--color-${colourName}-${shade}); }\n`;
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// Border colours
|
|
835
|
+
Object.entries(shades).forEach(([shade]) => {
|
|
836
|
+
css += `.border-${colourName}-${shade} { border-color: var(--color-${colourName}-${shade}); }\n`;
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
// Accent colours (for form elements like checkboxes, radio buttons)
|
|
840
|
+
Object.entries(shades).forEach(([shade]) => {
|
|
841
|
+
css += `.accent-${colourName}-${shade} { accent-color: var(--color-${colourName}-${shade}); }\n`;
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
css += `.bg-white { background-color: #FAFAFA; }\n`;
|
|
846
|
+
css += `.bg-black { background-color: #111110; }\n`;
|
|
847
|
+
css += `.bg-transparent { background-color: transparent; }\n`;
|
|
848
|
+
css += `.bg-current { background-color: currentColor; }\n`;
|
|
849
|
+
|
|
850
|
+
css += `.text-white { color: #FAFAFA; }\n`;
|
|
851
|
+
css += `.text-black { color: #111110; }\n`;
|
|
852
|
+
css += `.text-transparent { color: transparent; }\n`;
|
|
853
|
+
css += `.text-current { color: currentColor; }\n`;
|
|
854
|
+
|
|
855
|
+
css += `\n`;
|
|
856
|
+
return css;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function generateSemanticColourUtilities(semanticColours) {
|
|
860
|
+
if (!semanticColours) return '';
|
|
861
|
+
let css = `/* Semantic colours: single value, no shade scale */\n`;
|
|
862
|
+
Object.entries(semanticColours).forEach(([name]) => {
|
|
863
|
+
css += `.bg-${name} { background-color: var(--color-${name}); }\n`;
|
|
864
|
+
css += `.text-${name} { color: var(--color-${name}); }\n`;
|
|
865
|
+
css += `.border-${name} { border-color: var(--color-${name}); }\n`;
|
|
866
|
+
css += `.fill-${name} { fill: var(--color-${name}); }\n`;
|
|
867
|
+
});
|
|
868
|
+
css += `\n`;
|
|
869
|
+
return css;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// ============================================================================
|
|
873
|
+
// ARIA & DATA-STATE VARIANTS
|
|
874
|
+
// ============================================================================
|
|
875
|
+
// Generates ARIA attribute and data-state variants for all utility classes.
|
|
876
|
+
// Selectors target the attribute value directly so they work without JS —
|
|
877
|
+
// just toggle the attribute and the utility activates.
|
|
878
|
+
//
|
|
879
|
+
// Usage in HTML:
|
|
880
|
+
// aria-expanded: class="aria-expanded:block" aria-expanded="true"
|
|
881
|
+
// data-open: class="data-open:flex" data-state="open"
|
|
882
|
+
//
|
|
883
|
+
// Output examples:
|
|
884
|
+
// .aria-expanded\:block[aria-expanded="true"] { display: block; }
|
|
885
|
+
// .data-open\:flex[data-state="open"] { display: flex; }
|
|
886
|
+
|
|
887
|
+
function addAriaDataVariants(css) {
|
|
888
|
+
const variants = [
|
|
889
|
+
{ name: 'aria-expanded', selector: '[aria-expanded="true"]' },
|
|
890
|
+
{ name: 'aria-selected', selector: '[aria-selected="true"]' },
|
|
891
|
+
{ name: 'aria-checked', selector: '[aria-checked="true"]' },
|
|
892
|
+
{ name: 'aria-current', selector: '[aria-current="page"]' },
|
|
893
|
+
{ name: 'aria-disabled', selector: '[aria-disabled="true"]' },
|
|
894
|
+
{ name: 'data-open', selector: '[data-state="open"]' },
|
|
895
|
+
{ name: 'data-closed', selector: '[data-state="closed"]' },
|
|
896
|
+
{ name: 'data-checked', selector: '[data-state="checked"]' },
|
|
897
|
+
{ name: 'data-unchecked', selector: '[data-state="unchecked"]' },
|
|
898
|
+
{ name: 'data-active', selector: '[data-state="active"]' },
|
|
899
|
+
{ name: 'data-inactive', selector: '[data-state="inactive"]' },
|
|
900
|
+
];
|
|
901
|
+
|
|
902
|
+
let variantCss = css;
|
|
903
|
+
|
|
904
|
+
variants.forEach(variant => {
|
|
905
|
+
let variantRules = '';
|
|
906
|
+
const lines = css.split('\n');
|
|
907
|
+
|
|
908
|
+
lines.forEach(line => {
|
|
909
|
+
if (line.startsWith('.') && line.includes('{')) {
|
|
910
|
+
const className = line.split('{')[0].trim();
|
|
911
|
+
// Skip already-variant lines (contain ':' in class name) and special selectors
|
|
912
|
+
if (
|
|
913
|
+
!className.startsWith(':root') &&
|
|
914
|
+
!className.includes('@') &&
|
|
915
|
+
!className.includes('::') &&
|
|
916
|
+
!className.includes(':')
|
|
917
|
+
) {
|
|
918
|
+
const classWithoutDot = className.substring(1);
|
|
919
|
+
const ariaSelector = `.${variant.name}\\:${classWithoutDot}${variant.selector}`;
|
|
920
|
+
const ariaRule = line.replace(className, ariaSelector);
|
|
921
|
+
variantRules += ariaRule + '\n';
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
if (variantRules) {
|
|
927
|
+
variantCss += `\n/* ARIA/data-state variant: ${variant.name} */\n` + variantRules;
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
return variantCss;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// ============================================================================
|
|
935
|
+
// DARK MODE VARIANTS
|
|
936
|
+
// ============================================================================
|
|
937
|
+
// Generates dark: prefixed versions of colour and appearance utilities only.
|
|
938
|
+
// Layout, spacing, and typography utilities don't change in dark mode —
|
|
939
|
+
// targeting only the utilities where dark mode actually makes a difference
|
|
940
|
+
// keeps the output lean and the purge step effective.
|
|
941
|
+
//
|
|
942
|
+
// Usage in HTML: class="bg-neutral-10 dark:bg-neutral-90 text-neutral-90 dark:text-neutral-10"
|
|
943
|
+
// Output: @media (prefers-color-scheme: dark) { .dark\:bg-neutral-90 { background-color: ...; } }
|
|
944
|
+
|
|
945
|
+
function addDarkModeVariants(css) {
|
|
946
|
+
// Match on CSS property, not class name prefix — avoids catching
|
|
947
|
+
// structural utilities like text-xs (font-size) or text-left (text-align)
|
|
948
|
+
// when we only want colour-related declarations.
|
|
949
|
+
const colourProperties = [
|
|
950
|
+
'background-color',
|
|
951
|
+
'color',
|
|
952
|
+
'border-color',
|
|
953
|
+
'accent-color',
|
|
954
|
+
'box-shadow',
|
|
955
|
+
'opacity',
|
|
956
|
+
'fill',
|
|
957
|
+
'stroke',
|
|
958
|
+
'--tw-ring-color',
|
|
959
|
+
'outline-color',
|
|
960
|
+
];
|
|
961
|
+
|
|
962
|
+
let darkRules = '';
|
|
963
|
+
const lines = css.split('\n');
|
|
964
|
+
|
|
965
|
+
lines.forEach(line => {
|
|
966
|
+
if (line.startsWith('.') && line.includes('{') && line.includes('}')) {
|
|
967
|
+
const className = line.split('{')[0].trim();
|
|
968
|
+
|
|
969
|
+
// Only base utilities — skip anything already a variant (contains ':')
|
|
970
|
+
if (className.includes(':')) return;
|
|
971
|
+
|
|
972
|
+
// Only colour/appearance properties
|
|
973
|
+
const isColourUtility = colourProperties.some(prop => line.includes(prop + ':'));
|
|
974
|
+
if (!isColourUtility) return;
|
|
975
|
+
|
|
976
|
+
const classWithoutDot = className.substring(1);
|
|
977
|
+
const darkRule = line.replace(className, `.dark\\:${classWithoutDot}`);
|
|
978
|
+
darkRules += ' ' + darkRule + '\n';
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
if (!darkRules) return css;
|
|
983
|
+
|
|
984
|
+
return css
|
|
985
|
+
+ `\n/* Dark mode variants — explicit override */\n[data-theme="dark"] {\n${darkRules}}\n`
|
|
986
|
+
+ `\n/* Dark mode variants — system preference (no override set) */\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme="light"]) {\n${darkRules} }\n}\n`;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// ============================================================================
|
|
990
|
+
// RESPONSIVE VARIANTS
|
|
991
|
+
// ============================================================================
|
|
992
|
+
|
|
993
|
+
function addResponsiveVariants(css, config) {
|
|
994
|
+
let variantCss = css;
|
|
995
|
+
|
|
996
|
+
Object.entries(config.breakpoints).forEach(([breakpointName, breakpointValue]) => {
|
|
997
|
+
const mediaQuery = `@media (min-width: ${breakpointValue}) {\n`;
|
|
998
|
+
let breakpointRules = '';
|
|
999
|
+
|
|
1000
|
+
// Extract all utility rules and add responsive prefix
|
|
1001
|
+
const lines = css.split('\n');
|
|
1002
|
+
lines.forEach(line => {
|
|
1003
|
+
if (line.startsWith('.') && line.includes('{')) {
|
|
1004
|
+
const className = line.split('{')[0].trim();
|
|
1005
|
+
const rule = line;
|
|
1006
|
+
// Skip variables and already responsive selectors
|
|
1007
|
+
if (!className.startsWith(':root') && !className.includes(':')) {
|
|
1008
|
+
const responsiveRule = rule.replace(className, `.${breakpointName}\\:${className.substring(1)}`);
|
|
1009
|
+
breakpointRules += ' ' + responsiveRule + '\n';
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
if (breakpointRules) {
|
|
1015
|
+
variantCss += mediaQuery + breakpointRules + '}\n\n';
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
return variantCss;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// ============================================================================
|
|
1023
|
+
// STATE VARIANTS
|
|
1024
|
+
// ============================================================================
|
|
1025
|
+
// Add pseudo-class variants for hover, focus-visible, active, disabled
|
|
1026
|
+
|
|
1027
|
+
function addStateVariants(css) {
|
|
1028
|
+
const states = [
|
|
1029
|
+
{ name: 'hover', selector: ':hover' },
|
|
1030
|
+
{ name: 'focus', selector: ':focus' },
|
|
1031
|
+
{ name: 'focus-within', selector: ':focus-within' },
|
|
1032
|
+
{ name: 'focus-visible', selector: ':focus-visible' },
|
|
1033
|
+
{ name: 'active', selector: ':active' },
|
|
1034
|
+
{ name: 'disabled', selector: ':disabled' }
|
|
1035
|
+
];
|
|
1036
|
+
|
|
1037
|
+
let variantCss = css;
|
|
1038
|
+
|
|
1039
|
+
states.forEach(state => {
|
|
1040
|
+
let stateRules = '';
|
|
1041
|
+
|
|
1042
|
+
// Extract all utility rules and add state prefix
|
|
1043
|
+
const lines = css.split('\n');
|
|
1044
|
+
lines.forEach(line => {
|
|
1045
|
+
if (line.startsWith('.') && line.includes('{')) {
|
|
1046
|
+
const className = line.split('{')[0].trim();
|
|
1047
|
+
// Skip variables, media queries, pseudo-elements, and state variants
|
|
1048
|
+
if (!className.startsWith(':root') && !className.includes('@') && !className.includes('::') && !className.includes(':')) {
|
|
1049
|
+
// Generate state variant: .hover\:block:hover { display: block; }
|
|
1050
|
+
// Remove leading dot from className, add state prefix with escaped colon
|
|
1051
|
+
const classWithoutDot = className.substring(1);
|
|
1052
|
+
const stateSelector = `.${state.name}\\:${classWithoutDot}${state.selector}`;
|
|
1053
|
+
const statefulRule = line.replace(className, stateSelector);
|
|
1054
|
+
stateRules += statefulRule + '\n';
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
if (stateRules) {
|
|
1060
|
+
variantCss += '\n/* State variant: ' + state.name + ' */\n' + stateRules;
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
return variantCss;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
// ============================================================================
|
|
1069
|
+
// PATTERN COMPONENTS
|
|
1070
|
+
// ============================================================================
|
|
1071
|
+
// Composite classes that combine multiple utilities into named patterns.
|
|
1072
|
+
// These live in @layer components so utilities always take precedence in the cascade.
|
|
1073
|
+
// Gap values reference spacing variables generated from emily.config.json,
|
|
1074
|
+
// with pixel fallbacks so they work even without the variables in scope.
|
|
1075
|
+
|
|
1076
|
+
function generatePatternComponents() {
|
|
1077
|
+
return `
|
|
1078
|
+
/* ---- Centering ---- */
|
|
1079
|
+
|
|
1080
|
+
/* Full-viewport overlay centering — use for modals, lightboxes, toasts */
|
|
1081
|
+
.center-screen {
|
|
1082
|
+
position: fixed;
|
|
1083
|
+
inset: 0;
|
|
1084
|
+
display: flex;
|
|
1085
|
+
align-items: center;
|
|
1086
|
+
justify-content: center;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/* Transform-based centering within a relative/absolute parent */
|
|
1090
|
+
.center-absolute {
|
|
1091
|
+
position: absolute;
|
|
1092
|
+
top: 50%;
|
|
1093
|
+
left: 50%;
|
|
1094
|
+
transform: translate(-50%, -50%);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* ---- Reading / Prose ---- */
|
|
1098
|
+
|
|
1099
|
+
/* Comfortable reading column — limits line length, centers the block */
|
|
1100
|
+
.prose {
|
|
1101
|
+
max-width: 65ch;
|
|
1102
|
+
margin-inline: auto;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.prose-emily {
|
|
1106
|
+
max-width: 65ch;
|
|
1107
|
+
margin-inline: auto;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
.prose-emily > * + * {
|
|
1111
|
+
margin-top: var(--space-4, 1rem);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
.prose-emily h2,
|
|
1115
|
+
.prose-emily h3 {
|
|
1116
|
+
font-family: inherit;
|
|
1117
|
+
color: var(--color-neutral-90);
|
|
1118
|
+
line-height: 1.25;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.prose-emily h2 {
|
|
1122
|
+
font-size: var(--text-2xl, 24px);
|
|
1123
|
+
margin-top: var(--space-10, 2.5rem);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.prose-emily h3 {
|
|
1127
|
+
font-size: var(--text-xl, 20px);
|
|
1128
|
+
margin-top: var(--space-8, 2rem);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.prose-emily p,
|
|
1132
|
+
.prose-emily li {
|
|
1133
|
+
color: var(--color-neutral-70);
|
|
1134
|
+
line-height: 1.75;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.prose-emily ul,
|
|
1138
|
+
.prose-emily ol {
|
|
1139
|
+
padding-left: var(--space-6, 1.5rem);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.prose-emily ul {
|
|
1143
|
+
list-style-type: disc;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
.prose-emily ol {
|
|
1147
|
+
list-style-type: decimal;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
.prose-emily a {
|
|
1151
|
+
color: var(--color-brand-80);
|
|
1152
|
+
text-decoration: underline;
|
|
1153
|
+
text-underline-offset: 2px;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.prose-emily code {
|
|
1157
|
+
font-size: var(--text-sm, 14px);
|
|
1158
|
+
background-color: var(--color-neutral-10);
|
|
1159
|
+
border: 1px solid var(--color-neutral-20);
|
|
1160
|
+
border-radius: var(--space-1, 0.25rem);
|
|
1161
|
+
padding: 0.125rem 0.375rem;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/* ---- Composition ---- */
|
|
1165
|
+
|
|
1166
|
+
/* Vertical stack with consistent gap — replaces manual margin chains */
|
|
1167
|
+
.stack {
|
|
1168
|
+
display: flex;
|
|
1169
|
+
flex-direction: column;
|
|
1170
|
+
gap: var(--space-4, 1rem);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/* Horizontal grouping with wrapping — for tags, button rows, icon lists */
|
|
1174
|
+
.cluster {
|
|
1175
|
+
display: flex;
|
|
1176
|
+
flex-wrap: wrap;
|
|
1177
|
+
gap: var(--space-4, 1rem);
|
|
1178
|
+
align-items: center;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/* ---- Layout ---- */
|
|
1182
|
+
|
|
1183
|
+
/* Constrained width container — 1100px max, full-width on small screens */
|
|
1184
|
+
.width-container {
|
|
1185
|
+
width: 100%;
|
|
1186
|
+
max-width: 1100px;
|
|
1187
|
+
margin-inline: auto;
|
|
1188
|
+
padding-inline: var(--space-4, 1rem);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
@media (min-width: 640px) {
|
|
1192
|
+
.width-container {
|
|
1193
|
+
padding-inline: var(--space-6, 1.5rem);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
@media (min-width: 1024px) {
|
|
1198
|
+
.width-container {
|
|
1199
|
+
padding-inline: var(--space-8, 2rem);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
@media (min-width: 1140px) {
|
|
1204
|
+
.width-container {
|
|
1205
|
+
padding-inline: 0;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/* ---- Forms ---- */
|
|
1210
|
+
|
|
1211
|
+
.field-container {
|
|
1212
|
+
display: flex;
|
|
1213
|
+
flex-direction: column;
|
|
1214
|
+
gap: var(--space-2, 0.5rem);
|
|
1215
|
+
margin-bottom: var(--space-6, 1.5rem);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.field-container label {
|
|
1219
|
+
display: block;
|
|
1220
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
1221
|
+
color: var(--color-neutral-90);
|
|
1222
|
+
font-size: var(--text-base, 16px);
|
|
1223
|
+
line-height: 1.4;
|
|
1224
|
+
margin-bottom: var(--space-1, 0.25rem);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
fieldset {
|
|
1228
|
+
border: none;
|
|
1229
|
+
padding: 0;
|
|
1230
|
+
margin: 0 0 var(--space-6, 1.5rem);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
fieldset legend {
|
|
1234
|
+
display: block;
|
|
1235
|
+
font-size: var(--text-lg, 18px);
|
|
1236
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
1237
|
+
margin-bottom: var(--space-3, 0.75rem);
|
|
1238
|
+
color: var(--color-neutral-90);
|
|
1239
|
+
padding: 0;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.form-hint {
|
|
1243
|
+
font-size: var(--text-sm, 14px);
|
|
1244
|
+
color: var(--color-neutral-60);
|
|
1245
|
+
margin-bottom: var(--space-1, 0.25rem);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
input[type="text"],
|
|
1249
|
+
input[type="email"],
|
|
1250
|
+
input[type="password"],
|
|
1251
|
+
input[type="number"],
|
|
1252
|
+
input[type="tel"],
|
|
1253
|
+
input[type="url"],
|
|
1254
|
+
input[type="search"],
|
|
1255
|
+
input[type="date"],
|
|
1256
|
+
select,
|
|
1257
|
+
textarea {
|
|
1258
|
+
width: 100%;
|
|
1259
|
+
max-width: 100%;
|
|
1260
|
+
padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
|
|
1261
|
+
border: 2px solid var(--color-neutral-30);
|
|
1262
|
+
border-radius: 8px;
|
|
1263
|
+
background-color: #ffffff;
|
|
1264
|
+
color: var(--color-neutral-90);
|
|
1265
|
+
font-family: inherit;
|
|
1266
|
+
font-size: var(--text-base, 16px);
|
|
1267
|
+
line-height: var(--leading-base, 1.6);
|
|
1268
|
+
appearance: none;
|
|
1269
|
+
transition: border-color 200ms ease, box-shadow 200ms ease;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
select {
|
|
1273
|
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
1274
|
+
background-position: right var(--space-2, 0.5rem) center;
|
|
1275
|
+
background-repeat: no-repeat;
|
|
1276
|
+
background-size: 1.5em 1.5em;
|
|
1277
|
+
padding-right: var(--space-10, 2.5rem);
|
|
1278
|
+
cursor: pointer;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
textarea {
|
|
1282
|
+
min-height: 120px;
|
|
1283
|
+
resize: vertical;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
input[type="text"]:focus,
|
|
1287
|
+
input[type="email"]:focus,
|
|
1288
|
+
input[type="password"]:focus,
|
|
1289
|
+
input[type="number"]:focus,
|
|
1290
|
+
input[type="tel"]:focus,
|
|
1291
|
+
input[type="url"]:focus,
|
|
1292
|
+
input[type="search"]:focus,
|
|
1293
|
+
input[type="date"]:focus,
|
|
1294
|
+
select:focus,
|
|
1295
|
+
textarea:focus {
|
|
1296
|
+
outline: 2px solid var(--color-neutral-80);
|
|
1297
|
+
outline-offset: 3px;
|
|
1298
|
+
border-color: var(--color-neutral-80);
|
|
1299
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
.checkbox-group,
|
|
1303
|
+
.radio-group {
|
|
1304
|
+
display: flex;
|
|
1305
|
+
align-items: center;
|
|
1306
|
+
gap: var(--space-3, 0.75rem);
|
|
1307
|
+
margin-bottom: var(--space-4, 1rem);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
.checkbox-group label,
|
|
1311
|
+
.radio-group label {
|
|
1312
|
+
font-weight: var(--font-weight-normal, 400);
|
|
1313
|
+
margin-bottom: 0;
|
|
1314
|
+
cursor: pointer;
|
|
1315
|
+
font-size: var(--text-base, 16px);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
input[type="checkbox"] {
|
|
1319
|
+
width: 1.5rem;
|
|
1320
|
+
height: 1.5rem;
|
|
1321
|
+
margin: 0;
|
|
1322
|
+
cursor: pointer;
|
|
1323
|
+
accent-color: var(--color-brand-80);
|
|
1324
|
+
flex-shrink: 0;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
input[type="checkbox"]:focus {
|
|
1328
|
+
outline: 2px solid var(--color-neutral-80);
|
|
1329
|
+
outline-offset: 3px;
|
|
1330
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
input[type="radio"] {
|
|
1334
|
+
width: 1.5rem;
|
|
1335
|
+
height: 1.5rem;
|
|
1336
|
+
margin: 0;
|
|
1337
|
+
border-radius: 50%;
|
|
1338
|
+
appearance: none;
|
|
1339
|
+
background-color: #ffffff;
|
|
1340
|
+
border: 2px solid var(--color-neutral-30);
|
|
1341
|
+
display: grid;
|
|
1342
|
+
place-content: center;
|
|
1343
|
+
cursor: pointer;
|
|
1344
|
+
flex-shrink: 0;
|
|
1345
|
+
transition: background-color 200ms ease, border-color 200ms ease;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
input[type="radio"]::before {
|
|
1349
|
+
content: "";
|
|
1350
|
+
width: 0.75rem;
|
|
1351
|
+
height: 0.75rem;
|
|
1352
|
+
border-radius: 50%;
|
|
1353
|
+
transform: scale(0);
|
|
1354
|
+
transition: 120ms transform ease-in-out;
|
|
1355
|
+
background-color: var(--color-brand-80);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
input[type="radio"]:checked {
|
|
1359
|
+
border-color: var(--color-brand-80);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
input[type="radio"]:checked::before {
|
|
1363
|
+
transform: scale(1);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
input[type="radio"]:hover {
|
|
1367
|
+
background-color: var(--color-brand-10);
|
|
1368
|
+
border-color: var(--color-brand-80);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
input[type="radio"]:focus {
|
|
1372
|
+
outline: 2px solid var(--color-neutral-80);
|
|
1373
|
+
outline-offset: 3px;
|
|
1374
|
+
border-radius: 50%;
|
|
1375
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
input[aria-invalid="true"] {
|
|
1379
|
+
border-color: var(--color-error-80) !important;
|
|
1380
|
+
border-width: 3px;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
.form-error-message {
|
|
1384
|
+
font-size: var(--text-sm, 14px);
|
|
1385
|
+
font-weight: var(--font-weight-bold, 700);
|
|
1386
|
+
color: var(--color-error-80);
|
|
1387
|
+
margin-top: var(--space-1, 0.25rem);
|
|
1388
|
+
display: block;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
.error-summary {
|
|
1392
|
+
border: 4px solid var(--color-error-80);
|
|
1393
|
+
padding: var(--space-6, 1.5rem);
|
|
1394
|
+
margin-bottom: var(--space-8, 2rem);
|
|
1395
|
+
border-radius: 8px;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
.error-summary ul {
|
|
1399
|
+
list-style: disc;
|
|
1400
|
+
padding-left: var(--space-5, 1.25rem);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
.error-summary a {
|
|
1404
|
+
color: var(--color-error-80);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/* ---- Buttons ---- */
|
|
1408
|
+
|
|
1409
|
+
.btn {
|
|
1410
|
+
display: inline-flex;
|
|
1411
|
+
align-items: center;
|
|
1412
|
+
justify-content: center;
|
|
1413
|
+
padding: var(--space-3, 0.75rem) var(--space-6, 1.5rem);
|
|
1414
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
1415
|
+
border-radius: 8px;
|
|
1416
|
+
cursor: pointer;
|
|
1417
|
+
transition: background-color 200ms ease, border-color 200ms ease, color 200ms ease;
|
|
1418
|
+
border: 2px solid transparent;
|
|
1419
|
+
text-align: center;
|
|
1420
|
+
min-height: 3rem;
|
|
1421
|
+
font-size: var(--text-base, 16px);
|
|
1422
|
+
text-decoration: none;
|
|
1423
|
+
font-family: inherit;
|
|
1424
|
+
line-height: 1;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.btn-primary {
|
|
1428
|
+
background-color: var(--color-brand-80);
|
|
1429
|
+
color: #ffffff;
|
|
1430
|
+
border-color: transparent;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
.btn-primary:hover {
|
|
1434
|
+
background-color: var(--color-brand-90);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
.btn-primary:focus-visible {
|
|
1438
|
+
outline: 2px solid var(--color-neutral-80);
|
|
1439
|
+
outline-offset: 3px;
|
|
1440
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
.btn-secondary {
|
|
1444
|
+
background-color: #ffffff;
|
|
1445
|
+
color: var(--color-accent-80);
|
|
1446
|
+
border-color: var(--color-accent-80);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
.btn-secondary:hover {
|
|
1450
|
+
background-color: var(--color-accent-10);
|
|
1451
|
+
color: var(--color-accent-90);
|
|
1452
|
+
border-color: var(--color-accent-90);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
.btn-secondary:focus-visible {
|
|
1456
|
+
outline: 2px solid var(--color-neutral-80);
|
|
1457
|
+
outline-offset: 3px;
|
|
1458
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
.btn-ghost {
|
|
1462
|
+
background-color: transparent;
|
|
1463
|
+
color: var(--color-neutral-80);
|
|
1464
|
+
border-color: transparent;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
.btn-ghost:hover {
|
|
1468
|
+
background-color: var(--color-neutral-10);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
.btn-ghost:focus-visible {
|
|
1472
|
+
outline: 2px solid var(--color-neutral-80);
|
|
1473
|
+
outline-offset: 3px;
|
|
1474
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
.btn-danger {
|
|
1478
|
+
background-color: var(--color-error-80);
|
|
1479
|
+
color: #ffffff;
|
|
1480
|
+
border-color: transparent;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
.btn-danger:hover {
|
|
1484
|
+
background-color: var(--color-error-90);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.btn-danger:focus-visible {
|
|
1488
|
+
outline: 2px solid var(--color-neutral-80);
|
|
1489
|
+
outline-offset: 3px;
|
|
1490
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
.btn-sm {
|
|
1494
|
+
padding: var(--space-2, 0.5rem) var(--space-4, 1rem);
|
|
1495
|
+
font-size: var(--text-sm, 14px);
|
|
1496
|
+
min-height: 2.25rem;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
.btn-lg {
|
|
1500
|
+
padding: var(--space-4, 1rem) var(--space-8, 2rem);
|
|
1501
|
+
font-size: var(--text-lg, 18px);
|
|
1502
|
+
min-height: 3.5rem;
|
|
1503
|
+
}
|
|
1504
|
+
`;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// ============================================================================
|
|
1508
|
+
// BUILD FUNCTION
|
|
1509
|
+
// ============================================================================
|
|
1510
|
+
|
|
1511
|
+
function buildFullFramework() {
|
|
1512
|
+
const config = getConfig();
|
|
1513
|
+
|
|
1514
|
+
console.log('Building EmilyCSS full framework...');
|
|
1515
|
+
|
|
1516
|
+
const colours = generateAllColours(config.colours);
|
|
1517
|
+
console.log(`✓ Generated ${Object.keys(colours).length} colour scales`);
|
|
1518
|
+
if (config.semanticColours) {
|
|
1519
|
+
console.log(`✓ Generated ${Object.keys(config.semanticColours).length} semantic colour tokens`);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
const spacing = generateSpacing(config.baseUnit, config.spacing.scale);
|
|
1523
|
+
console.log(`✓ Generated ${Object.keys(spacing).length} spacing values`);
|
|
1524
|
+
|
|
1525
|
+
const variablesCss = generateCSSVariables(colours, spacing, config);
|
|
1526
|
+
|
|
1527
|
+
let utilityCss = '';
|
|
1528
|
+
utilityCss += displayUtilities();
|
|
1529
|
+
utilityCss += generateSpacingUtilities(spacing);
|
|
1530
|
+
utilityCss += generateFlexboxUtilities(spacing);
|
|
1531
|
+
utilityCss += generateGridUtilities(spacing);
|
|
1532
|
+
utilityCss += sizingUtilities(spacing);
|
|
1533
|
+
utilityCss += generateTypographyUtilities(config);
|
|
1534
|
+
utilityCss += generateBorderUtilities(config);
|
|
1535
|
+
utilityCss += generateColourUtilities(colours);
|
|
1536
|
+
utilityCss += generateSemanticColourUtilities(config.semanticColours);
|
|
1537
|
+
utilityCss += positioningUtilities(spacing);
|
|
1538
|
+
utilityCss += overflowUtilities();
|
|
1539
|
+
utilityCss += transformUtilities(spacing);
|
|
1540
|
+
utilityCss += shadowUtilities();
|
|
1541
|
+
utilityCss += ringUtilities(colours);
|
|
1542
|
+
utilityCss += objectUtilities();
|
|
1543
|
+
utilityCss += tableListUtilities();
|
|
1544
|
+
utilityCss += svgUtilities(colours);
|
|
1545
|
+
utilityCss += formUtilities();
|
|
1546
|
+
utilityCss += verticalAlignUtilities();
|
|
1547
|
+
utilityCss += contentScrollUtilities();
|
|
1548
|
+
utilityCss += opacityUtilities();
|
|
1549
|
+
utilityCss += transitionUtilities();
|
|
1550
|
+
utilityCss += blendUtilities();
|
|
1551
|
+
utilityCss += cursorUtilities();
|
|
1552
|
+
utilityCss += accessibilityUtilities();
|
|
1553
|
+
utilityCss += containerUtilities();
|
|
1554
|
+
utilityCss += codeUtilities();
|
|
1555
|
+
utilityCss += animationUtilities();
|
|
1556
|
+
utilityCss += backdropUtilities();
|
|
1557
|
+
utilityCss += spaceUtilities(spacing);
|
|
1558
|
+
utilityCss += divideUtilities(spacing, colours);
|
|
1559
|
+
utilityCss += backgroundUtilities();
|
|
1560
|
+
utilityCss += filterUtilities();
|
|
1561
|
+
|
|
1562
|
+
utilityCss = addStateVariants(utilityCss);
|
|
1563
|
+
utilityCss = addAriaDataVariants(utilityCss);
|
|
1564
|
+
utilityCss = addDarkModeVariants(utilityCss);
|
|
1565
|
+
utilityCss = addResponsiveVariants(utilityCss, config);
|
|
1566
|
+
|
|
1567
|
+
const { fontFace, bodyFont } = generateFontCSS(config);
|
|
1568
|
+
|
|
1569
|
+
const fontLabel = typeof config.fontFamily === 'object'
|
|
1570
|
+
? 'heading: ' + (config.fontFamily.heading || 'system') + ', body: ' + (config.fontFamily.body || 'system')
|
|
1571
|
+
: (config.fontFamily || 'system');
|
|
1572
|
+
|
|
1573
|
+
console.log('✓ Font: ' + fontLabel);
|
|
1574
|
+
|
|
1575
|
+
const htmlFontSize = (config.baseFontSize && config.baseFontSize !== "16px")
|
|
1576
|
+
? `\n html { font-size: ${config.baseFontSize}; }\n`
|
|
1577
|
+
: "";
|
|
1578
|
+
|
|
1579
|
+
const baseCss = `${htmlFontSize}
|
|
1580
|
+
/* Box sizing */
|
|
1581
|
+
*, *::before, *::after {
|
|
1582
|
+
box-sizing: border-box;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
body, h1, h2, h3, h4, h5, h6, p,
|
|
1586
|
+
ul, ol, dl, dd, figure, blockquote,
|
|
1587
|
+
fieldset, textarea, pre {
|
|
1588
|
+
margin: 0;
|
|
1589
|
+
padding: 0;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
ul, ol {
|
|
1593
|
+
list-style: none;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
input, button, textarea, select {
|
|
1597
|
+
font: inherit;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
img, picture, video, canvas, svg {
|
|
1601
|
+
display: block;
|
|
1602
|
+
max-width: 100%;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
button {
|
|
1606
|
+
background: none;
|
|
1607
|
+
border: none;
|
|
1608
|
+
cursor: pointer;
|
|
1609
|
+
padding: 0;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
p, h1, h2, h3, h4, h5, h6 {
|
|
1613
|
+
overflow-wrap: break-word;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/* Base heading scale */
|
|
1617
|
+
h1 {
|
|
1618
|
+
font-size: var(--text-4xl, 36px);
|
|
1619
|
+
line-height: var(--leading-4xl, 1.3);
|
|
1620
|
+
font-weight: var(--font-weight-bold, 700);
|
|
1621
|
+
margin-bottom: var(--space-6, 1.5rem);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
h2 {
|
|
1625
|
+
font-size: var(--text-3xl, 30px);
|
|
1626
|
+
line-height: var(--leading-3xl, 1.4);
|
|
1627
|
+
font-weight: var(--font-weight-bold, 700);
|
|
1628
|
+
margin-bottom: var(--space-5, 1.25rem);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
h3 {
|
|
1632
|
+
font-size: var(--text-2xl, 24px);
|
|
1633
|
+
line-height: var(--leading-2xl, 1.4);
|
|
1634
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
1635
|
+
margin-bottom: var(--space-4, 1rem);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
h4 {
|
|
1639
|
+
font-size: var(--text-xl, 20px);
|
|
1640
|
+
line-height: var(--leading-xl, 1.6);
|
|
1641
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
1642
|
+
margin-bottom: var(--space-3, 0.75rem);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
h5 {
|
|
1646
|
+
font-size: var(--text-lg, 18px);
|
|
1647
|
+
line-height: var(--leading-lg, 1.6);
|
|
1648
|
+
font-weight: var(--font-weight-medium, 500);
|
|
1649
|
+
margin-bottom: var(--space-3, 0.75rem);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
h6 {
|
|
1653
|
+
font-size: var(--text-base, 16px);
|
|
1654
|
+
line-height: var(--leading-base, 1.6);
|
|
1655
|
+
font-weight: var(--font-weight-medium, 500);
|
|
1656
|
+
margin-bottom: var(--space-2, 0.5rem);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
p {
|
|
1660
|
+
font-size: var(--text-base, 16px);
|
|
1661
|
+
line-height: var(--leading-base, 1.6);
|
|
1662
|
+
margin-bottom: var(--space-4, 1rem);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
p:last-child {
|
|
1666
|
+
margin-bottom: 0;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
code {
|
|
1670
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
1671
|
+
font-size: 0.875em;
|
|
1672
|
+
background-color: #0d0c0b;
|
|
1673
|
+
color: #e6ffd2;
|
|
1674
|
+
padding: 0.125rem 0.4rem;
|
|
1675
|
+
border-radius: 4px;
|
|
1676
|
+
display: inline;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
code.block {
|
|
1680
|
+
display: block;
|
|
1681
|
+
padding: 0.625rem 1rem;
|
|
1682
|
+
border-radius: 6px;
|
|
1683
|
+
font-size: 0.8125rem;
|
|
1684
|
+
line-height: 1.6;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
pre {
|
|
1688
|
+
background-color: #0d0c0b;
|
|
1689
|
+
color: #e4e0db;
|
|
1690
|
+
padding: 1.25rem;
|
|
1691
|
+
border-radius: 0 0 6px;
|
|
1692
|
+
overflow-x: auto;
|
|
1693
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
1694
|
+
font-size: 0.875rem;
|
|
1695
|
+
line-height: 1.7;
|
|
1696
|
+
border: 1px solid #2a2520;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
pre code {
|
|
1700
|
+
background: none;
|
|
1701
|
+
padding: 0;
|
|
1702
|
+
border-radius: 0;
|
|
1703
|
+
color: inherit;
|
|
1704
|
+
font-size: inherit;
|
|
1705
|
+
font-family: inherit;
|
|
1706
|
+
display: inline;
|
|
1707
|
+
}
|
|
1708
|
+
${bodyFont}`;
|
|
1709
|
+
|
|
1710
|
+
let css = fontFace ? `${fontFace}\n` : '';
|
|
1711
|
+
css += `@layer theme, base, components, utilities;\n\n`;
|
|
1712
|
+
css += `@layer theme {\n${variablesCss}}\n\n`;
|
|
1713
|
+
|
|
1714
|
+
const baseStylesCss = generateBaseStyles(config);
|
|
1715
|
+
css += `@layer base {${baseCss}${baseStylesCss}}\n\n`;
|
|
1716
|
+
css += `@layer components {\n${generatePatternComponents()}}\n\n`;
|
|
1717
|
+
css += `@layer utilities {\n${utilityCss}}\n`;
|
|
1718
|
+
|
|
1719
|
+
const fullCssPath = getFullCssPath(config);
|
|
1720
|
+
|
|
1721
|
+
ensureDirectoryForFile(fullCssPath);
|
|
1722
|
+
fs.writeFileSync(fullCssPath, css);
|
|
1723
|
+
|
|
1724
|
+
const manifestSettings = getManifestSettings(config);
|
|
1725
|
+
const intellisenseSettings = getIntellisenseSettings(config);
|
|
1726
|
+
const shouldGenerateManifestData =
|
|
1727
|
+
manifestSettings.enabled || intellisenseSettings.enabled;
|
|
1728
|
+
const manifestData = shouldGenerateManifestData
|
|
1729
|
+
? generateManifest(css, config)
|
|
1730
|
+
: null;
|
|
1731
|
+
|
|
1732
|
+
if (manifestSettings.enabled) {
|
|
1733
|
+
const manifestPath = getManifestOutputPath(config);
|
|
1734
|
+
|
|
1735
|
+
ensureDirectoryForFile(manifestPath);
|
|
1736
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifestData, null, 2));
|
|
1737
|
+
console.log(`✓ Generated manifest: ${manifestPath}`);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
if (intellisenseSettings.enabled) {
|
|
1741
|
+
const intellisensePath = getIntellisenseOutputPath(config);
|
|
1742
|
+
const intellisense = generateIntellisense(manifestData);
|
|
1743
|
+
|
|
1744
|
+
ensureDirectoryForFile(intellisensePath);
|
|
1745
|
+
fs.writeFileSync(intellisensePath, JSON.stringify(intellisense, null, 2));
|
|
1746
|
+
console.log(`✓ Generated IntelliSense: ${intellisensePath}`);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
console.log(`✓ Generated CSS: ${fullCssPath}`);
|
|
1750
|
+
console.log(`✓ File size: ${(css.length / 1024).toFixed(2)} KB (unminified)`);
|
|
1751
|
+
console.log('Full framework build complete');
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
function minify(css) {
|
|
1755
|
+
return css
|
|
1756
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
1757
|
+
.replace(/\s+/g, ' ')
|
|
1758
|
+
.replace(/\s?\{/g, '{')
|
|
1759
|
+
.replace(/\s?\}/g, '}')
|
|
1760
|
+
.replace(/;\s/g, ';')
|
|
1761
|
+
.trim();
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
function buildProductionCss(options = {}) {
|
|
1765
|
+
const config = getConfig();
|
|
1766
|
+
const sourceDir = getSourceDir(config);
|
|
1767
|
+
const fullCssPath = getFullCssPath(config);
|
|
1768
|
+
const productionCssPath = getProductionCssPath(config);
|
|
1769
|
+
const profile = {
|
|
1770
|
+
purge: 0,
|
|
1771
|
+
minify: 0,
|
|
1772
|
+
write: 0,
|
|
1773
|
+
};
|
|
1774
|
+
|
|
1775
|
+
if (!fs.existsSync(fullCssPath)) {
|
|
1776
|
+
buildFullFramework();
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
const { purgeCSS } = require('./purge.js');
|
|
1780
|
+
const css = fs.readFileSync(fullCssPath, 'utf8');
|
|
1781
|
+
const purgeStart = Date.now();
|
|
1782
|
+
const purged = purgeCSS(css, sourceDir, config);
|
|
1783
|
+
profile.purge = Date.now() - purgeStart;
|
|
1784
|
+
|
|
1785
|
+
const minifyStart = Date.now();
|
|
1786
|
+
const minified = minify(purged);
|
|
1787
|
+
profile.minify = Date.now() - minifyStart;
|
|
1788
|
+
|
|
1789
|
+
const writeStart = Date.now();
|
|
1790
|
+
ensureDirectoryForFile(productionCssPath);
|
|
1791
|
+
fs.writeFileSync(productionCssPath, minified);
|
|
1792
|
+
profile.write = Date.now() - writeStart;
|
|
1793
|
+
|
|
1794
|
+
return {
|
|
1795
|
+
css,
|
|
1796
|
+
purged,
|
|
1797
|
+
minified,
|
|
1798
|
+
originalSize: Buffer.byteLength(css, 'utf8'),
|
|
1799
|
+
outputSize: Buffer.byteLength(minified, 'utf8'),
|
|
1800
|
+
outputPath: productionCssPath,
|
|
1801
|
+
fullCssPath,
|
|
1802
|
+
profile: options.profile ? profile : undefined,
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
function isFrameworkStale() {
|
|
1807
|
+
const config = getConfig();
|
|
1808
|
+
const configPath = getConfigPath();
|
|
1809
|
+
const fullCssPath = getFullCssPath(config);
|
|
1810
|
+
|
|
1811
|
+
if (!fs.existsSync(fullCssPath)) return true;
|
|
1812
|
+
if (!fs.existsSync(configPath)) return true;
|
|
1813
|
+
|
|
1814
|
+
return fs.statSync(configPath).mtimeMs > fs.statSync(fullCssPath).mtimeMs;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
function ensureFullFramework() {
|
|
1818
|
+
if (isFrameworkStale()) {
|
|
1819
|
+
buildFullFramework();
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
function build(options = {}) {
|
|
1824
|
+
const totalStart = Date.now();
|
|
1825
|
+
const frameworkStart = Date.now();
|
|
1826
|
+
ensureFullFramework();
|
|
1827
|
+
const frameworkMs = Date.now() - frameworkStart;
|
|
1828
|
+
|
|
1829
|
+
const config = getConfig();
|
|
1830
|
+
const fullCssPath = getFullCssPath(config);
|
|
1831
|
+
const result = buildProductionCss({ profile: options.profile });
|
|
1832
|
+
|
|
1833
|
+
console.log('\u2713 Generated production CSS: ' + path.relative(process.cwd(), result.outputPath));
|
|
1834
|
+
console.log('\u2713 File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
|
|
1835
|
+
|
|
1836
|
+
if (!options.keepFull && fs.existsSync(fullCssPath)) {
|
|
1837
|
+
try {
|
|
1838
|
+
fs.unlinkSync(fullCssPath);
|
|
1839
|
+
console.log('Removed ' + path.relative(process.cwd(), fullCssPath) + ' for production build.');
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
// Windows FUSE: cannot always delete files cleanly, non-fatal.
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
if (options.profile) {
|
|
1846
|
+
const timings = result.profile || { purge: 0, minify: 0, write: 0 };
|
|
1847
|
+
const totalMs = Date.now() - totalStart;
|
|
1848
|
+
|
|
1849
|
+
console.log('\nEmilyCSS build profile\n');
|
|
1850
|
+
console.log('Framework: ' + frameworkMs + 'ms');
|
|
1851
|
+
console.log('Purge: ' + timings.purge + 'ms');
|
|
1852
|
+
console.log('Minify: ' + timings.minify + 'ms');
|
|
1853
|
+
console.log('Write: ' + timings.write + 'ms');
|
|
1854
|
+
console.log('Total: ' + totalMs + 'ms');
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
console.log('Build complete');
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
if (require.main === module) {
|
|
1861
|
+
const args = process.argv.slice(2);
|
|
1862
|
+
build({
|
|
1863
|
+
keepFull: args.includes('--keep-full'),
|
|
1864
|
+
profile: args.includes('--profile'),
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
module.exports = {
|
|
1869
|
+
build,
|
|
1870
|
+
buildFullFramework,
|
|
1871
|
+
buildProductionCss,
|
|
1872
|
+
ensureFullFramework,
|
|
1873
|
+
hexToOklch,
|
|
1874
|
+
oklchToHex,
|
|
1875
|
+
generateColourScale,
|
|
1876
|
+
generateAllColours,
|
|
1877
|
+
generateFontCSS,
|
|
1878
|
+
generateSpacing,
|
|
1879
|
+
generateBorderUtilities,
|
|
1880
|
+
generateColourUtilities,
|
|
1881
|
+
generateSemanticColourUtilities,
|
|
1882
|
+
generateTypographyUtilities,
|
|
1883
|
+
generateSpacingUtilities,
|
|
1884
|
+
generateFlexboxUtilities,
|
|
1885
|
+
generateGridUtilities,
|
|
1886
|
+
addStateVariants,
|
|
1887
|
+
addAriaDataVariants,
|
|
1888
|
+
addResponsiveVariants,
|
|
1889
|
+
generateManifest,
|
|
1890
|
+
};
|