vida-css 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,718 @@
1
+ // scripts/generator.js
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import config from "../vida.config.js";
5
+
6
+ function escapeClass(cls) {
7
+ // Escape characters that would break CSS class selectors.
8
+ // Tailwind-style escaping is a close reference for this.
9
+ return cls
10
+ .replace(/\//g,"\\/") // /
11
+ .replace(/\:/g,"\\:") // :
12
+ .replace(/\./g,"\\.") // .
13
+ .replace(/\[/g,"\\[") // [
14
+ .replace(/\]/g,"\\]") // ]
15
+ .replace(/'/g,"\\'") // '
16
+ .replace(/"/g,'\\"'); // "
17
+ }
18
+
19
+ function splitByColonOutsideBrackets(className) {
20
+ // Tailwind syntax splits variants on `:` but arbitrary values inside `[...]` may contain colons.
21
+ const tokens = [];
22
+ let current = "";
23
+ let bracketDepth = 0;
24
+
25
+ for (const ch of className) {
26
+ if (ch === "[") bracketDepth++;
27
+ if (ch === "]") bracketDepth = Math.max(0, bracketDepth - 1);
28
+
29
+ if (ch === ":" && bracketDepth === 0) {
30
+ tokens.push(current);
31
+ current = "";
32
+ continue;
33
+ }
34
+ current += ch;
35
+ }
36
+
37
+ tokens.push(current);
38
+ return tokens.filter(t => t.length > 0);
39
+ }
40
+
41
+ function resolveSpacingToken(token) {
42
+ // Supports:
43
+ // - config.spacing keys: "1", "2", ...
44
+ // - config.sizeScale keys: "8", "16", ...
45
+ // - fractions: "1/2" => 50%
46
+ // - arbitrary: "[12px]" => 12px
47
+ if (!token) return null;
48
+
49
+ const arbitrary = token.match(/^\[(.+)\]$/);
50
+ if (arbitrary) return arbitrary[1];
51
+
52
+ const frac = token.match(/^(\d+)\/(\d+)$/);
53
+ if (frac) {
54
+ const a = Number(frac[1]);
55
+ const b = Number(frac[2]);
56
+ if (!Number.isFinite(a) || !Number.isFinite(b) || b === 0) return null;
57
+ return `${(a / b) * 100}%`;
58
+ }
59
+
60
+ if (config.spacing && config.spacing[token] != null) return config.spacing[token];
61
+ if (config.sizeScale && config.sizeScale[token] != null) return config.sizeScale[token];
62
+ return null;
63
+ }
64
+
65
+ function resolveColorSpec(colorSpec) {
66
+ // Supports:
67
+ // - "white", "black"
68
+ // - "blue-500", "gray-900", etc (shade must exist in config.colors)
69
+ if (!colorSpec) return null;
70
+
71
+ const direct = config.colors?.[colorSpec];
72
+ if (typeof direct === "string") return direct;
73
+
74
+ const parts = colorSpec.split("-");
75
+ const key = parts[0];
76
+ const shade = parts.slice(1).join("-");
77
+ const entry = config.colors?.[key];
78
+ if (entry && typeof entry === "object" && shade && entry[shade] != null) return entry[shade];
79
+ return null;
80
+ }
81
+
82
+ function hexToRgb(hex) {
83
+ const match = hex.match(/^#([0-9a-fA-F]{6})$/);
84
+ if (!match) return null;
85
+ const value = parseInt(match[1], 16);
86
+ return {
87
+ r: (value >> 16) & 255,
88
+ g: (value >> 8) & 255,
89
+ b: value & 255,
90
+ };
91
+ }
92
+
93
+ function colorWithOpacity(color, opacityVar) {
94
+ const rgb = hexToRgb(color);
95
+ if (!rgb) return color;
96
+ return `rgba(${rgb.r},${rgb.g},${rgb.b},var(${opacityVar},1))`;
97
+ }
98
+
99
+ function resolveUtility(util) {
100
+ // Returns a declaration string like: "width: 12px;" or null if unknown.
101
+ let match;
102
+
103
+ // Gradients (Tailwind-like)
104
+ if (util === "bg-gradient-to-r") {
105
+ return "--vida-gradient-from: rgba(255,255,255,0); --vida-gradient-via: var(--vida-gradient-from); --vida-gradient-to: rgba(255,255,255,0); background-image:linear-gradient(to right, var(--vida-gradient-from), var(--vida-gradient-via), var(--vida-gradient-to));";
106
+ }
107
+ if (util === "bg-gradient-to-l") {
108
+ return "--vida-gradient-from: rgba(255,255,255,0); --vida-gradient-via: var(--vida-gradient-from); --vida-gradient-to: rgba(255,255,255,0); background-image:linear-gradient(to left, var(--vida-gradient-from), var(--vida-gradient-via), var(--vida-gradient-to));";
109
+ }
110
+ if (util === "bg-gradient-to-t") {
111
+ return "--vida-gradient-from: rgba(255,255,255,0); --vida-gradient-via: var(--vida-gradient-from); --vida-gradient-to: rgba(255,255,255,0); background-image:linear-gradient(to top, var(--vida-gradient-from), var(--vida-gradient-via), var(--vida-gradient-to));";
112
+ }
113
+ if (util === "bg-gradient-to-b") {
114
+ return "--vida-gradient-from: rgba(255,255,255,0); --vida-gradient-via: var(--vida-gradient-from); --vida-gradient-to: rgba(255,255,255,0); background-image:linear-gradient(to bottom, var(--vida-gradient-from), var(--vida-gradient-via), var(--vida-gradient-to));";
115
+ }
116
+ if (util === "bg-gradient-to-tr") {
117
+ return "--vida-gradient-from: rgba(255,255,255,0); --vida-gradient-via: var(--vida-gradient-from); --vida-gradient-to: rgba(255,255,255,0); background-image:linear-gradient(to top right, var(--vida-gradient-from), var(--vida-gradient-via), var(--vida-gradient-to));";
118
+ }
119
+ if (util === "bg-gradient-to-br") {
120
+ return "--vida-gradient-from: rgba(255,255,255,0); --vida-gradient-via: var(--vida-gradient-from); --vida-gradient-to: rgba(255,255,255,0); background-image:linear-gradient(to bottom right, var(--vida-gradient-from), var(--vida-gradient-via), var(--vida-gradient-to));";
121
+ }
122
+ if (util === "bg-gradient-to-tl") {
123
+ return "--vida-gradient-from: rgba(255,255,255,0); --vida-gradient-via: var(--vida-gradient-from); --vida-gradient-to: rgba(255,255,255,0); background-image:linear-gradient(to top left, var(--vida-gradient-from), var(--vida-gradient-via), var(--vida-gradient-to));";
124
+ }
125
+ if (util === "bg-gradient-to-bl") {
126
+ return "--vida-gradient-from: rgba(255,255,255,0); --vida-gradient-via: var(--vida-gradient-from); --vida-gradient-to: rgba(255,255,255,0); background-image:linear-gradient(to bottom left, var(--vida-gradient-from), var(--vida-gradient-via), var(--vida-gradient-to));";
127
+ }
128
+ match = util.match(/^from-(.+)$/);
129
+ if (match) {
130
+ const c = resolveColorSpec(match[1]);
131
+ if (c) return `--vida-gradient-from:${c}; --vida-gradient-via:${c};`;
132
+ }
133
+ match = util.match(/^via-(.+)$/);
134
+ if (match) {
135
+ const c = resolveColorSpec(match[1]);
136
+ if (c) return `--vida-gradient-via:${c};`;
137
+ }
138
+ match = util.match(/^to-(.+)$/);
139
+ if (match) {
140
+ const c = resolveColorSpec(match[1]);
141
+ if (c) return `--vida-gradient-to:${c};`;
142
+ }
143
+
144
+ // Width / height
145
+ if (util === "w-full") return "width:100%;";
146
+ if (util === "h-full") return "height:100%;";
147
+ if (util === "w-screen") return "width:100vw;";
148
+ if (util === "h-screen") return "height:100vh;";
149
+ if (util === "min-h-screen") return "min-height:100vh;";
150
+ if (util === "min-w-0") return "min-width:0;";
151
+ if (util === "min-h-0") return "min-height:0;";
152
+ match = util.match(/^w-\[(.+)\]$/);
153
+ if (match) return `width:${match[1]};`;
154
+ match = util.match(/^h-\[(.+)\]$/);
155
+ if (match) return `height:${match[1]};`;
156
+
157
+ if (util.startsWith("w-") && config.sizeScale[util.slice(2)] != null) {
158
+ return `width:${config.sizeScale[util.slice(2)]};`;
159
+ }
160
+ if (util.startsWith("h-") && config.sizeScale[util.slice(2)] != null) {
161
+ return `height:${config.sizeScale[util.slice(2)]};`;
162
+ }
163
+
164
+ // Max/min width (based on existing repo utilities)
165
+ if (util === "max-w-full") return "max-width:100%;";
166
+ if (util === "min-w-full") return "min-width:100%;";
167
+ match = util.match(/^max-w-screen-(.+)$/);
168
+ if (match && config.screens?.[match[1]] != null) return `max-width:${config.screens[match[1]]};`;
169
+ match = util.match(/^(max-w|min-w)-(\d+)\/(\d+)$/);
170
+ if (match) {
171
+ const kind = match[1];
172
+ const a = Number(match[2]);
173
+ const b = Number(match[3]);
174
+ if (!Number.isFinite(a) || !Number.isFinite(b) || b === 0) return null;
175
+ const pct = (a / b) * 100;
176
+ return `${kind === "max-w" ? "max-width" : "min-width"}:${pct}%;`;
177
+ }
178
+
179
+ // Background colors
180
+ if (util.startsWith("bg-")) {
181
+ const colorSpec = util.slice(3); // e.g. "blue-500"
182
+ const parts = colorSpec.split("-");
183
+ const key = parts[0];
184
+ const shade = parts[1];
185
+
186
+ const entry = config.colors?.[key];
187
+ if (typeof entry === "string") return `background-color:${colorWithOpacity(entry, "--vida-bg-opacity")};`;
188
+ if (entry && typeof entry === "object" && shade && entry[shade] != null) {
189
+ return `background-color:${colorWithOpacity(entry[shade], "--vida-bg-opacity")};`;
190
+ }
191
+ }
192
+ match = util.match(/^bg-opacity-(\d+)$/);
193
+ if (match) {
194
+ const v = Number(match[1]);
195
+ if (Number.isFinite(v) && v >= 0 && v <= 100) return `--vida-bg-opacity:${v / 100};`;
196
+ }
197
+ if (util === "bg-cover") return "background-size:cover;";
198
+ if (util === "bg-contain") return "background-size:contain;";
199
+ if (util === "bg-auto") return "background-size:auto;";
200
+ if (util === "bg-center") return "background-position:center;";
201
+ if (util === "bg-top") return "background-position:top;";
202
+ if (util === "bg-bottom") return "background-position:bottom;";
203
+ if (util === "bg-left") return "background-position:left;";
204
+ if (util === "bg-right") return "background-position:right;";
205
+ if (util === "bg-no-repeat") return "background-repeat:no-repeat;";
206
+ if (util === "bg-repeat") return "background-repeat:repeat;";
207
+ if (util === "bg-repeat-x") return "background-repeat:repeat-x;";
208
+ if (util === "bg-repeat-y") return "background-repeat:repeat-y;";
209
+ if (util === "bg-fixed") return "background-attachment:fixed;";
210
+ if (util === "bg-scroll") return "background-attachment:scroll;";
211
+
212
+ // Linear gradients (arbitrary)
213
+ // Example: bg-linear-[linear-gradient(135deg,#1d4ed8,#22c55e)]
214
+ match = util.match(/^bg-linear-\[(.+)\]$/);
215
+ if (match) {
216
+ const value = match[1].replace(/_/g, " ");
217
+ return `background-image:${value};`;
218
+ }
219
+
220
+ // Shadow
221
+ if (util === "shadow-none") return "box-shadow:none;";
222
+ if (util === "shadow-sm") return "box-shadow:0 6px 10px rgba(0,0,0,0.3);";
223
+ if (util === "shadow-md") return "box-shadow:0 8px 12px rgba(0,0,0,0.4);";
224
+ if (util === "shadow-lg") return "box-shadow:0 12px 16px rgba(0,0,0,0.5);";
225
+ if (util === "shadow-xl") return "box-shadow:0 22px 17px rgba(0,0,0,0.5);";
226
+ if (util === "shadow-2xl") return "box-shadow:0 27px 22px rgba(0,0,0,0.6);";
227
+ match = util.match(/^shadow-\[(.+)\]$/);
228
+ if (match) {
229
+ // Tailwind replaces spaces with underscores inside arbitrary values.
230
+ const value = match[1].replace(/_/g, " ");
231
+ return `box-shadow:${value};`;
232
+ }
233
+
234
+ // Text align
235
+ match = util.match(/^text-(left|center|right)$/);
236
+ if (match) return `text-align:${match[1] === "left" ? "left" : match[1] === "center" ? "center" : "right"};`;
237
+
238
+ // Typography
239
+ match = util.match(/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/);
240
+ if (match) {
241
+ const map = {
242
+ xs: "12px",
243
+ sm: "14px",
244
+ base: "16px",
245
+ lg: "18px",
246
+ xl: "20px",
247
+ "2xl": "24px",
248
+ "3xl": "30px",
249
+ "4xl": "36px",
250
+ "5xl": "48px",
251
+ "6xl": "60px",
252
+ "7xl": "72px",
253
+ "8xl": "96px",
254
+ "9xl": "128px",
255
+ };
256
+ return `font-size:${map[match[1]]};`;
257
+ }
258
+ match = util.match(/^font-(sans|serif|mono|display)$/);
259
+ if (match) {
260
+ const fam = config.fontFamily?.[match[1]];
261
+ if (fam) return `font-family:${fam};`;
262
+ }
263
+ match = util.match(/^font-(light|normal|medium|semibold|bold)$/);
264
+ if (match) {
265
+ const map = {
266
+ light: "300",
267
+ normal: "400",
268
+ medium: "500",
269
+ semibold: "600",
270
+ bold: "700",
271
+ };
272
+ return `font-weight:${map[match[1]]};`;
273
+ }
274
+ match = util.match(/^leading-(none|tight|snug|normal|relaxed|loose)$/);
275
+ if (match) {
276
+ const map = {
277
+ none: "1",
278
+ tight: "1.25",
279
+ snug: "1.375",
280
+ normal: "1.5",
281
+ relaxed: "1.625",
282
+ loose: "2",
283
+ };
284
+ return `line-height:${map[match[1]]};`;
285
+ }
286
+ match = util.match(/^tracking-(tighter|tight|normal|wide|wider|widest)$/);
287
+ if (match) {
288
+ const map = {
289
+ tighter: "-0.05em",
290
+ tight: "-0.025em",
291
+ normal: "0em",
292
+ wide: "0.025em",
293
+ wider: "0.05em",
294
+ widest: "0.1em",
295
+ };
296
+ return `letter-spacing:${map[match[1]]};`;
297
+ }
298
+ if (util === "uppercase") return "text-transform:uppercase;";
299
+ if (util === "lowercase") return "text-transform:lowercase;";
300
+ if (util === "capitalize") return "text-transform:capitalize;";
301
+ if (util === "normal-case") return "text-transform:none;";
302
+ if (util === "underline") return "text-decoration:underline;";
303
+ if (util === "no-underline") return "text-decoration:none;";
304
+ if (util === "truncate") return "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";
305
+ // Text colors
306
+ if (util.startsWith("text-")) {
307
+ const colorSpec = util.slice(5);
308
+ const color = resolveColorSpec(colorSpec);
309
+ if (color) return `color:${color};`;
310
+ }
311
+
312
+ // Display
313
+ if (util === "block") return "display:block;";
314
+ if (util === "inline-block") return "display:inline-block;";
315
+ if (util === "inline") return "display:inline;";
316
+ if (util === "hidden") return "display:none;";
317
+
318
+ // Flex / grid
319
+ if (util === "flex") return "display:flex;";
320
+ if (util === "inline-flex") return "display:inline-flex;";
321
+ if (util === "grid") return "display:grid;";
322
+ if (util === "flex-1") return "flex:1 1 0%;";
323
+
324
+ match = util.match(/^flex-(row|col|row-reverse|col-reverse)$/);
325
+ if (match) {
326
+ const map = {
327
+ row: "row",
328
+ col: "column",
329
+ "row-reverse": "row-reverse",
330
+ "col-reverse": "column-reverse",
331
+ };
332
+ return `flex-direction:${map[match[1]]};`;
333
+ }
334
+
335
+ match = util.match(/^justify-(start|center|end|between|around|evenly)$/);
336
+ if (match) {
337
+ const map = {
338
+ start: "flex-start",
339
+ center: "center",
340
+ end: "flex-end",
341
+ between: "space-between",
342
+ around: "space-around",
343
+ evenly: "space-evenly",
344
+ };
345
+ return `justify-content:${map[match[1]]};`;
346
+ }
347
+
348
+ match = util.match(/^items-(start|center|end|stretch)$/);
349
+ if (match) {
350
+ const map = {
351
+ start: "flex-start",
352
+ center: "center",
353
+ end: "flex-end",
354
+ stretch: "stretch",
355
+ };
356
+ return `align-items:${map[match[1]]};`;
357
+ }
358
+
359
+ match = util.match(/^grid-cols-(\d+)$/);
360
+ if (match) {
361
+ const n = Number(match[1]);
362
+ if (!Number.isFinite(n) || n <= 0) return null;
363
+ return `grid-template-columns:repeat(${n},1fr);`;
364
+ }
365
+
366
+ match = util.match(/^gap-(\d+)$/);
367
+ if (match && config.spacing?.[match[1]] != null) return `gap:${config.spacing[match[1]]};`;
368
+
369
+ match = util.match(/^overflow-(hidden|auto|scroll)$/);
370
+ if (match) return `overflow:${match[1]};`;
371
+
372
+ // Positioning
373
+ match = util.match(/^(static|relative|absolute|fixed|sticky)$/);
374
+ if (match) return `position:${match[1]};`;
375
+
376
+ // Z index
377
+ match = util.match(/^z-(\d+)$/);
378
+ if (match) return `z-index:${Number(match[1])};`;
379
+
380
+ // Border radius
381
+ match = util.match(/^rounded-(sm|md|lg)$/);
382
+ if (match && config.borderRadius?.[match[1]] != null) return `border-radius:${config.borderRadius[match[1]]};`;
383
+
384
+ // Border width + color
385
+ if (util === "border") return "border-width:1px;border-style:solid;border-color:currentColor;";
386
+ if (util === "border-0") return "border-width:0;border-style:solid;border-color:currentColor;";
387
+ if (util === "border-2") return "border-width:2px;border-style:solid;border-color:currentColor;";
388
+ if (util === "border-4") return "border-width:4px;border-style:solid;border-color:currentColor;";
389
+ match = util.match(/^border-\[(.+)\]$/);
390
+ if (match) return `border-width:${match[1]};border-style:solid;border-color:currentColor;`;
391
+ if (util.startsWith("border-")) {
392
+ const colorSpec = util.slice(7);
393
+ const color = resolveColorSpec(colorSpec);
394
+ if (color) return `border-color:${color};`;
395
+ }
396
+
397
+ // Accessibility
398
+ if (util === "sr-only") {
399
+ return "position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;";
400
+ }
401
+ if (util === "not-sr-only") {
402
+ return "position:static;width:auto;height:auto;padding:0;margin:0;overflow:visible;clip:auto;white-space:normal;";
403
+ }
404
+
405
+ // Padding / margin
406
+ // Examples:
407
+ // - p-1, px-2, pt-4
408
+ // - p-1/2
409
+ // - p-[12px]
410
+ match = util.match(/^(p|m)([trblxy]?)-(.*)$/);
411
+ if (match) {
412
+ const isPad = match[1] === "p";
413
+ const axis = match[2]; // "", x, y, t, r, b, l
414
+ const valueToken = match[3];
415
+ const value = resolveSpacingToken(valueToken);
416
+ if (value == null) return null;
417
+
418
+ const prefix = isPad ? "padding" : "margin";
419
+ if (!axis) return `${prefix}:${value};`;
420
+ if (axis === "x") return `${prefix}-left:${value};${prefix}-right:${value};`;
421
+ if (axis === "y") return `${prefix}-top:${value};${prefix}-bottom:${value};`;
422
+ if (axis === "t") return `${prefix}-top:${value};`;
423
+ if (axis === "r") return `${prefix}-right:${value};`;
424
+ if (axis === "b") return `${prefix}-bottom:${value};`;
425
+ if (axis === "l") return `${prefix}-left:${value};`;
426
+ }
427
+
428
+ // Positioning offsets
429
+ match = util.match(/^(inset|inset-x|inset-y|top|right|bottom|left)-(.+)$/);
430
+ if (match) {
431
+ const what = match[1];
432
+ const valueToken = match[2];
433
+
434
+ const arbitrary = valueToken.match(/^\[(.+)\]$/);
435
+ const raw = arbitrary ? arbitrary[1] : valueToken;
436
+ const value =
437
+ arbitrary ? raw
438
+ : resolveSpacingToken(valueToken) ?? (valueToken.match(/^-?\d+(\.\d+)?$/) ? `${raw}px` : null);
439
+ if (value == null) return null;
440
+
441
+ if (what === "inset") return `top:${value};right:${value};bottom:${value};left:${value};`;
442
+ if (what === "inset-x") return `left:${value};right:${value};`;
443
+ if (what === "inset-y") return `top:${value};bottom:${value};`;
444
+ return `${what}:${value};`;
445
+ }
446
+
447
+ // Transitions
448
+ if (util === "transition") return "transition-property:all;";
449
+ if (util === "transition-all") return "transition-property:all;";
450
+ if (util === "transition-none") return "transition-property:none;";
451
+ match = util.match(/^duration-(.+)$/);
452
+ if (match) {
453
+ const valueToken = match[1];
454
+ const arbitrary = valueToken.match(/^\[(.+)\]$/);
455
+ if (arbitrary) return `transition-duration:${arbitrary[1]};`;
456
+ if (valueToken.match(/^\d+$/)) return `transition-duration:${valueToken}ms;`;
457
+ }
458
+ match = util.match(/^delay-(.+)$/);
459
+ if (match) {
460
+ const valueToken = match[1];
461
+ const arbitrary = valueToken.match(/^\[(.+)\]$/);
462
+ if (arbitrary) return `transition-delay:${arbitrary[1]};`;
463
+ if (valueToken.match(/^\d+$/)) return `transition-delay:${valueToken}ms;`;
464
+ }
465
+ match = util.match(/^ease-(.+)$/);
466
+ if (match) {
467
+ const valueToken = match[1];
468
+ const arbitrary = valueToken.match(/^\[(.+)\]$/);
469
+ if (arbitrary) return `transition-timing-function:${arbitrary[1]};`;
470
+ const map = {
471
+ linear: "cubic-bezier(0,0,1,1)",
472
+ in: "cubic-bezier(0.4,0,1,1)",
473
+ out: "cubic-bezier(0,0,0.2,1)",
474
+ "in-out": "cubic-bezier(0.4,0,0.2,1)",
475
+ };
476
+ if (map[valueToken]) return `transition-timing-function:${map[valueToken]};`;
477
+ }
478
+
479
+ // Animations (keyframes injected in generateCSS)
480
+ if (util === "animate-spin") return "animation:spin 1s linear infinite;";
481
+ if (util === "animate-ping") return "animation:ping 1s cubic-bezier(0,0,0.2,1) infinite;";
482
+ if (util === "animate-pulse") return "animation:pulse 2s cubic-bezier(0.4,0,0.6,1) infinite;";
483
+
484
+ // Rings
485
+ if (util === "ring-inset") {
486
+ return `--vida-ring-inset: inset; box-shadow:var(--vida-ring-inset, ) 0 0 0 var(--vida-ring-width, 0px) var(--vida-ring-color, transparent), var(--vida-ring-inset, ) 0 0 0 calc(var(--vida-ring-width, 0px) + var(--vida-ring-offset-width, 0px)) var(--vida-ring-offset-color, transparent);`;
487
+ }
488
+ match = util.match(/^ring-offset-(.+)$/);
489
+ if (match) {
490
+ const v = match[1];
491
+ if (v.match(/^-?\d+(\.\d+)?$/)) {
492
+ const px = resolveSpacingToken(v) ?? `${v}px`;
493
+ return `--vida-ring-offset-width:${px}; box-shadow:var(--vida-ring-inset, ) 0 0 0 var(--vida-ring-width, 0px) var(--vida-ring-color, transparent), var(--vida-ring-inset, ) 0 0 0 calc(var(--vida-ring-width, 0px) + var(--vida-ring-offset-width, 0px)) var(--vida-ring-offset-color, transparent);`;
494
+ }
495
+ const c = resolveColorSpec(v);
496
+ if (c) {
497
+ return `--vida-ring-offset-color:${c}; box-shadow:var(--vida-ring-inset, ) 0 0 0 var(--vida-ring-width, 0px) var(--vida-ring-color, transparent), var(--vida-ring-inset, ) 0 0 0 calc(var(--vida-ring-width, 0px) + var(--vida-ring-offset-width, 0px)) var(--vida-ring-offset-color, transparent);`;
498
+ }
499
+ }
500
+ match = util.match(/^ring-(\d+)$/);
501
+ if (match) {
502
+ const w = `${match[1]}px`;
503
+ return `--vida-ring-width:${w}; box-shadow:var(--vida-ring-inset, ) 0 0 0 var(--vida-ring-width, 0px) var(--vida-ring-color, transparent), var(--vida-ring-inset, ) 0 0 0 calc(var(--vida-ring-width, 0px) + var(--vida-ring-offset-width, 0px)) var(--vida-ring-offset-color, transparent);`;
504
+ }
505
+ match = util.match(/^ring-(.+)$/);
506
+ if (match) {
507
+ const c = resolveColorSpec(match[1]);
508
+ if (c) {
509
+ return `--vida-ring-color:${c}; box-shadow:var(--vida-ring-inset, ) 0 0 0 var(--vida-ring-width, 0px) var(--vida-ring-color, transparent), var(--vida-ring-inset, ) 0 0 0 calc(var(--vida-ring-width, 0px) + var(--vida-ring-offset-width, 0px)) var(--vida-ring-offset-color, transparent);`;
510
+ }
511
+ }
512
+
513
+ // Transforms (composed via CSS variables)
514
+ match = util.match(/^translate-x-(.+)$/);
515
+ if (match) {
516
+ const v = match[1];
517
+ const arbitrary = v.match(/^\[(.+)\]$/);
518
+ const value = arbitrary ? arbitrary[1] : (resolveSpacingToken(v) ?? (v.match(/^-?\d+(\.\d+)?$/) ? `${v}px` : null));
519
+ if (value == null) return null;
520
+ return `--vida-translate-x:${value}; transform:translateX(var(--vida-translate-x, 0px)) translateY(var(--vida-translate-y, 0px)) rotate(var(--vida-rotate, 0deg)) scale(var(--vida-scale-x, 1), var(--vida-scale-y, 1)) skewX(var(--vida-skew-x, 0deg)) skewY(var(--vida-skew-y, 0deg));`;
521
+ }
522
+ match = util.match(/^translate-y-(.+)$/);
523
+ if (match) {
524
+ const v = match[1];
525
+ const arbitrary = v.match(/^\[(.+)\]$/);
526
+ const value = arbitrary ? arbitrary[1] : (resolveSpacingToken(v) ?? (v.match(/^-?\d+(\.\d+)?$/) ? `${v}px` : null));
527
+ if (value == null) return null;
528
+ return `--vida-translate-y:${value}; transform:translateX(var(--vida-translate-x, 0px)) translateY(var(--vida-translate-y, 0px)) rotate(var(--vida-rotate, 0deg)) scale(var(--vida-scale-x, 1), var(--vida-scale-y, 1)) skewX(var(--vida-skew-x, 0deg)) skewY(var(--vida-skew-y, 0deg));`;
529
+ }
530
+ match = util.match(/^rotate-(.+)$/);
531
+ if (match) {
532
+ const v = match[1];
533
+ const arbitrary = v.match(/^\[(.+)\]$/);
534
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${v}deg` : null);
535
+ if (value == null) return null;
536
+ return `--vida-rotate:${value}; transform:translateX(var(--vida-translate-x, 0px)) translateY(var(--vida-translate-y, 0px)) rotate(var(--vida-rotate, 0deg)) scale(var(--vida-scale-x, 1), var(--vida-scale-y, 1)) skewX(var(--vida-skew-x, 0deg)) skewY(var(--vida-skew-y, 0deg));`;
537
+ }
538
+ match = util.match(/^scale-(.+)$/);
539
+ if (match) {
540
+ const v = match[1];
541
+ const arbitrary = v.match(/^\[(.+)\]$/);
542
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${Number(v) / 100}` : null);
543
+ if (value == null) return null;
544
+ return `--vida-scale-x:${value}; --vida-scale-y:${value}; transform:translateX(var(--vida-translate-x, 0px)) translateY(var(--vida-translate-y, 0px)) rotate(var(--vida-rotate, 0deg)) scale(var(--vida-scale-x, 1), var(--vida-scale-y, 1)) skewX(var(--vida-skew-x, 0deg)) skewY(var(--vida-skew-y, 0deg));`;
545
+ }
546
+ match = util.match(/^scale-x-(.+)$/);
547
+ if (match) {
548
+ const v = match[1];
549
+ const arbitrary = v.match(/^\[(.+)\]$/);
550
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${Number(v) / 100}` : null);
551
+ if (value == null) return null;
552
+ return `--vida-scale-x:${value}; transform:translateX(var(--vida-translate-x, 0px)) translateY(var(--vida-translate-y, 0px)) rotate(var(--vida-rotate, 0deg)) scale(var(--vida-scale-x, 1), var(--vida-scale-y, 1)) skewX(var(--vida-skew-x, 0deg)) skewY(var(--vida-skew-y, 0deg));`;
553
+ }
554
+ match = util.match(/^scale-y-(.+)$/);
555
+ if (match) {
556
+ const v = match[1];
557
+ const arbitrary = v.match(/^\[(.+)\]$/);
558
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${Number(v) / 100}` : null);
559
+ if (value == null) return null;
560
+ return `--vida-scale-y:${value}; transform:translateX(var(--vida-translate-x, 0px)) translateY(var(--vida-translate-y, 0px)) rotate(var(--vida-rotate, 0deg)) scale(var(--vida-scale-x, 1), var(--vida-scale-y, 1)) skewX(var(--vida-skew-x, 0deg)) skewY(var(--vida-skew-y, 0deg));`;
561
+ }
562
+ match = util.match(/^skew-x-(.+)$/);
563
+ if (match) {
564
+ const v = match[1];
565
+ const arbitrary = v.match(/^\[(.+)\]$/);
566
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${v}deg` : null);
567
+ if (value == null) return null;
568
+ return `--vida-skew-x:${value}; transform:translateX(var(--vida-translate-x, 0px)) translateY(var(--vida-translate-y, 0px)) rotate(var(--vida-rotate, 0deg)) scale(var(--vida-scale-x, 1), var(--vida-scale-y, 1)) skewX(var(--vida-skew-x, 0deg)) skewY(var(--vida-skew-y, 0deg));`;
569
+ }
570
+ match = util.match(/^skew-y-(.+)$/);
571
+ if (match) {
572
+ const v = match[1];
573
+ const arbitrary = v.match(/^\[(.+)\]$/);
574
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${v}deg` : null);
575
+ if (value == null) return null;
576
+ return `--vida-skew-y:${value}; transform:translateX(var(--vida-translate-x, 0px)) translateY(var(--vida-translate-y, 0px)) rotate(var(--vida-rotate, 0deg)) scale(var(--vida-scale-x, 1), var(--vida-scale-y, 1)) skewX(var(--vida-skew-x, 0deg)) skewY(var(--vida-skew-y, 0deg));`;
577
+ }
578
+
579
+ // Filters (composed via CSS variables)
580
+ if (util === "filter-none") return "filter:none;";
581
+ match = util.match(/^blur-(.+)$/);
582
+ if (match) {
583
+ const v = match[1];
584
+ const arbitrary = v.match(/^\[(.+)\]$/);
585
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${v}px` : null);
586
+ if (value == null) return null;
587
+ return `--vida-blur:${value}; filter:blur(var(--vida-blur, 0px)) brightness(var(--vida-brightness, 1)) contrast(var(--vida-contrast, 1)) saturate(var(--vida-saturate, 1)) grayscale(var(--vida-grayscale, 0%)) sepia(var(--vida-sepia, 0%)) hue-rotate(var(--vida-hue-rotate, 0deg)) invert(var(--vida-invert, 0%)) opacity(var(--vida-opacity, 1));`;
588
+ }
589
+ match = util.match(/^(brightness|contrast|saturate)-(.*)$/);
590
+ if (match) {
591
+ const kind = match[1];
592
+ const v = match[2];
593
+ const arbitrary = v.match(/^\[(.+)\]$/);
594
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${Number(v) / 100}` : null);
595
+ if (value == null) return null;
596
+ const varName = kind === "brightness" ? "--vida-brightness" : kind === "contrast" ? "--vida-contrast" : "--vida-saturate";
597
+ return `${varName}:${value}; filter:blur(var(--vida-blur, 0px)) brightness(var(--vida-brightness, 1)) contrast(var(--vida-contrast, 1)) saturate(var(--vida-saturate, 1)) grayscale(var(--vida-grayscale, 0%)) sepia(var(--vida-sepia, 0%)) hue-rotate(var(--vida-hue-rotate, 0deg)) invert(var(--vida-invert, 0%)) opacity(var(--vida-opacity, 1));`;
598
+ }
599
+ match = util.match(/^(grayscale|sepia|invert)-(.*)$/);
600
+ if (match) {
601
+ const kind = match[1];
602
+ const v = match[2];
603
+ const arbitrary = v.match(/^\[(.+)\]$/);
604
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${v}%` : null);
605
+ if (value == null) return null;
606
+ const varName = kind === "grayscale" ? "--vida-grayscale" : kind === "sepia" ? "--vida-sepia" : "--vida-invert";
607
+ return `${varName}:${value}; filter:blur(var(--vida-blur, 0px)) brightness(var(--vida-brightness, 1)) contrast(var(--vida-contrast, 1)) saturate(var(--vida-saturate, 1)) grayscale(var(--vida-grayscale, 0%)) sepia(var(--vida-sepia, 0%)) hue-rotate(var(--vida-hue-rotate, 0deg)) invert(var(--vida-invert, 0%)) opacity(var(--vida-opacity, 1));`;
608
+ }
609
+ match = util.match(/^hue-rotate-(.+)$/);
610
+ if (match) {
611
+ const v = match[1];
612
+ const arbitrary = v.match(/^\[(.+)\]$/);
613
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${v}deg` : null);
614
+ if (value == null) return null;
615
+ return `--vida-hue-rotate:${value}; filter:blur(var(--vida-blur, 0px)) brightness(var(--vida-brightness, 1)) contrast(var(--vida-contrast, 1)) saturate(var(--vida-saturate, 1)) grayscale(var(--vida-grayscale, 0%)) sepia(var(--vida-sepia, 0%)) hue-rotate(var(--vida-hue-rotate, 0deg)) invert(var(--vida-invert, 0%)) opacity(var(--vida-opacity, 1));`;
616
+ }
617
+ match = util.match(/^opacity-(.+)$/);
618
+ if (match) {
619
+ const v = match[1];
620
+ const arbitrary = v.match(/^\[(.+)\]$/);
621
+ const value = arbitrary ? arbitrary[1] : (v.match(/^-?\d+(\.\d+)?$/) ? `${Number(v) / 100}` : null);
622
+ if (value == null) return null;
623
+ return `--vida-opacity:${value}; filter:blur(var(--vida-blur, 0px)) brightness(var(--vida-brightness, 1)) contrast(var(--vida-contrast, 1)) saturate(var(--vida-saturate, 1)) grayscale(var(--vida-grayscale, 0%)) sepia(var(--vida-sepia, 0%)) hue-rotate(var(--vida-hue-rotate, 0deg)) invert(var(--vida-invert, 0%)) opacity(var(--vida-opacity, 1));`;
624
+ }
625
+
626
+ // Pseudo-elements
627
+ match = util.match(/^content-\[(.+)\]$/);
628
+ if (match) return `content:${match[1]};`;
629
+
630
+ // Nothing matched
631
+ return null;
632
+ }
633
+
634
+ export function generateCSS() {
635
+ const classFile = path.join(process.cwd(), ".vida-classes.json");
636
+ if (!fs.existsSync(classFile)) { console.error("No .vida-classes.json found!"); return; }
637
+ const classes = JSON.parse(fs.readFileSync(classFile,"utf-8"));
638
+
639
+ let finalCSS = "";
640
+ let keyframesCSS = "";
641
+ const keyframesAdded = new Set();
642
+
643
+ classes.forEach(cls => {
644
+ // Split Tailwind-like variants: md:hover:bg-red-500, dark:bg-*, disabled:*, visited:*
645
+ const tokens = splitByColonOutsideBrackets(cls);
646
+ if (tokens.length === 0) return;
647
+
648
+ const pseudoMap = {
649
+ hover: ":hover",
650
+ focus: ":focus",
651
+ "focus-visible": ":focus-visible",
652
+ active: ":active",
653
+ disabled: ":disabled",
654
+ visited: ":visited",
655
+ };
656
+ const isScreen = t => config.screens && config.screens[t] != null;
657
+ const isDark = t => t === "dark";
658
+
659
+ // Consume variant tokens from the left until the first "utility" token.
660
+ let i = 0;
661
+ for (; i < tokens.length; i++) {
662
+ const t = tokens[i];
663
+ if (isScreen(t) || pseudoMap[t] != null || t === "before" || t === "after" || isDark(t)) continue;
664
+ break;
665
+ }
666
+
667
+ const variantTokens = tokens.slice(0, i);
668
+ const util = tokens.slice(i).join(":");
669
+
670
+ // Inject keyframes once per build when needed.
671
+ if (util === "animate-spin" && !keyframesAdded.has("spin")) {
672
+ keyframesAdded.add("spin");
673
+ keyframesCSS += "@keyframes spin{to{transform:rotate(360deg)}}\n";
674
+ }
675
+ if (util === "animate-ping" && !keyframesAdded.has("ping")) {
676
+ keyframesAdded.add("ping");
677
+ keyframesCSS += "@keyframes ping{75%,100%{transform:scale(2);opacity:0}}0%{transform:scale(1);opacity:1}\n";
678
+ }
679
+ if (util === "animate-pulse" && !keyframesAdded.has("pulse")) {
680
+ keyframesAdded.add("pulse");
681
+ keyframesCSS += "@keyframes pulse{50%{opacity:.5}}100%{opacity:1}\n";
682
+ }
683
+
684
+ const declarations = resolveUtility(util);
685
+ if (!declarations) return;
686
+
687
+ // Base selector always uses the full original class name (Tailwind escaping).
688
+ let selector = `.${escapeClass(cls)}`;
689
+
690
+ const pseudoElementMap = { before: "::before", after: "::after" };
691
+ const pseudoElementTokens = variantTokens.filter(t => pseudoElementMap[t] != null);
692
+
693
+ // Pseudo variants should come before pseudo-elements:
694
+ // e.g. `.hover\:before\:bg-red-500:hover::before` (valid form)
695
+ const pseudoTokens = variantTokens.filter(t => pseudoMap[t] != null);
696
+ if (pseudoTokens.length > 0) selector += pseudoTokens.map(t => pseudoMap[t]).join("");
697
+
698
+ if (pseudoElementTokens.length > 0) selector += pseudoElementTokens.map(t => pseudoElementMap[t]).join("");
699
+
700
+ // Dark variant scopes with `.dark` ancestor (Tailwind default for class-based dark mode).
701
+ if (variantTokens.includes("dark")) selector = `.dark ${selector}`;
702
+
703
+ // Responsive variants wrap in media queries (nesting if multiple are provided).
704
+ const screenTokens = variantTokens.filter(t => isScreen(t));
705
+ let rule = `${selector} { ${declarations} }\n`;
706
+ for (const bp of screenTokens.slice().reverse()) {
707
+ const size = config.screens[bp];
708
+ rule = `@media (min-width:${size}) { ${rule} }\n`;
709
+ }
710
+
711
+ finalCSS += rule;
712
+ });
713
+
714
+ const distDir = path.join(process.cwd(),"dist");
715
+ if (!fs.existsSync(distDir)) fs.mkdirSync(distDir);
716
+ fs.writeFileSync(path.join(distDir,"vida.css"),keyframesCSS + finalCSS);
717
+ console.log("✅ CSS generated successfully!");
718
+ }