mnfst 0.5.199-next.4 → 0.5.199

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,2870 @@
1
+ /* manifest.utilities.node.mjs — built from scripts/utilities/ (DOM-free) */
2
+
3
+ // Utility generators
4
+ // Functions that generate CSS utilities from CSS variable suffixes
5
+
6
+ function createUtilityGenerators() {
7
+ return {
8
+ 'color-': (suffix, value) => {
9
+ const utilities = [];
10
+ const addUtility = (prefix, property, baseValue) => {
11
+ utilities.push([`${prefix}-${suffix}`, `${property}: ${baseValue}`]);
12
+ };
13
+ addUtility('text', 'color', value);
14
+ addUtility('bg', 'background-color', value);
15
+ addUtility('border', 'border-color', value);
16
+ addUtility('outline', 'outline-color', value);
17
+ addUtility('ring', 'box-shadow', `0 0 0 1px ${value}`);
18
+ addUtility('fill', 'fill', value);
19
+ addUtility('stroke', 'stroke', value);
20
+ addUtility('decoration', 'text-decoration-color', value);
21
+ addUtility('accent', 'accent-color', value);
22
+ addUtility('caret', 'caret-color', value);
23
+ utilities.push([`from-${suffix}`, `--tw-gradient-from: ${value}; --tw-gradient-stops: var(--tw-gradient-via-stops)`]);
24
+ utilities.push([`via-${suffix}`, `--tw-gradient-via: ${value}; --tw-gradient-stops: var(--tw-gradient-via-stops)`]);
25
+ utilities.push([`to-${suffix}`, `--tw-gradient-to: ${value}; --tw-gradient-stops: var(--tw-gradient-via-stops)`]);
26
+ return utilities;
27
+ },
28
+ 'font-': (suffix, value) => [
29
+ [`font-${suffix}`, `font-family: ${value}`]
30
+ ],
31
+ 'text-': (suffix, value) => [
32
+ [`text-${suffix}`, `font-size: ${value}`]
33
+ ],
34
+ 'font-weight-': (suffix, value) => [
35
+ [`font-${suffix}`, `font-weight: ${value}`]
36
+ ],
37
+ 'tracking-': (suffix, value) => [
38
+ [`tracking-${suffix}`, `letter-spacing: ${value}`]
39
+ ],
40
+ 'leading-': (suffix, value) => [
41
+ [`leading-${suffix}`, `line-height: ${value}`]
42
+ ],
43
+ 'breakpoint-': (suffix, value) => [
44
+ [`@${suffix}`, `@media (min-width: ${value})`]
45
+ ],
46
+ 'container-': (suffix, value) => [
47
+ [`container-${suffix}`, `max-width: ${value}`],
48
+ [`@container-${suffix}`, `@container (min-width: ${value})`]
49
+ ],
50
+ 'spacing-': (suffix, value) => [
51
+ [`gap-${suffix}`, `gap: ${value}`],
52
+ [`p-${suffix}`, `padding: ${value}`],
53
+ [`px-${suffix}`, `padding-left: ${value}; padding-right: ${value}`],
54
+ [`py-${suffix}`, `padding-top: ${value}; padding-bottom: ${value}`],
55
+ [`m-${suffix}`, `margin: ${value}`],
56
+ [`mx-${suffix}`, `margin-left: ${value}; margin-right: ${value}`],
57
+ [`my-${suffix}`, `margin-top: ${value}; margin-bottom: ${value}`],
58
+ [`space-x-${suffix}`, `> * + * { margin-left: ${value}; }`],
59
+ [`space-y-${suffix}`, `> * + * { margin-top: ${value}; }`],
60
+ [`max-w-${suffix}`, `max-width: ${value}`],
61
+ [`max-h-${suffix}`, `max-height: ${value}`],
62
+ [`min-w-${suffix}`, `min-width: ${value}`],
63
+ [`min-h-${suffix}`, `min-height: ${value}`],
64
+ [`w-${suffix}`, `width: ${value}`],
65
+ [`h-${suffix}`, `height: ${value}`]
66
+ ],
67
+ 'radius-': (suffix, value) => [
68
+ [`rounded-${suffix}`, `border-radius: ${value}`]
69
+ ],
70
+ 'shadow-': (suffix, value) => [
71
+ [`shadow-${suffix}`, `box-shadow: ${value}`]
72
+ ],
73
+ 'inset-shadow-': (suffix, value) => [
74
+ [`inset-shadow-${suffix}`, `box-shadow: inset ${value}`]
75
+ ],
76
+ 'drop-shadow-': (suffix, value) => [
77
+ [`drop-shadow-${suffix}`, `filter: drop-shadow(${value})`]
78
+ ],
79
+ 'blur-': (suffix, value) => [
80
+ [`blur-${suffix}`, `filter: blur(${value})`]
81
+ ],
82
+ 'perspective-': (suffix, value) => [
83
+ [`perspective-${suffix}`, `perspective: ${value}`]
84
+ ],
85
+ 'aspect-': (suffix, value) => [
86
+ [`aspect-${suffix}`, `aspect-ratio: ${value}`]
87
+ ],
88
+ 'ease-': (suffix, value) => [
89
+ [`ease-${suffix}`, `transition-timing-function: ${value}`]
90
+ ],
91
+ 'animate-': (suffix, value) => [
92
+ [`animate-${suffix}`, `animation: ${value}`]
93
+ ],
94
+ 'border-width-': (suffix, value) => [
95
+ [`border-${suffix}`, `border-width: ${value}`]
96
+ ],
97
+ 'border-style-': (suffix, value) => [
98
+ [`border-${suffix}`, `border-style: ${value}`]
99
+ ],
100
+ 'outline-': (suffix, value) => [
101
+ [`outline-${suffix}`, `outline-color: ${value}`]
102
+ ],
103
+ 'outline-width-': (suffix, value) => [
104
+ [`outline-${suffix}`, `outline-width: ${value}`]
105
+ ],
106
+ 'outline-style-': (suffix, value) => [
107
+ [`outline-${suffix}`, `outline-style: ${value}`]
108
+ ],
109
+ 'ring-': (suffix, value) => [
110
+ [`ring-${suffix}`, `box-shadow: 0 0 0 ${value} var(--color-ring)`]
111
+ ],
112
+ 'ring-offset-': (suffix, value) => [
113
+ [`ring-offset-${suffix}`, `--tw-ring-offset-width: ${value}`]
114
+ ],
115
+ 'divide-': (suffix, value) => [
116
+ [`divide-${suffix}`, `border-color: ${value}`]
117
+ ],
118
+ 'accent-': (suffix, value) => [
119
+ [`accent-${suffix}`, `accent-color: ${value}`]
120
+ ],
121
+ 'caret-': (suffix, value) => [
122
+ [`caret-${suffix}`, `caret-color: ${value}`]
123
+ ],
124
+ 'decoration-': (suffix, value) => [
125
+ [`decoration-${suffix}`, `text-decoration-color: ${value}`]
126
+ ],
127
+ 'placeholder-': (suffix, value) => [
128
+ [`placeholder-${suffix}`, `&::placeholder { color: ${value} }`]
129
+ ],
130
+ 'selection-': (suffix, value) => [
131
+ [`selection-${suffix}`, `&::selection { background-color: ${value} }`]
132
+ ],
133
+ 'scrollbar-': (suffix, value) => [
134
+ [`scrollbar-${suffix}`, `scrollbar-color: ${value}`]
135
+ ]
136
+ };
137
+ }
138
+
139
+
140
+
141
+ // Variants and variant groups
142
+ // CSS selector mappings for Tailwind variants
143
+
144
+ function createVariants() {
145
+ return {
146
+ // State variants
147
+ 'hover': ':hover',
148
+ 'focus': ':focus',
149
+ 'focus-visible': ':focus-visible',
150
+ 'focus-within': ':focus-within',
151
+ 'active': ':active',
152
+ 'visited': ':visited',
153
+ 'target': ':target',
154
+ 'first': ':first-child',
155
+ 'last': ':last-child',
156
+ 'only': ':only-child',
157
+ 'odd': ':nth-child(odd)',
158
+ 'even': ':nth-child(even)',
159
+ 'first-of-type': ':first-of-type',
160
+ 'last-of-type': ':last-of-type',
161
+ 'only-of-type': ':only-of-type',
162
+ 'empty': ':empty',
163
+ 'disabled': ':disabled',
164
+ 'enabled': ':enabled',
165
+ 'checked': ':checked',
166
+ 'indeterminate': ':indeterminate',
167
+ 'default': ':default',
168
+ 'required': ':required',
169
+ 'valid': ':valid',
170
+ 'invalid': ':invalid',
171
+ 'in-range': ':in-range',
172
+ 'out-of-range': ':out-of-range',
173
+ 'placeholder-shown': ':placeholder-shown',
174
+ 'autofill': ':autofill',
175
+ 'read-only': ':read-only',
176
+ 'read-write': ':read-write',
177
+ 'optional': ':optional',
178
+ 'user-valid': ':user-valid',
179
+ 'user-invalid': ':user-invalid',
180
+ 'inert': ':inert',
181
+ 'open': ':is([open], :popover-open, :open) &',
182
+ 'closed': ':not(:is([open], :popover-open, :open)) &',
183
+ 'paused': '[data-state="paused"] &',
184
+ 'playing': '[data-state="playing"] &',
185
+ 'muted': '[data-state="muted"] &',
186
+ 'unmuted': '[data-state="unmuted"] &',
187
+ 'collapsed': '[data-state="collapsed"] &',
188
+ 'expanded': '[data-state="expanded"] &',
189
+ 'unchecked': ':not(:checked)',
190
+ 'selected': '[data-state="selected"] &',
191
+ 'unselected': '[data-state="unselected"] &',
192
+ 'details-content': '::details-content',
193
+ 'nth': ':nth-child',
194
+ 'nth-last': ':nth-last-child',
195
+ 'nth-of-type': ':nth-of-type',
196
+ 'nth-last-of-type': ':nth-last-of-type',
197
+ 'has': ':has',
198
+ 'not': ':not',
199
+
200
+ // Pseudo-elements
201
+ 'before': '::before',
202
+ 'after': '::after',
203
+ 'first-letter': '::first-letter',
204
+ 'first-line': '::first-line',
205
+ 'marker': '::marker',
206
+ 'selection': '::selection',
207
+ 'file': '::file-selector-button',
208
+ 'backdrop': '::backdrop',
209
+ 'placeholder': '::placeholder',
210
+ 'target-text': '::target-text',
211
+ 'spelling-error': '::spelling-error',
212
+ 'grammar-error': '::grammar-error',
213
+
214
+ // Media queries
215
+ 'dark': '.dark &',
216
+ 'light': '.light &',
217
+
218
+ // Group variants
219
+ 'group': '.group &',
220
+ 'group-hover': '.group:hover &',
221
+ 'group-focus': '.group:focus &',
222
+ 'group-focus-within': '.group:focus-within &',
223
+ 'group-active': '.group:active &',
224
+ 'group-disabled': '.group:disabled &',
225
+ 'group-visited': '.group:visited &',
226
+ 'group-checked': '.group:checked &',
227
+ 'group-required': '.group:required &',
228
+ 'group-valid': '.group:valid &',
229
+ 'group-invalid': '.group:invalid &',
230
+ 'group-in-range': '.group:in-range &',
231
+ 'group-out-of-range': '.group:out-of-range &',
232
+ 'group-placeholder-shown': '.group:placeholder-shown &',
233
+ 'group-autofill': '.group:autofill &',
234
+ 'group-read-only': '.group:read-only &',
235
+ 'group-read-write': '.group:read-write &',
236
+ 'group-optional': '.group:optional &',
237
+ 'group-user-valid': '.group:user-valid &',
238
+ 'group-user-invalid': '.group:user-invalid &',
239
+
240
+ // Peer variants
241
+ 'peer': '.peer ~ &',
242
+ 'peer-hover': '.peer:hover ~ &',
243
+ 'peer-focus': '.peer:focus ~ &',
244
+ 'peer-focus-within': '.peer:focus-within ~ &',
245
+ 'peer-active': '.peer:active ~ &',
246
+ 'peer-disabled': '.peer:disabled ~ &',
247
+ 'peer-visited': '.peer:visited ~ &',
248
+ 'peer-checked': '.peer:checked ~ &',
249
+ 'peer-required': '.peer:required ~ &',
250
+ 'peer-valid': '.peer:valid ~ &',
251
+ 'peer-invalid': '.peer:invalid ~ &',
252
+ 'peer-in-range': '.peer:in-range ~ &',
253
+ 'peer-out-of-range': '.peer:out-of-range ~ &',
254
+ 'peer-placeholder-shown': '.peer:placeholder-shown ~ &',
255
+ 'peer-autofill': '.peer:autofill ~ &',
256
+ 'peer-read-only': '.peer:read-only ~ &',
257
+ 'peer-read-write': '.peer:read-write ~ &',
258
+ 'peer-optional': '.peer:optional ~ &',
259
+ 'peer-user-valid': '.peer:user-valid ~ &',
260
+ 'peer-user-invalid': '.peer:user-invalid &',
261
+
262
+ 'motion-safe': '@media (prefers-reduced-motion: no-preference)',
263
+ 'motion-reduce': '@media (prefers-reduced-motion: reduce)',
264
+ 'print': '@media print',
265
+ 'portrait': '@media (orientation: portrait)',
266
+ 'landscape': '@media (orientation: landscape)',
267
+ 'contrast-more': '@media (prefers-contrast: more)',
268
+ 'contrast-less': '@media (prefers-contrast: less)',
269
+ 'forced-colors': '@media (forced-colors: active)',
270
+ 'rtl': '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)',
271
+ 'ltr': '&:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *)',
272
+ '[dir=rtl]': '[dir="rtl"] &',
273
+ '[dir=ltr]': '[dir="ltr"] &',
274
+ 'pointer-fine': '@media (pointer: fine)',
275
+ 'pointer-coarse': '@media (pointer: coarse)',
276
+ 'pointer-none': '@media (pointer: none)',
277
+ 'any-pointer-fine': '@media (any-pointer: fine)',
278
+ 'any-pointer-coarse': '@media (any-pointer: coarse)',
279
+ 'any-pointer-none': '@media (any-pointer: none)',
280
+ 'scripting-enabled': '@media (scripting: enabled)',
281
+ 'can-hover': '@media (hover: hover)',
282
+ 'can-not-hover': '@media (hover: none)',
283
+ 'any-hover': '@media (any-hover: hover)',
284
+ 'any-hover-none': '@media (any-hover: none)',
285
+ 'any-pointer': '@media (any-pointer: fine)',
286
+ 'any-pointer-coarse': '@media (any-pointer: coarse)',
287
+ 'any-pointer-none': '@media (any-pointer: none)',
288
+ 'color': '@media (color)',
289
+ 'color-gamut': '@media (color-gamut: srgb)',
290
+ 'color-gamut-p3': '@media (color-gamut: p3)',
291
+ 'color-gamut-rec2020': '@media (color-gamut: rec2020)',
292
+ 'monochrome': '@media (monochrome)',
293
+ 'monochrome-color': '@media (monochrome: 0)',
294
+ 'monochrome-grayscale': '@media (monochrome: 1)',
295
+ 'inverted-colors': '@media (inverted-colors: inverted)',
296
+ 'inverted-colors-none': '@media (inverted-colors: none)',
297
+ 'update': '@media (update: fast)',
298
+ 'update-slow': '@media (update: slow)',
299
+ 'update-none': '@media (update: none)',
300
+ 'overflow-block': '@media (overflow-block: scroll)',
301
+ 'overflow-block-paged': '@media (overflow-block: paged)',
302
+ 'overflow-inline': '@media (overflow-inline: scroll)',
303
+ 'overflow-inline-auto': '@media (overflow-inline: auto)',
304
+ 'prefers-color-scheme': '@media (prefers-color-scheme: dark)',
305
+ 'prefers-color-scheme-light': '@media (prefers-color-scheme: light)',
306
+ 'prefers-contrast': '@media (prefers-contrast: more)',
307
+ 'prefers-contrast-less': '@media (prefers-contrast: less)',
308
+ 'prefers-contrast-no-preference': '@media (prefers-contrast: no-preference)',
309
+ 'prefers-reduced-motion': '@media (prefers-reduced-motion: reduce)',
310
+ 'prefers-reduced-motion-no-preference': '@media (prefers-reduced-motion: no-preference)',
311
+ 'prefers-reduced-transparency': '@media (prefers-reduced-transparency: reduce)',
312
+ 'prefers-reduced-transparency-no-preference': '@media (prefers-reduced-transparency: no-preference)',
313
+ 'resolution': '@media (resolution: 1dppx)',
314
+ 'resolution-low': '@media (resolution: 1dppx)',
315
+ 'resolution-high': '@media (resolution: 2dppx)',
316
+ 'scan': '@media (scan: progressive)',
317
+ 'scan-interlace': '@media (scan: interlace)',
318
+ 'scripting': '@media (scripting: enabled)',
319
+ 'scripting-none': '@media (scripting: none)',
320
+ 'scripting-initial-only': '@media (scripting: initial-only)',
321
+
322
+ // Container queries
323
+ 'container': '@container',
324
+ 'container-name': '@container',
325
+
326
+ // Important modifier
327
+ '!': '!important',
328
+
329
+ // Responsive breakpoints
330
+ 'sm': '@media (min-width: 640px)',
331
+ 'md': '@media (min-width: 768px)',
332
+ 'lg': '@media (min-width: 1024px)',
333
+ 'xl': '@media (min-width: 1280px)',
334
+ '2xl': '@media (min-width: 1536px)',
335
+
336
+ // Supports queries
337
+ 'supports': '@supports',
338
+
339
+ // Starting style
340
+ 'starting': '@starting-style',
341
+
342
+ // Device / input modality (breakpoint-independent, pure media queries)
343
+ 'touch': '@media (pointer: coarse)',
344
+ 'cursor': '@media (pointer: fine) and (hover: hover)',
345
+ 'pointer': '@media (any-pointer: fine)',
346
+
347
+ // Operating system (html[data-os] is set by the utilities plugin)
348
+ 'mac': 'html[data-os="macos"] &',
349
+ 'windows': 'html[data-os="windows"] &',
350
+ 'linux': 'html[data-os="linux"] &',
351
+ 'ios': 'html[data-os="ios"] &',
352
+ 'android': 'html[data-os="android"] &',
353
+ 'apple': 'html[data-os="macos"] &, html[data-os="ios"] &',
354
+
355
+ // Data attribute variants (common patterns)
356
+ 'data-open': '[data-state="open"] &',
357
+ 'data-closed': '[data-state="closed"] &',
358
+ 'data-checked': '[data-state="checked"] &',
359
+ 'data-unchecked': '[data-state="unchecked"] &',
360
+ 'data-on': '[data-state="on"] &',
361
+ 'data-off': '[data-state="off"] &',
362
+ 'data-visible': '[data-state="visible"] &',
363
+ 'data-hidden': '[data-state="hidden"] &',
364
+ 'data-disabled': '[data-disabled] &',
365
+ 'data-loading': '[data-loading] &',
366
+ 'data-error': '[data-error] &',
367
+ 'data-success': '[data-success] &',
368
+ 'data-warning': '[data-warning] &',
369
+ 'data-selected': '[data-selected] &',
370
+ 'data-highlighted': '[data-highlighted] &',
371
+ 'data-pressed': '[data-pressed] &',
372
+ 'data-expanded': '[data-expanded] &',
373
+ 'data-collapsed': '[data-collapsed] &',
374
+ 'data-active': '[data-active] &',
375
+ 'data-inactive': '[data-inactive] &',
376
+ 'data-valid': '[data-valid] &',
377
+ 'data-invalid': '[data-invalid] &',
378
+ 'data-required': '[data-required] &',
379
+ 'data-optional': '[data-optional] &',
380
+ 'data-readonly': '[data-readonly] &',
381
+ 'data-write': '[data-write] &',
382
+
383
+ // Aria attribute variants (common patterns)
384
+ 'aria-expanded': '[aria-expanded="true"] &',
385
+ 'aria-collapsed': '[aria-expanded="false"] &',
386
+ 'aria-pressed': '[aria-pressed="true"] &',
387
+ 'aria-unpressed': '[aria-pressed="false"] &',
388
+ 'aria-checked': '[aria-checked="true"] &',
389
+ 'aria-unchecked': '[aria-checked="false"] &',
390
+ 'aria-selected': '[aria-selected="true"] &',
391
+ 'aria-unselected': '[aria-selected="false"] &',
392
+ 'aria-invalid': '[aria-invalid="true"] &',
393
+ 'aria-valid': '[aria-invalid="false"] &',
394
+ 'aria-required': '[aria-required="true"] &',
395
+ 'aria-optional': '[aria-required="false"] &',
396
+ 'aria-disabled': '[aria-disabled="true"] &',
397
+ 'aria-enabled': '[aria-disabled="false"] &',
398
+ 'aria-hidden': '[aria-hidden="true"] &',
399
+ 'aria-visible': '[aria-hidden="false"] &',
400
+ 'aria-busy': '[aria-busy="true"] &',
401
+ 'aria-available': '[aria-busy="false"] &',
402
+ 'aria-current': '[aria-current="true"] &',
403
+ 'aria-not-current': '[aria-current="false"] &',
404
+ 'aria-live': '[aria-live="polite"] &, [aria-live="assertive"] &',
405
+ 'aria-atomic': '[aria-atomic="true"] &',
406
+ 'aria-relevant': '[aria-relevant="additions"] &, [aria-relevant="removals"] &, [aria-relevant="text"] &, [aria-relevant="all"] &'
407
+ };
408
+ }
409
+
410
+ function createVariantGroups() {
411
+ return {
412
+ 'state': ['hover', 'focus', 'active', 'visited', 'target', 'open', 'closed', 'paused', 'playing', 'muted', 'unmuted', 'collapsed', 'expanded', 'unchecked', 'selected', 'unselected'],
413
+ 'child': ['first', 'last', 'only', 'odd', 'even'],
414
+ 'form': ['disabled', 'enabled', 'checked', 'indeterminate', 'required', 'valid', 'invalid'],
415
+ 'pseudo': ['before', 'after', 'first-letter', 'first-line', 'marker', 'selection', 'file', 'backdrop'],
416
+ 'media': ['dark', 'light', 'motion-safe', 'motion-reduce', 'print', 'portrait', 'landscape', 'rtl', 'ltr', 'can-hover', 'can-not-hover', 'any-hover', 'any-hover-none', 'color', 'monochrome', 'inverted-colors', 'inverted-colors-none', 'update', 'update-slow', 'update-none', 'overflow-block', 'overflow-block-paged', 'overflow-inline', 'overflow-inline-auto', 'prefers-color-scheme', 'prefers-color-scheme-light', 'prefers-contrast', 'prefers-contrast-less', 'prefers-contrast-no-preference', 'prefers-reduced-motion', 'prefers-reduced-motion-no-preference', 'prefers-reduced-transparency', 'prefers-reduced-transparency-no-preference', 'resolution', 'resolution-low', 'resolution-high', 'scan', 'scan-interlace', 'scripting', 'scripting-none', 'scripting-initial-only', 'forced-colors', 'contrast-more', 'contrast-less', 'pointer-fine', 'pointer-coarse', 'pointer-none', 'any-pointer-fine', 'any-pointer-coarse', 'any-pointer-none', 'scripting-enabled'],
417
+ 'responsive': ['sm', 'md', 'lg', 'xl', '2xl'],
418
+ 'device': ['touch', 'cursor', 'pointer'],
419
+ 'os': ['mac', 'windows', 'linux', 'ios', 'android', 'apple'],
420
+ 'group': ['group', 'group-hover', 'group-focus', 'group-active', 'group-disabled', 'group-checked', 'group-required', 'group-valid', 'group-invalid'],
421
+ 'peer': ['peer', 'peer-hover', 'peer-focus', 'peer-active', 'peer-disabled', 'peer-checked', 'peer-required', 'peer-valid', 'peer-invalid'],
422
+ 'data': ['data-open', 'data-closed', 'data-checked', 'data-unchecked', 'data-visible', 'data-hidden', 'data-disabled', 'data-loading', 'data-error', 'data-success', 'data-warning', 'data-selected', 'data-highlighted', 'data-pressed', 'data-expanded', 'data-collapsed', 'data-active', 'data-inactive', 'data-valid', 'data-invalid', 'data-required', 'data-optional', 'data-readonly', 'data-write'],
423
+ 'aria': ['aria-expanded', 'aria-collapsed', 'aria-pressed', 'aria-unpressed', 'aria-checked', 'aria-unchecked', 'aria-selected', 'aria-unselected', 'aria-invalid', 'aria-valid', 'aria-required', 'aria-optional', 'aria-disabled', 'aria-enabled', 'aria-hidden', 'aria-visible', 'aria-busy', 'aria-available', 'aria-current', 'aria-not-current', 'aria-live', 'aria-atomic', 'aria-relevant']
424
+ };
425
+ }
426
+
427
+
428
+
429
+ /* Manifest Utilities */
430
+
431
+ /** True when prerender wrote utilities to prerender.utilities.css — skip runtime #manifest-styles generation. */
432
+ function manifestPageUsesStaticPrerenderUtilities() {
433
+ if (typeof document === 'undefined') return false;
434
+ try {
435
+ if (!document.querySelector('meta[name="manifest:prerendered"][content="1"]')) return false;
436
+ for (const link of document.querySelectorAll('link[rel="stylesheet"][href]')) {
437
+ if ((link.getAttribute('href') || '').toLowerCase().includes('prerender.utilities.css')) {
438
+ return true;
439
+ }
440
+ }
441
+ } catch (e) {
442
+ return false;
443
+ }
444
+ return false;
445
+ }
446
+
447
+ // Browser runtime compiler
448
+ class TailwindCompiler {
449
+ constructor(options = {}) {
450
+ this.usesStaticPrerenderUtilities = manifestPageUsesStaticPrerenderUtilities();
451
+
452
+ // Prerender already emitted utility CSS; do not inject duplicate #manifest-styles / observers.
453
+ if (this.usesStaticPrerenderUtilities) {
454
+ this.debug = options.debug === true;
455
+ this.startTime = performance.now();
456
+ this.options = {
457
+ rootSelector: options.rootSelector || ':root',
458
+ themeSelector: options.themeSelector || '@theme',
459
+ debounceTime: options.debounceTime || 50,
460
+ maxCacheAge: options.maxCacheAge || 24 * 60 * 60 * 1000,
461
+ debug: options.debug !== false,
462
+ ...options
463
+ };
464
+ this.criticalStyleElement = null;
465
+ this.styleElement = null;
466
+ this.tailwindLink = null;
467
+ this.observer = null;
468
+ this.isCompiling = false;
469
+ this.compileTimeout = null;
470
+ this.cache = new Map();
471
+ this.hasInitialized = true;
472
+ this.staticUtilitiesCoveredClasses = null;
473
+ // manifest.code.js (and others) may still register ignore rules; mirror full constructor defaults.
474
+ this.ignoredClassPatterns = [
475
+ /^hljs/, /^language-/, /^copy$/, /^copied$/, /^lines$/, /^selected$/
476
+ ];
477
+ this.ignoredElementSelectors = [
478
+ 'pre', 'code', 'x-code', 'x-code-group'
479
+ ];
480
+ this.significantChangeSelectors = [
481
+ '[data-component]', '[x-data]'
482
+ ];
483
+ return;
484
+ }
485
+
486
+ this.debug = options.debug === true;
487
+ this.startTime = performance.now();
488
+
489
+ // Create critical style element FIRST - must be before any rendering
490
+ const criticalStyleStart = performance.now();
491
+ this.criticalStyleElement = document.createElement('style');
492
+ this.criticalStyleElement.id = 'manifest-styles-critical';
493
+ // Insert at the very beginning of head
494
+ if (document.head) {
495
+ if (document.head.firstChild) {
496
+ document.head.insertBefore(this.criticalStyleElement, document.head.firstChild);
497
+ } else {
498
+ document.head.appendChild(this.criticalStyleElement);
499
+ }
500
+ } else {
501
+ // If head doesn't exist yet, wait for it (shouldn't happen, but safety check)
502
+ const checkHead = setInterval(() => {
503
+ if (document.head) {
504
+ clearInterval(checkHead);
505
+ if (document.head.firstChild) {
506
+ document.head.insertBefore(this.criticalStyleElement, document.head.firstChild);
507
+ } else {
508
+ document.head.appendChild(this.criticalStyleElement);
509
+ }
510
+ }
511
+ }, 1);
512
+ }
513
+
514
+ // Initialize options first (needed for regex patterns)
515
+ this.options = {
516
+ rootSelector: options.rootSelector || ':root',
517
+ themeSelector: options.themeSelector || '@theme',
518
+ debounceTime: options.debounceTime || 50,
519
+ maxCacheAge: options.maxCacheAge || 24 * 60 * 60 * 1000,
520
+ debug: options.debug !== false,
521
+ ...options
522
+ };
523
+
524
+ // Initialize regex patterns (needed for utility generation)
525
+ this.regexPatterns = {
526
+ root: new RegExp(`${this.options.rootSelector}\\s*{([^}]*)}`, 'g'),
527
+ theme: new RegExp(`${this.options.themeSelector}\\s*{([^}]*)}`, 'g'),
528
+ variable: /--([\w-]+):\s*([^;]+);/g,
529
+ tailwindPrefix: /^(color|font|text|font-weight|tracking|leading|breakpoint|container|spacing|radius|shadow|inset-shadow|drop-shadow|blur|perspective|aspect|ease|animate|border-width|border-style|outline|outline-width|outline-style|ring|ring-offset|divide|accent|caret|decoration|placeholder|selection|scrollbar)-/
530
+ };
531
+
532
+ // Initialize utility generators from component
533
+ const generators = createUtilityGenerators();
534
+ // Start with minimal generators needed for synchronous generation
535
+ this.utilityGenerators = {
536
+ 'color-': generators['color-'],
537
+ 'font-': generators['font-'],
538
+ 'text-': generators['text-'],
539
+ 'font-weight-': generators['font-weight-'],
540
+ 'tracking-': generators['tracking-'],
541
+ 'leading-': generators['leading-'],
542
+ 'spacing-': generators['spacing-'],
543
+ 'radius-': generators['radius-'],
544
+ 'shadow-': generators['shadow-'],
545
+ 'blur-': generators['blur-']
546
+ };
547
+ // Add remaining generators
548
+ Object.assign(this.utilityGenerators, generators);
549
+
550
+ // Initialize variants from component (needed for parseClassName during sync generation)
551
+ this.variants = createVariants();
552
+ this.variantGroups = createVariantGroups();
553
+
554
+ // Cache for parsed class names (must be before addCriticalBlockingStylesSync)
555
+ this.classCache = new Map();
556
+
557
+ // Read any publish/render-provided static utilities sheet before the
558
+ // first compile — its classes are skipped rather than regenerated.
559
+ this.staticUtilitiesCoveredClasses = null;
560
+ this.detectStaticUtilitiesSheet();
561
+
562
+ // Add critical styles IMMEDIATELY - don't wait for anything
563
+ this.addCriticalBlockingStylesSync();
564
+
565
+ // Create main style element for generated utilities
566
+ this.styleElement = document.createElement('style');
567
+ this.styleElement.id = 'manifest-styles';
568
+ document.head.appendChild(this.styleElement);
569
+ this.setupUtilityStylesOrderObserver();
570
+
571
+ // Initialize properties
572
+ this.tailwindLink = null;
573
+ this.observer = null;
574
+ this.isCompiling = false;
575
+ this.compileTimeout = null;
576
+ this.cache = new Map();
577
+ this.lastThemeHash = null;
578
+ this.processedElements = new WeakSet();
579
+ this.activeBreakpoints = new Set();
580
+ this.activeModifiers = new Set();
581
+ this.cssFiles = new Set();
582
+ this.pendingStyles = new Map();
583
+ this.currentThemeVars = new Map();
584
+ this.hasInitialized = false;
585
+ this.lastCompileTime = 0;
586
+ this.minCompileInterval = 100; // Minimum time between compilations in ms
587
+ this.cssContentCache = new Map(); // Cache CSS file contents with timestamps
588
+ this.lastClassesHash = ''; // Track changes in used classes
589
+ this.staticClassCache = new Set(); // Cache classes found in static HTML/components
590
+ this.dynamicClassCache = new Set(); // Cache classes that appear dynamically
591
+ this.hasScannedStatic = false; // Track if we've done initial static scan
592
+ this.staticScanPromise = null; // Promise for initial static scan
593
+ this.ignoredClassPatterns = [ // Patterns for classes to ignore
594
+ /^hljs/, /^language-/, /^copy$/, /^copied$/, /^lines$/, /^selected$/
595
+ ];
596
+ this.ignoredElementSelectors = [ // Elements to ignore for DOM mutations
597
+ 'pre', 'code', 'x-code', 'x-code-group'
598
+ ];
599
+ this.ignoredAttributes = [ // Attribute changes to ignore (non-visual/utility changes)
600
+ 'id', 'data-order', 'data-component-id', 'data-highlighted', 'data-processed',
601
+ 'x-intersect', 'x-intersect:leave', 'x-show', 'x-hide', 'x-transition',
602
+ 'aria-expanded', 'aria-selected', 'aria-current', 'aria-hidden', 'aria-label',
603
+ 'tabindex', 'role', 'title', 'alt', 'data-state', 'data-value'
604
+ ];
605
+ this.significantChangeSelectors = [ // Only these DOM additions trigger recompilation
606
+ '[data-component]', '[x-data]' // Components and Alpine elements
607
+ ];
608
+
609
+ // Pre-define pseudo classes
610
+ this.pseudoClasses = ['hover', 'focus', 'active', 'disabled', 'dark'];
611
+
612
+ // Cache for discovered custom utility classes
613
+ this.customUtilities = new Map();
614
+
615
+ // Variants and classCache already initialized above (before addCriticalBlockingStylesSync)
616
+
617
+ // Load cache and start processing
618
+ this.loadAndApplyCache();
619
+
620
+ // If cache loaded utilities, they'll be in the main style element
621
+ // The critical style element will be cleared when full utilities are ready
622
+
623
+ // Try to generate minimal utilities synchronously from inline styles
624
+ this.generateSynchronousUtilities();
625
+
626
+ // Listen for component loads
627
+ this.setupComponentLoadListener();
628
+
629
+ this.waitForTailwind().then(() => {
630
+ this.startProcessing();
631
+ });
632
+ }
633
+
634
+ // Public API for other plugins to configure behavior
635
+ addIgnoredClassPattern(pattern) {
636
+ if (!Array.isArray(this.ignoredClassPatterns)) {
637
+ this.ignoredClassPatterns = [];
638
+ }
639
+ if (pattern instanceof RegExp) {
640
+ this.ignoredClassPatterns.push(pattern);
641
+ } else if (typeof pattern === 'string') {
642
+ this.ignoredClassPatterns.push(new RegExp(pattern));
643
+ }
644
+ }
645
+
646
+ addIgnoredElementSelector(selector) {
647
+ if (!Array.isArray(this.ignoredElementSelectors)) {
648
+ this.ignoredElementSelectors = [];
649
+ }
650
+ if (typeof selector === 'string') {
651
+ this.ignoredElementSelectors.push(selector);
652
+ }
653
+ }
654
+
655
+ addSignificantChangeSelector(selector) {
656
+ if (!Array.isArray(this.significantChangeSelectors)) {
657
+ this.significantChangeSelectors = [];
658
+ }
659
+ if (typeof selector === 'string') {
660
+ this.significantChangeSelectors.push(selector);
661
+ }
662
+ }
663
+
664
+ // Allow plugins to trigger recompilation when needed
665
+ triggerRecompilation(reason = 'manual') {
666
+ this.compile();
667
+ }
668
+
669
+ // Debounce utility
670
+ debounce(func, wait) {
671
+ let timeout;
672
+ return function executedFunction(...args) {
673
+ const later = () => {
674
+ clearTimeout(timeout);
675
+ func(...args);
676
+ };
677
+ clearTimeout(timeout);
678
+ timeout = setTimeout(later, wait);
679
+ };
680
+ }
681
+
682
+ // Wait for Tailwind to be available
683
+ async waitForTailwind() {
684
+ return new Promise((resolve) => {
685
+ if (this.isTailwindAvailable()) {
686
+ resolve();
687
+ return;
688
+ }
689
+
690
+ const checkInterval = setInterval(() => {
691
+ if (this.isTailwindAvailable()) {
692
+ clearInterval(checkInterval);
693
+ resolve();
694
+ }
695
+ }, 100);
696
+
697
+ // Also check on DOMContentLoaded
698
+ document.addEventListener('DOMContentLoaded', () => {
699
+ if (this.isTailwindAvailable()) {
700
+ clearInterval(checkInterval);
701
+ resolve();
702
+ }
703
+ });
704
+
705
+ // Set a timeout to prevent infinite waiting
706
+ setTimeout(() => {
707
+ clearInterval(checkInterval);
708
+ resolve();
709
+ }, 5000);
710
+ });
711
+ }
712
+
713
+ // Check if Tailwind is available
714
+ isTailwindAvailable() {
715
+ // Check for Tailwind in various ways
716
+ return (
717
+ // Check for Tailwind CSS file
718
+ Array.from(document.styleSheets).some(sheet =>
719
+ sheet.href && (
720
+ sheet.href.includes('tailwind') ||
721
+ sheet.href.includes('tailwindcss') ||
722
+ sheet.href.includes('manifest')
723
+ )
724
+ ) ||
725
+ // Check for Tailwind classes in document
726
+ document.querySelector('[class*="tailwind"]') ||
727
+ // Check for Tailwind in window object
728
+ window.tailwind ||
729
+ // Check for Tailwind in document head
730
+ document.head.innerHTML.includes('tailwind') ||
731
+ // Check for Manifest CSS files
732
+ document.head.innerHTML.includes('manifest')
733
+ );
734
+ }
735
+ }
736
+
737
+
738
+
739
+ // Helper methods
740
+ // Utility functions for extracting, parsing, and processing CSS and classes
741
+
742
+ // Ensure #manifest-styles is last in head so our responsive/variant rules win over Tailwind and any later-injected styles
743
+ TailwindCompiler.prototype.ensureUtilityStylesLast = function () {
744
+ if (this.styleElement && this.styleElement.parentNode && document.head.lastElementChild !== this.styleElement) {
745
+ document.head.appendChild(this.styleElement);
746
+ }
747
+ };
748
+
749
+ // When any element is added to head after ours, move #manifest-styles to end. Handles CDN load order (e.g. Tailwind injecting after we run).
750
+ TailwindCompiler.prototype.setupUtilityStylesOrderObserver = function () {
751
+ if (!document.head || !this.styleElement) return;
752
+ const self = this;
753
+ const observer = new MutationObserver((mutations) => {
754
+ for (const mutation of mutations) {
755
+ if (mutation.type !== 'childList' || !mutation.addedNodes.length) continue;
756
+ for (const node of mutation.addedNodes) {
757
+ if (node.nodeType !== Node.ELEMENT_NODE || node === self.styleElement) continue;
758
+ self.ensureUtilityStylesLast();
759
+ break;
760
+ }
761
+ }
762
+ });
763
+ observer.observe(document.head, { childList: true, subtree: false });
764
+ };
765
+
766
+ // Schedule ensureUtilityStylesLast at 0ms, 100ms, 500ms so we win when Tailwind (or other scripts) inject styles later (e.g. CDN).
767
+ TailwindCompiler.prototype.scheduleEnsureUtilityStylesLast = function () {
768
+ const self = this;
769
+ [0, 100, 500].forEach((ms) => {
770
+ setTimeout(() => self.ensureUtilityStylesLast(), ms);
771
+ });
772
+ };
773
+
774
+ // Discover CSS files from stylesheets and imports
775
+ TailwindCompiler.prototype.discoverCssFiles = function () {
776
+ try {
777
+ // Get all stylesheets from the document
778
+ const stylesheets = Array.from(document.styleSheets);
779
+
780
+ // Process each stylesheet
781
+ for (const sheet of stylesheets) {
782
+ try {
783
+ // Process local files and framework CDN files (manifestjs, mnfst)
784
+ const isFrameworkCdn = (href) =>
785
+ href.includes('manifestjs') || href.includes('mnfst') ||
786
+ (href.includes('jsdelivr') && (href.includes('manifestjs') || href.includes('mnfst'))) ||
787
+ (href.includes('unpkg') && (href.includes('manifestjs') || href.includes('mnfst')));
788
+ if (sheet.href && (
789
+ sheet.href.startsWith(window.location.origin) ||
790
+ isFrameworkCdn(sheet.href)
791
+ )) {
792
+ this.cssFiles.add(sheet.href);
793
+ }
794
+
795
+ // Get all @import rules (local and framework CDN)
796
+ const rules = Array.from(sheet.cssRules || []);
797
+ for (const rule of rules) {
798
+ if (rule.type === CSSRule.IMPORT_RULE && rule.href && (
799
+ rule.href.startsWith(window.location.origin) ||
800
+ isFrameworkCdn(rule.href)
801
+ )) {
802
+ this.cssFiles.add(rule.href);
803
+ }
804
+ }
805
+ } catch (e) {
806
+ // Skip stylesheets that can't be accessed (external CDN files, CORS, etc.)
807
+ }
808
+ }
809
+
810
+ // Add any inline styles (exclude generated styles)
811
+ const styleElements = document.querySelectorAll('style:not(#manifest-styles)');
812
+ for (const style of styleElements) {
813
+ if (style.textContent && style.textContent.trim()) {
814
+ const id = style.id || `inline-style-${Array.from(styleElements).indexOf(style)}`;
815
+ this.cssFiles.add('inline:' + id);
816
+ }
817
+ }
818
+ } catch (error) {
819
+ console.warn('Error discovering CSS files:', error);
820
+ }
821
+ };
822
+
823
+ // Scan static HTML files and components for classes
824
+ TailwindCompiler.prototype.scanStaticClasses = async function () {
825
+ if (this.staticScanPromise) {
826
+ return this.staticScanPromise;
827
+ }
828
+
829
+ this.staticScanPromise = (async () => {
830
+ try {
831
+ const staticClasses = new Set();
832
+
833
+ // 1. Scan index.html content
834
+ const htmlContent = document.documentElement.outerHTML;
835
+ this.extractClassesFromHTML(htmlContent, staticClasses);
836
+
837
+ // 2. Scan component files from manifest
838
+ // Wait for components registry to be ready (with timeout)
839
+ let registry = window.ManifestComponentsRegistry;
840
+ const maxWaitTime = 2000; // Wait up to 2 seconds for registry
841
+ const checkInterval = 50;
842
+ let waited = 0;
843
+
844
+ while (!registry && waited < maxWaitTime) {
845
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
846
+ registry = window.ManifestComponentsRegistry;
847
+ waited += checkInterval;
848
+ }
849
+
850
+ const componentUrls = [];
851
+
852
+ if (registry && registry.manifest) {
853
+ // Get all component paths from manifest
854
+ const allComponents = [
855
+ ...(registry.manifest.preloadedComponents || []),
856
+ ...(registry.manifest.components || [])
857
+ ];
858
+ componentUrls.push(...allComponents);
859
+ }
860
+
861
+ const componentPromises = componentUrls.map(async (url) => {
862
+ try {
863
+ // Use root-relative URL first to avoid 404s on doc routes (e.g. /getting-started/...)
864
+ const rootRelative = url.startsWith('/') ? url : '/' + url;
865
+ const pathsToTry = [rootRelative, url];
866
+ for (const path of pathsToTry) {
867
+ try {
868
+ const response = await fetch(path);
869
+ if (response.ok) {
870
+ const html = await response.text();
871
+ this.extractClassesFromHTML(html, staticClasses);
872
+ break;
873
+ }
874
+ } catch (e) {
875
+ continue;
876
+ }
877
+ }
878
+ } catch (error) {
879
+ // Silently ignore missing components
880
+ }
881
+ });
882
+
883
+ await Promise.all(componentPromises);
884
+
885
+ // Cache static classes
886
+ for (const cls of staticClasses) {
887
+ this.staticClassCache.add(cls);
888
+ }
889
+
890
+ this.hasScannedStatic = true;
891
+
892
+ return staticClasses;
893
+ } catch (error) {
894
+ console.warn('[TailwindCompiler] Error scanning static classes:', error);
895
+ this.hasScannedStatic = true;
896
+ return new Set();
897
+ }
898
+ })();
899
+
900
+ return this.staticScanPromise;
901
+ };
902
+
903
+ // Extract classes from HTML content
904
+ TailwindCompiler.prototype.extractClassesFromHTML = function (html, classSet) {
905
+ // Plain class attributes only — the leading boundary excludes Alpine's
906
+ // :class / x-bind:class bindings (JS expressions, not token lists)
907
+ const classRegex = /(?:^|[\s"'`>/])(?:[a-zA-Z][\w.-]*-)?class=["']([^"']+)["']/g;
908
+ let match;
909
+
910
+ while ((match = classRegex.exec(html)) !== null) {
911
+ const classString = match[1];
912
+ const classes = classString.split(/\s+/).filter(Boolean);
913
+ for (const cls of classes) {
914
+ if (cls && !cls.startsWith('x-') && !cls.startsWith('$')) {
915
+ classSet.add(cls);
916
+ }
917
+ }
918
+ }
919
+
920
+ // Also check for x-data and other Alpine directives that might contain classes
921
+ const alpineRegex = /x-(?:data|bind:class|class)=["']([^"']+)["']/g;
922
+ while ((match = alpineRegex.exec(html)) !== null) {
923
+ // Simple extraction - could be enhanced for complex Alpine expressions
924
+ const content = match[1];
925
+ const classMatches = content.match(/['"`]([^'"`\s]+)['"`]/g);
926
+ if (classMatches) {
927
+ for (const classMatch of classMatches) {
928
+ const cls = classMatch.replace(/['"`]/g, '');
929
+ if (cls && !cls.startsWith('$') && !cls.includes('(')) {
930
+ classSet.add(cls);
931
+ }
932
+ }
933
+ }
934
+ }
935
+ };
936
+
937
+ // Get all used classes from static and dynamic sources
938
+ TailwindCompiler.prototype.getUsedClasses = function () {
939
+ try {
940
+ const allClasses = new Set();
941
+ const usedVariableSuffixes = new Set();
942
+
943
+ // Add static classes (pre-scanned)
944
+ for (const cls of this.staticClassCache) {
945
+ allClasses.add(cls);
946
+ }
947
+
948
+ // Scan current DOM for dynamic classes only
949
+ const elements = document.getElementsByTagName('*');
950
+ for (const element of elements) {
951
+ let classes = [];
952
+ if (typeof element.className === 'string') {
953
+ classes = element.className.split(/\s+/).filter(Boolean);
954
+ } else if (element.classList) {
955
+ classes = Array.from(element.classList);
956
+ }
957
+
958
+ for (const cls of classes) {
959
+ if (!cls) continue;
960
+
961
+ // Skip classes using configurable patterns
962
+ const isIgnoredClass = this.ignoredClassPatterns.some(pattern =>
963
+ pattern.test(cls)
964
+ );
965
+
966
+ if (isIgnoredClass) {
967
+ continue;
968
+ }
969
+
970
+ // Add all classes (static + dynamic)
971
+ allClasses.add(cls);
972
+
973
+ // Track dynamic classes separately
974
+ if (!this.staticClassCache.has(cls)) {
975
+ this.dynamicClassCache.add(cls);
976
+ }
977
+ }
978
+ }
979
+
980
+ // Process all classes for variable suffixes
981
+ for (const cls of allClasses) {
982
+ // Extract base class and variants
983
+ const parts = cls.split(':');
984
+ const baseClass = parts[parts.length - 1];
985
+
986
+ // Extract suffix for variable matching
987
+ const classParts = baseClass.split('-');
988
+ if (classParts.length > 1) {
989
+ let suffix = classParts.slice(1).join('-');
990
+
991
+ // Handle opacity modifiers (like /90, /50)
992
+ let baseSuffix = suffix;
993
+ if (suffix.includes('/')) {
994
+ const parts = suffix.split('/');
995
+ baseSuffix = parts[0];
996
+ const opacity = parts[1];
997
+
998
+ // Add both the base suffix and the full suffix with opacity
999
+ usedVariableSuffixes.add(baseSuffix);
1000
+ usedVariableSuffixes.add(suffix); // Keep the full suffix with opacity
1001
+ } else {
1002
+ usedVariableSuffixes.add(suffix);
1003
+ }
1004
+
1005
+ // For compound classes like text-content-subtle, also add the full suffix
1006
+ if (classParts.length > 2) {
1007
+ const fullSuffix = classParts.slice(1).join('-');
1008
+ if (fullSuffix.includes('/')) {
1009
+ usedVariableSuffixes.add(fullSuffix.split('/')[0]);
1010
+ } else {
1011
+ usedVariableSuffixes.add(fullSuffix);
1012
+ }
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
+ const result = {
1018
+ classes: this.filterStaticallyCoveredClasses(Array.from(allClasses)),
1019
+ variableSuffixes: Array.from(usedVariableSuffixes)
1020
+ };
1021
+
1022
+ return result;
1023
+ } catch (error) {
1024
+ console.error('Error getting used classes:', error);
1025
+ return { classes: [], variableSuffixes: [] };
1026
+ }
1027
+ };
1028
+
1029
+ // Fetch theme content from CSS files
1030
+ TailwindCompiler.prototype.fetchThemeContent = async function () {
1031
+ const themeContents = new Set();
1032
+ const fetchPromises = [];
1033
+
1034
+ // If we haven't discovered CSS files yet, do it now
1035
+ if (this.cssFiles.size === 0) {
1036
+ this.discoverCssFiles();
1037
+ }
1038
+
1039
+ // Process all files concurrently
1040
+ for (const source of this.cssFiles) {
1041
+ const fetchPromise = (async () => {
1042
+ try {
1043
+ let content = '';
1044
+ let needsFetch = true;
1045
+
1046
+ if (source.startsWith('inline:')) {
1047
+ const styleId = source.replace('inline:', '');
1048
+ const styleElement = styleId ?
1049
+ document.getElementById(styleId) :
1050
+ document.querySelector('style');
1051
+ if (styleElement) {
1052
+ content = styleElement.textContent;
1053
+ }
1054
+ needsFetch = false;
1055
+ } else {
1056
+ // Smart caching: use session storage + timestamp approach
1057
+ const cacheKey = source;
1058
+ const cached = this.cssContentCache.get(cacheKey);
1059
+ const now = Date.now();
1060
+
1061
+ // Different cache times based on file source
1062
+ let cacheTime;
1063
+ if (source.includes('manifestjs') || source.includes('mnfst') || source.includes('jsdelivr') || source.includes('unpkg')) {
1064
+ // CDN files: cache longer (5 minutes for static, 1 minute for dynamic)
1065
+ cacheTime = this.hasScannedStatic ? 60000 : 300000;
1066
+ } else {
1067
+ // Local files: shorter cache (5 seconds for dynamic, 30 seconds for static)
1068
+ cacheTime = this.hasScannedStatic ? 5000 : 30000;
1069
+ }
1070
+
1071
+ if (cached && (now - cached.timestamp) < cacheTime) {
1072
+ content = cached.content;
1073
+ needsFetch = false;
1074
+ }
1075
+
1076
+ if (needsFetch) {
1077
+ // Add timestamp for development cache busting, but keep it minimal
1078
+ const timestamp = Math.floor(now / 1000); // Only changes every second
1079
+ const url = `${source}${source.includes('?') ? '&' : '?'}t=${timestamp}`;
1080
+
1081
+ const response = await fetch(url);
1082
+
1083
+ if (!response.ok) {
1084
+ console.warn('Failed to fetch stylesheet:', url);
1085
+ return;
1086
+ }
1087
+
1088
+ content = await response.text();
1089
+
1090
+ // Cache the content with timestamp
1091
+ this.cssContentCache.set(cacheKey, {
1092
+ content: content,
1093
+ timestamp: now
1094
+ });
1095
+ }
1096
+ }
1097
+
1098
+ if (content) {
1099
+ themeContents.add(content);
1100
+ }
1101
+ } catch (error) {
1102
+ console.warn(`Error fetching CSS from ${source}:`, error);
1103
+ }
1104
+ })();
1105
+ fetchPromises.push(fetchPromise);
1106
+ }
1107
+
1108
+ // Wait for all fetches to complete
1109
+ await Promise.all(fetchPromises);
1110
+
1111
+ return Array.from(themeContents).join('\n');
1112
+ };
1113
+
1114
+ // Extract CSS variables from CSS text
1115
+ TailwindCompiler.prototype.extractThemeVariables = function (cssText) {
1116
+ const variables = new Map();
1117
+
1118
+ // Extract ALL CSS custom properties from ANY declaration block
1119
+ const varRegex = /--([\w-]+):\s*([^;]+);/g;
1120
+
1121
+ let varMatch;
1122
+ while ((varMatch = varRegex.exec(cssText)) !== null) {
1123
+ const name = varMatch[1];
1124
+ const value = varMatch[2].trim();
1125
+ variables.set(name, value);
1126
+ }
1127
+
1128
+ return variables;
1129
+ };
1130
+
1131
+ // Extract custom utilities from CSS text
1132
+ TailwindCompiler.prototype.extractCustomUtilities = function (cssText) {
1133
+ const utilities = new Map();
1134
+
1135
+ // Helper to ensure CSS is always a string
1136
+ const ensureCssString = (css) => {
1137
+ if (typeof css === 'string') {
1138
+ // Clean up any [object Object] that might have snuck in
1139
+ return css.replace(/\[object Object\](;?\s*)/g, '').trim();
1140
+ }
1141
+ if (css && typeof css === 'object' && css.css) {
1142
+ return ensureCssString(css.css);
1143
+ }
1144
+ const stringified = String(css);
1145
+ // Return empty string instead of [object Object]
1146
+ if (stringified === '[object Object]') {
1147
+ return '';
1148
+ }
1149
+ return stringified;
1150
+ };
1151
+
1152
+ // Extract custom utility classes from CSS
1153
+ // Match: .classname or .!classname (where classname can contain word chars and hyphens)
1154
+ const utilityRegex = /(?:@layer\s+utilities\s*{[^}]*}|^)(?:[^{}]*?)(?:^|\s)(\.!?[\w-]+)\s*{([^}]+)}/gm;
1155
+
1156
+ let match;
1157
+ while ((match = utilityRegex.exec(cssText)) !== null) {
1158
+ const className = match[1].substring(1); // Remove the leading dot
1159
+ const cssRules = match[2].trim();
1160
+
1161
+ // Skip if it's a Tailwind-generated class (check base name without !)
1162
+ const baseClassName = className.startsWith('!') ? className.slice(1) : className;
1163
+ if (this.isTailwindGeneratedClass(baseClassName)) {
1164
+ continue;
1165
+ }
1166
+
1167
+ // Check if CSS rules contain !important
1168
+ const hasImportant = /\s!important/.test(cssRules);
1169
+ const classHasImportantPrefix = className.startsWith('!');
1170
+
1171
+ // Determine final CSS rules
1172
+ let finalCssRules = cssRules;
1173
+ if (classHasImportantPrefix) {
1174
+ // Class has ! prefix, ensure CSS has !important
1175
+ if (!hasImportant) {
1176
+ finalCssRules = cssRules.includes(';') ?
1177
+ cssRules.replace(/;/g, ' !important;') :
1178
+ cssRules + ' !important';
1179
+ }
1180
+ } else {
1181
+ // Class doesn't have ! prefix, remove !important if present
1182
+ if (hasImportant) {
1183
+ finalCssRules = cssRules.replace(/\s!important/g, '');
1184
+ }
1185
+ }
1186
+
1187
+ // Store the utility class with its full name (including ! prefix) as the key
1188
+ // This ensures !col is stored separately from col
1189
+ // Ensure CSS is always a string
1190
+ const cssString = ensureCssString(finalCssRules);
1191
+ if (utilities.has(className)) {
1192
+ const existingRules = utilities.get(className);
1193
+ const existingCssString = ensureCssString(existingRules);
1194
+ utilities.set(className, `${existingCssString}; ${cssString}`);
1195
+ } else {
1196
+ utilities.set(className, cssString);
1197
+ }
1198
+ }
1199
+
1200
+ // Also look for :where() selectors which are common in Manifest utilities
1201
+ // Handle both single class and multiple class selectors
1202
+ // Use a function to properly match braces for nested rules
1203
+ const extractWhereSelectors = (text) => {
1204
+ const matches = [];
1205
+ let i = 0;
1206
+ while (i < text.length) {
1207
+ // Find :where(
1208
+ const whereStart = text.indexOf(':where(', i);
1209
+ if (whereStart === -1) break;
1210
+
1211
+ // Find matching closing paren for :where(
1212
+ let parenDepth = 1;
1213
+ let j = whereStart + 7; // Skip ':where('
1214
+ while (j < text.length && parenDepth > 0) {
1215
+ if (text[j] === '(') parenDepth++;
1216
+ else if (text[j] === ')') parenDepth--;
1217
+ j++;
1218
+ }
1219
+ if (parenDepth > 0) {
1220
+ i = whereStart + 1;
1221
+ continue; // Malformed, skip
1222
+ }
1223
+
1224
+ const selectorContent = text.slice(whereStart + 7, j - 1);
1225
+
1226
+ // Skip whitespace and find opening brace
1227
+ while (j < text.length && /\s/.test(text[j])) j++;
1228
+ if (j >= text.length || text[j] !== '{') {
1229
+ i = whereStart + 1;
1230
+ continue;
1231
+ }
1232
+
1233
+ // Find matching closing brace (handle nested braces)
1234
+ let braceDepth = 1;
1235
+ let blockStart = j + 1;
1236
+ j++;
1237
+ while (j < text.length && braceDepth > 0) {
1238
+ if (text[j] === '{') braceDepth++;
1239
+ else if (text[j] === '}') braceDepth--;
1240
+ j++;
1241
+ }
1242
+ if (braceDepth > 0) {
1243
+ i = whereStart + 1;
1244
+ continue; // Malformed, skip
1245
+ }
1246
+
1247
+ const cssRules = text.slice(blockStart, j - 1).trim();
1248
+ matches.push({ selectorContent, cssRules, fullMatch: text.slice(whereStart, j) });
1249
+ i = j;
1250
+ }
1251
+ return matches;
1252
+ };
1253
+
1254
+ const whereMatches = extractWhereSelectors(cssText);
1255
+ for (const match of whereMatches) {
1256
+ const selectorContent = match.selectorContent;
1257
+ const cssRules = match.cssRules;
1258
+
1259
+ // Check if CSS rules contain !important
1260
+ const hasImportant = /\s!important/.test(cssRules);
1261
+
1262
+ // Extract individual class names from the selector, including those with ! prefix
1263
+ // Match: .classname or .!classname (where classname can contain word chars and hyphens)
1264
+ const classMatches = selectorContent.match(/\.(!?[\w-]+)/g);
1265
+ if (classMatches) {
1266
+ for (const classMatch of classMatches) {
1267
+ const className = classMatch.substring(1); // Remove the leading dot
1268
+
1269
+ // Skip if it's a Tailwind-generated class (but check base name without !)
1270
+ const baseClassName = className.startsWith('!') ? className.slice(1) : className;
1271
+ if (this.isTailwindGeneratedClass(baseClassName)) {
1272
+ continue;
1273
+ }
1274
+
1275
+ // Determine if this class should have !important
1276
+ // Only apply !important if the class name itself starts with ! (e.g., !col)
1277
+ const classHasImportantPrefix = className.startsWith('!');
1278
+ let finalCssRules = cssRules;
1279
+
1280
+ if (classHasImportantPrefix) {
1281
+ // Class has ! prefix, ensure CSS has !important
1282
+ if (!hasImportant) {
1283
+ finalCssRules = cssRules.includes(';') ?
1284
+ cssRules.replace(/;/g, ' !important;') :
1285
+ cssRules + ' !important';
1286
+ }
1287
+ } else {
1288
+ // Class doesn't have ! prefix, remove !important if present
1289
+ if (hasImportant) {
1290
+ finalCssRules = cssRules.replace(/\s!important/g, '');
1291
+ }
1292
+ }
1293
+
1294
+ // Store the class with its full name (including ! prefix) as the key
1295
+ // This ensures !col is stored separately from col
1296
+ // Store as selector-aware object to preserve :where() context for variants
1297
+ // Mark as fullBlock since it contains nested rules
1298
+ const storageKey = className;
1299
+ const originalSelector = `:where(${selectorContent})`;
1300
+ // Ensure CSS is always a string
1301
+ const cssString = ensureCssString(finalCssRules);
1302
+ const value = {
1303
+ selector: originalSelector,
1304
+ css: cssString,
1305
+ fullBlock: true // :where() blocks often contain nested rules
1306
+ };
1307
+
1308
+ // Combine CSS rules if the class already exists
1309
+ if (utilities.has(storageKey)) {
1310
+ const existing = utilities.get(storageKey);
1311
+ // If existing is a string, convert to array format
1312
+ if (typeof existing === 'string') {
1313
+ const existingCssString = ensureCssString(existing);
1314
+ utilities.set(storageKey, [
1315
+ { selector: `.${className}`, css: existingCssString },
1316
+ value
1317
+ ]);
1318
+ } else if (Array.isArray(existing)) {
1319
+ // Check if this selector already exists in the array
1320
+ const found = existing.find(e => e.selector === value.selector);
1321
+ if (found) {
1322
+ // Ensure both are strings before concatenating
1323
+ const foundCss = ensureCssString(found.css);
1324
+ const valueCss = ensureCssString(value.css);
1325
+ found.css = `${foundCss}; ${valueCss}`;
1326
+ } else {
1327
+ existing.push(value);
1328
+ }
1329
+ } else if (existing && existing.selector) {
1330
+ // Convert single object to array
1331
+ if (existing.selector === value.selector) {
1332
+ // Ensure both are strings before concatenating
1333
+ const existingCss = ensureCssString(existing.css);
1334
+ const valueCss = ensureCssString(value.css);
1335
+ existing.css = `${existingCss}; ${valueCss}`;
1336
+ utilities.set(storageKey, [existing]);
1337
+ } else {
1338
+ utilities.set(storageKey, [existing, value]);
1339
+ }
1340
+ }
1341
+ } else {
1342
+ utilities.set(storageKey, [value]);
1343
+ }
1344
+ }
1345
+ }
1346
+ }
1347
+
1348
+ // Fallback: detect classes inside compound selectors (e.g., aside[popover].appear-start { ... })
1349
+ // Use brace matching to handle nested rules properly
1350
+ try {
1351
+ let i = 0;
1352
+ while (i < cssText.length) {
1353
+ // Skip whitespace
1354
+ while (i < cssText.length && /\s/.test(cssText[i])) i++;
1355
+ if (i >= cssText.length) break;
1356
+
1357
+ // Skip at-rules
1358
+ if (cssText[i] === '@') {
1359
+ // Skip to next line or closing brace
1360
+ while (i < cssText.length && cssText[i] !== '\n' && cssText[i] !== '}') i++;
1361
+ continue;
1362
+ }
1363
+
1364
+ // Capture selector up to '{'
1365
+ let selStart = i;
1366
+ while (i < cssText.length && cssText[i] !== '{') i++;
1367
+ if (i >= cssText.length) break;
1368
+ const selector = cssText.slice(selStart, i).trim();
1369
+
1370
+ // Find matching '}' with brace depth (handles nested braces)
1371
+ i++; // skip '{'
1372
+ let depth = 1;
1373
+ let blockStart = i;
1374
+ while (i < cssText.length && depth > 0) {
1375
+ if (cssText[i] === '{') depth++;
1376
+ else if (cssText[i] === '}') depth--;
1377
+ i++;
1378
+ }
1379
+ if (depth > 0) {
1380
+ // Malformed, skip
1381
+ continue;
1382
+ }
1383
+ const cssRules = cssText.slice(blockStart, i - 1).trim();
1384
+
1385
+ // Skip at-rules and keyframes/selectors without classes
1386
+ if (selector.startsWith('@')) continue;
1387
+
1388
+ // Match: .classname or .!classname
1389
+ const classMatches = selector.match(/\.!?[A-Za-z0-9_-]+/g);
1390
+ if (!classMatches) continue;
1391
+
1392
+ // Check if CSS rules contain !important
1393
+ const hasImportant = /\s!important/.test(cssRules);
1394
+
1395
+ for (const classToken of classMatches) {
1396
+ const className = classToken.substring(1); // Remove the leading dot
1397
+
1398
+ // Skip Tailwind-generated or already captured classes (check base name without !)
1399
+ const baseClassName = className.startsWith('!') ? className.slice(1) : className;
1400
+ if (this.isTailwindGeneratedClass(baseClassName)) continue;
1401
+
1402
+ // Determine if this class should have !important
1403
+ const classHasImportantPrefix = className.startsWith('!');
1404
+ let finalCssRules = cssRules;
1405
+
1406
+ if (classHasImportantPrefix) {
1407
+ // Class has ! prefix, ensure CSS has !important
1408
+ if (!hasImportant) {
1409
+ finalCssRules = cssRules.includes(';') ?
1410
+ cssRules.replace(/;/g, ' !important;') :
1411
+ cssRules + ' !important';
1412
+ }
1413
+ } else {
1414
+ // Class doesn't have ! prefix, remove !important if present
1415
+ if (hasImportant) {
1416
+ finalCssRules = cssRules.replace(/\s!important/g, '');
1417
+ }
1418
+ }
1419
+
1420
+ // Store the class with its full name (including ! prefix) as the key
1421
+ // Ensure CSS is always a string
1422
+ const cssString = ensureCssString(finalCssRules);
1423
+ if (utilities.has(className)) {
1424
+ const existingRules = utilities.get(className);
1425
+
1426
+ // Handle arrays properly - don't stringify them
1427
+ if (Array.isArray(existingRules)) {
1428
+ // If existing is an array, add this as a new entry
1429
+ // Create a simple selector entry for the compound selector
1430
+ utilities.set(className, [...existingRules, {
1431
+ selector: `.${className}`,
1432
+ css: cssString,
1433
+ fullBlock: true // Compound selectors often have nested rules
1434
+ }]);
1435
+ } else {
1436
+ // Existing is a string or object - convert to array format
1437
+ const existingCssString = ensureCssString(existingRules);
1438
+ utilities.set(className, [
1439
+ { selector: `.${className}`, css: existingCssString },
1440
+ { selector: `.${className}`, css: cssString, fullBlock: true }
1441
+ ]);
1442
+ }
1443
+ } else {
1444
+ utilities.set(className, cssString);
1445
+ }
1446
+ }
1447
+ }
1448
+ } catch (e) {
1449
+ // Be tolerant: this is a best-effort extractor
1450
+ }
1451
+
1452
+ // Universal fallback with basic nesting resolution for selectors using '&'
1453
+ // Captures context like :where(aside[popover]) &.appear-start &:not(:popover-open)
1454
+ try {
1455
+ const rules = [];
1456
+
1457
+ // Minimal nested CSS resolver: scans and builds combined selectors
1458
+ // Only stores top-level rules with their full block content (including nested blocks)
1459
+ const resolveNested = (text, parentSelector = '', isTopLevel = true) => {
1460
+ let i = 0;
1461
+ while (i < text.length) {
1462
+ // Skip whitespace
1463
+ while (i < text.length && /\s/.test(text[i])) i++;
1464
+ if (i >= text.length) break;
1465
+
1466
+ // Capture selector up to '{'
1467
+ let selStart = i;
1468
+ while (i < text.length && text[i] !== '{') i++;
1469
+ if (i >= text.length) break;
1470
+ const rawSelector = text.slice(selStart, i).trim();
1471
+
1472
+ // Find matching '}' with brace depth
1473
+ i++; // skip '{'
1474
+ let depth = 1;
1475
+ let blockStart = i;
1476
+ while (i < text.length && depth > 0) {
1477
+ if (text[i] === '{') depth++;
1478
+ else if (text[i] === '}') depth--;
1479
+ i++;
1480
+ }
1481
+ const block = text.slice(blockStart, i - 1);
1482
+
1483
+ // Build combined selector by replacing '&' with parentSelector
1484
+ const combinedSelector = parentSelector
1485
+ ? rawSelector.replace(/&/g, parentSelector).trim()
1486
+ : rawSelector.trim();
1487
+
1488
+ // Only store top-level rules (not nested blocks) with their full content
1489
+ // This preserves @starting-style and other nested at-rules in the CSS
1490
+ if (isTopLevel) {
1491
+ const fullBlock = block.trim();
1492
+ if (fullBlock) {
1493
+ rules.push({ selector: combinedSelector, css: fullBlock, fullBlock: true });
1494
+ }
1495
+ }
1496
+
1497
+ // Recurse into nested blocks but don't store them separately
1498
+ // They're already included in the parent block's CSS
1499
+ resolveNested(block, combinedSelector, false);
1500
+ }
1501
+ };
1502
+
1503
+ resolveNested(cssText, '');
1504
+
1505
+ // Map resolved rules to utilities by class token presence
1506
+ for (const rule of rules) {
1507
+ // Clean selector: strip comments and normalize whitespace
1508
+ let cleanedSelector = rule.selector.replace(/\/\*[^]*?\*\//g, '').replace(/\s+/g, ' ').trim();
1509
+ // Match: .classname or .!classname
1510
+ const classTokens = cleanedSelector.match(/\.!?[A-Za-z0-9_-]+/g);
1511
+ if (!classTokens) continue;
1512
+
1513
+ // Check if CSS rules contain !important
1514
+ const hasImportant = /\s!important/.test(rule.css);
1515
+
1516
+ for (const token of classTokens) {
1517
+ const className = token.slice(1); // Remove the leading dot
1518
+
1519
+ // Skip Tailwind-generated classes (check base name without !)
1520
+ const baseClassName = className.startsWith('!') ? className.slice(1) : className;
1521
+ if (this.isTailwindGeneratedClass(baseClassName)) continue;
1522
+
1523
+ // Determine if this class should have !important
1524
+ const classHasImportantPrefix = className.startsWith('!');
1525
+ let finalCss = rule.css;
1526
+
1527
+ if (classHasImportantPrefix) {
1528
+ // Class has ! prefix, ensure CSS has !important
1529
+ if (!hasImportant) {
1530
+ finalCss = rule.css.includes(';') ?
1531
+ rule.css.replace(/;/g, ' !important;') :
1532
+ rule.css + ' !important';
1533
+ }
1534
+ } else {
1535
+ // Class doesn't have ! prefix, remove !important if present
1536
+ if (hasImportant) {
1537
+ finalCss = rule.css.replace(/\s!important/g, '');
1538
+ }
1539
+ }
1540
+
1541
+ // Store selector-aware utility so variants preserve context and pseudos
1542
+ // Use full className (including !) as the key
1543
+ // Preserve fullBlock flag if present
1544
+ // Ensure CSS is always a string
1545
+ const cssString = ensureCssString(finalCss);
1546
+ const value = {
1547
+ selector: cleanedSelector,
1548
+ css: cssString,
1549
+ fullBlock: rule.fullBlock || false
1550
+ };
1551
+ if (utilities.has(className)) {
1552
+ const existing = utilities.get(className);
1553
+ if (typeof existing === 'string') {
1554
+ const existingCssString = ensureCssString(existing);
1555
+ utilities.set(className, [{ selector: `.${className}`, css: existingCssString }, value]);
1556
+ } else if (Array.isArray(existing)) {
1557
+ const found = existing.find(e => e.selector === value.selector);
1558
+ if (found) {
1559
+ // Ensure both are strings before concatenating
1560
+ const foundCss = ensureCssString(found.css);
1561
+ const valueCss = ensureCssString(value.css);
1562
+ found.css = `${foundCss}; ${valueCss}`;
1563
+ } else {
1564
+ existing.push(value);
1565
+ }
1566
+ } else if (existing && existing.selector) {
1567
+ if (existing.selector === value.selector) {
1568
+ // Ensure both are strings before concatenating
1569
+ const existingCss = ensureCssString(existing.css);
1570
+ const valueCss = ensureCssString(value.css);
1571
+ existing.css = `${existingCss}; ${valueCss}`;
1572
+ utilities.set(className, [existing]);
1573
+ } else {
1574
+ utilities.set(className, [existing, value]);
1575
+ }
1576
+ }
1577
+ } else {
1578
+ utilities.set(className, [value]);
1579
+ }
1580
+ }
1581
+ }
1582
+ } catch (e) {
1583
+ // Tolerate parsing errors; this is best-effort
1584
+ }
1585
+
1586
+ // Dedupe per class: the four parser passes can capture the same rule with
1587
+ // differing whitespace/selector order; normalize before comparing.
1588
+ for (const [className, value] of utilities.entries()) {
1589
+ if (!Array.isArray(value)) continue;
1590
+ const seen = new Set();
1591
+ const deduped = [];
1592
+ for (const entry of value) {
1593
+ if (!entry || typeof entry !== 'object') continue;
1594
+ const sel = String(entry.selector || '').replace(/\s+/g, ' ').trim();
1595
+ const css = String(entry.css || '').replace(/\s+/g, ' ').trim();
1596
+ if (!css) continue;
1597
+ const key = `${sel}${css}`;
1598
+ if (seen.has(key)) continue;
1599
+ seen.add(key);
1600
+ deduped.push(entry);
1601
+ }
1602
+ if (deduped.length > 0) {
1603
+ utilities.set(className, deduped);
1604
+ }
1605
+ }
1606
+
1607
+ return utilities;
1608
+ };
1609
+
1610
+ // Check if a class name looks like a Tailwind-generated class
1611
+ TailwindCompiler.prototype.isTailwindGeneratedClass = function (className) {
1612
+ // Check if this looks like a Tailwind-generated class
1613
+ const tailwindPatterns = [
1614
+ /^[a-z]+-\d+$/, // spacing, sizing classes like p-4, w-10
1615
+ /^[a-z]+-\[/, // arbitrary values like w-[100px]
1616
+ /^(text|bg|border|ring|shadow|opacity|scale|rotate|translate|skew|origin|transform|transition|duration|delay|ease|animate|backdrop|blur|brightness|contrast|drop-shadow|grayscale|hue-rotate|invert|saturate|sepia|filter|backdrop-)/, // common Tailwind prefixes
1617
+ /^(sm|md|lg|xl|2xl):/, // responsive prefixes
1618
+ /^(hover|focus|active|disabled|group-hover|group-focus|peer-hover|peer-focus):/, // state prefixes
1619
+ /^(dark|light):/, // theme prefixes
1620
+ /^!/, // important modifier
1621
+ /^\[/, // arbitrary selectors
1622
+ ];
1623
+
1624
+ return tailwindPatterns.some(pattern => pattern.test(className));
1625
+ };
1626
+
1627
+ // Parse a class name into its components (variants, base class, important)
1628
+ TailwindCompiler.prototype.parseClassName = function (className) {
1629
+ // Check cache first
1630
+ if (this.classCache.has(className)) {
1631
+ return this.classCache.get(className);
1632
+ }
1633
+
1634
+ const result = {
1635
+ important: className.startsWith('!'),
1636
+ variants: [],
1637
+ baseClass: className
1638
+ };
1639
+
1640
+ // Remove important modifier if present
1641
+ if (result.important) {
1642
+ className = className.slice(1);
1643
+ }
1644
+
1645
+ // Split by variant separator, but preserve content within brackets
1646
+ const parts = [];
1647
+ let current = '';
1648
+ let bracketDepth = 0;
1649
+
1650
+ for (let i = 0; i < className.length; i++) {
1651
+ const char = className[i];
1652
+
1653
+ if (char === '[') {
1654
+ bracketDepth++;
1655
+ } else if (char === ']') {
1656
+ bracketDepth--;
1657
+ }
1658
+
1659
+ if (char === ':' && bracketDepth === 0) {
1660
+ parts.push(current);
1661
+ current = '';
1662
+ } else {
1663
+ current += char;
1664
+ }
1665
+ }
1666
+ parts.push(current); // Add the last part
1667
+
1668
+ result.baseClass = parts.pop(); // Last part is always the base class
1669
+
1670
+ // Process variants in order (left to right)
1671
+ result.variants = parts.map(variant => {
1672
+ // FIRST: Check for exact variant matches (most common case)
1673
+ const exactSelector = this.variants[variant];
1674
+ if (exactSelector) {
1675
+ return {
1676
+ name: variant,
1677
+ selector: exactSelector,
1678
+ isArbitrary: false
1679
+ };
1680
+ }
1681
+
1682
+ // SECOND: Check for arbitrary selector variants [&_selector]
1683
+ if (variant.startsWith('[') && variant.endsWith(']')) {
1684
+ const arbitrarySelector = variant.slice(1, -1); // Remove brackets
1685
+ if (arbitrarySelector.startsWith('&')) {
1686
+ return {
1687
+ name: variant,
1688
+ selector: arbitrarySelector,
1689
+ isArbitrary: true
1690
+ };
1691
+ }
1692
+ // Handle arbitrary data variants like [data-state=active]
1693
+ if (arbitrarySelector.startsWith('data-')) {
1694
+ return {
1695
+ name: variant,
1696
+ selector: `[${arbitrarySelector}] &`,
1697
+ isArbitrary: true
1698
+ };
1699
+ }
1700
+ }
1701
+
1702
+ // THIRD: Handle parameterized variants (only if exact match not found)
1703
+
1704
+ // Handle data-[...] variants like data-[state=active] (not starting with bracket)
1705
+ // These should be regular attribute selectors, not arbitrary
1706
+ const dataMatch = variant.match(/^data-\[(.+)\]$/);
1707
+ if (dataMatch) {
1708
+ const attrValue = dataMatch[1];
1709
+ // Add quotes around the value if it contains = (e.g., state=active -> state="active")
1710
+ const quotedValue = attrValue.includes('=')
1711
+ ? attrValue.replace(/^([^=]+)=(.+)$/, '$1="$2"')
1712
+ : attrValue;
1713
+ return {
1714
+ name: variant,
1715
+ selector: `[data-${quotedValue}] &`,
1716
+ isArbitrary: false
1717
+ };
1718
+ }
1719
+
1720
+ // Handle parameterized nth variants: nth-3, nth-last-2, nth-of-type-2, nth-last-of-type-2
1721
+ const nthMatch = variant.match(/^(nth|nth-last|nth-of-type|nth-last-of-type)-(\d+)$/);
1722
+ if (nthMatch) {
1723
+ const baseVariant = nthMatch[1];
1724
+ const param = nthMatch[2];
1725
+ const baseSelector = this.variants[baseVariant];
1726
+ if (baseSelector) {
1727
+ return {
1728
+ name: variant,
1729
+ selector: `${baseSelector}(${param})`,
1730
+ isArbitrary: false
1731
+ };
1732
+ }
1733
+ }
1734
+
1735
+ // Handle arbitrary nth variants: nth-[4n+1], nth-[2n], nth-last-[n+3], nth-of-type-[odd], etc.
1736
+ const nthArbitraryMatch = variant.match(/^(nth|nth-last|nth-of-type|nth-last-of-type)-\[(.+)\]$/);
1737
+ if (nthArbitraryMatch) {
1738
+ const baseVariant = nthArbitraryMatch[1];
1739
+ const param = nthArbitraryMatch[2];
1740
+ const baseSelector = this.variants[baseVariant];
1741
+ if (baseSelector) {
1742
+ return {
1743
+ name: variant,
1744
+ selector: `${baseSelector}(${param})`,
1745
+ isArbitrary: false
1746
+ };
1747
+ }
1748
+ }
1749
+
1750
+ // Handle parameterized has/not variants: has-[>p], not-\[hidden\]
1751
+ const hasMatch = variant.match(/^has-\[(.+)\]$/);
1752
+ if (hasMatch) {
1753
+ const param = hasMatch[1];
1754
+ const baseSelector = this.variants['has'];
1755
+ if (baseSelector) {
1756
+ return {
1757
+ name: variant,
1758
+ selector: `${baseSelector}(${param})`,
1759
+ isArbitrary: false
1760
+ };
1761
+ }
1762
+ }
1763
+
1764
+ // Handle not-\[...\] variants like not-\[hidden\]
1765
+ // The backslashes escape the brackets in the class name
1766
+ // Match: not-\[content\] where content may have escaped brackets
1767
+ const notMatch = variant.match(/^not-\\\[(.+?)\\\]$/);
1768
+ if (notMatch) {
1769
+ const param = notMatch[1];
1770
+ // The parameter is already clean (backslashes were just for escaping brackets in class name)
1771
+ const baseSelector = this.variants['not'];
1772
+ if (baseSelector) {
1773
+ return {
1774
+ name: variant,
1775
+ selector: `${baseSelector}([${param}])`,
1776
+ isArbitrary: false
1777
+ };
1778
+ }
1779
+ }
1780
+
1781
+ // Container query variants (Tailwind v4): @[size], @min-[size], @max-[size],
1782
+ // named @sm/@md/… and @max-sm/…, each optionally scoped to a /name container.
1783
+ // Tailwind emits these for its own utilities; recognising them here keeps them
1784
+ // off the "Unknown variant" path and lets the Manifest compiler apply them to
1785
+ // theme/semantic utilities too. The @-prefixed selector is wrapped as an
1786
+ // at-rule by the generator, producing `@container (width …) { … }`.
1787
+ if (variant.startsWith('@')) {
1788
+ const containerSizes = {
1789
+ '3xs': '16rem', '2xs': '18rem', 'xs': '20rem', 'sm': '24rem',
1790
+ 'md': '28rem', 'lg': '32rem', 'xl': '36rem', '2xl': '42rem',
1791
+ '3xl': '48rem', '4xl': '56rem', '5xl': '64rem', '6xl': '72rem', '7xl': '80rem'
1792
+ };
1793
+ const slash = variant.indexOf('/');
1794
+ const name = slash !== -1 ? variant.slice(slash + 1) : '';
1795
+ const ctx = name ? `@container ${name}` : '@container';
1796
+ const body = (slash !== -1 ? variant.slice(0, slash) : variant).slice(1); // strip '@'
1797
+ let m;
1798
+ if (body === '' || body === 'container') {
1799
+ return { name: variant, selector: ctx, isArbitrary: false };
1800
+ }
1801
+ if ((m = body.match(/^(?:min-)?\[(.+)\]$/))) {
1802
+ return { name: variant, selector: `${ctx} (width >= ${m[1]})`, isArbitrary: false };
1803
+ }
1804
+ if ((m = body.match(/^max-\[(.+)\]$/))) {
1805
+ return { name: variant, selector: `${ctx} (width < ${m[1]})`, isArbitrary: false };
1806
+ }
1807
+ if ((m = body.match(/^max-(.+)$/))) {
1808
+ if (containerSizes[m[1]]) {
1809
+ return { name: variant, selector: `${ctx} (width < ${containerSizes[m[1]]})`, isArbitrary: false };
1810
+ }
1811
+ } else if (containerSizes[body]) {
1812
+ return { name: variant, selector: `${ctx} (width >= ${containerSizes[body]})`, isArbitrary: false };
1813
+ }
1814
+ // Unrecognised @-form falls through to the warning below.
1815
+ }
1816
+
1817
+ // If no match found, warn once per unique token and return null.
1818
+ // Deduped so unsupported variants (e.g. container queries like
1819
+ // `@[10rem]:`) don't flood the console on every compile pass.
1820
+ if (!this._warnedVariants) this._warnedVariants = new Set();
1821
+ if (!this._warnedVariants.has(variant)) {
1822
+ this._warnedVariants.add(variant);
1823
+ console.warn(`Unknown variant: ${variant}`);
1824
+ }
1825
+ return null;
1826
+ }).filter(Boolean);
1827
+
1828
+ // Cache the result
1829
+ this.classCache.set(className, result);
1830
+ return result;
1831
+ };
1832
+
1833
+
1834
+
1835
+ // Compilation logic and utility generation
1836
+
1837
+ // Generate utilities from CSS variables
1838
+ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedData) {
1839
+ try {
1840
+ const utilities = [];
1841
+ const generatedRules = new Set(); // Track generated rules to prevent duplicates
1842
+ const variables = this.extractThemeVariables(cssText);
1843
+ const { classes: usedClasses, variableSuffixes } = usedData;
1844
+
1845
+ if (variables.size === 0) {
1846
+ return '';
1847
+ }
1848
+
1849
+ // Helper to escape special characters in class names
1850
+ const escapeClassName = (className) => {
1851
+ return className.replace(/[^a-zA-Z0-9-]/g, '\\$&');
1852
+ };
1853
+
1854
+ // Helper to generate a single utility with its variants
1855
+ const generateUtility = (baseClass, css) => {
1856
+ // Find all variants of this base class that are actually used
1857
+ const usedVariants = usedClasses
1858
+ .filter(cls => {
1859
+ const parts = cls.split(':');
1860
+ const basePart = parts[parts.length - 1];
1861
+ return basePart === baseClass || (basePart.startsWith('!') && basePart.slice(1) === baseClass);
1862
+ });
1863
+
1864
+ // Generate base utility if it's used directly
1865
+ if (usedClasses.includes(baseClass)) {
1866
+ const rule = `.${escapeClassName(baseClass)} { ${css} }`;
1867
+ if (!generatedRules.has(rule)) {
1868
+ utilities.push(rule);
1869
+ generatedRules.add(rule);
1870
+ }
1871
+ }
1872
+ // Generate important version if used
1873
+ if (usedClasses.includes('!' + baseClass)) {
1874
+ const importantCss = css.includes(';') ?
1875
+ css.replace(/;/g, ' !important;') :
1876
+ css + ' !important';
1877
+ const rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
1878
+ if (!generatedRules.has(rule)) {
1879
+ utilities.push(rule);
1880
+ generatedRules.add(rule);
1881
+ }
1882
+ }
1883
+
1884
+ // Generate each variant as a separate class
1885
+ for (const variantClass of usedVariants) {
1886
+ if (variantClass === baseClass) continue;
1887
+
1888
+ const parsed = this.parseClassName(variantClass);
1889
+
1890
+ // Check if this is an important variant
1891
+ const isImportant = parsed.important;
1892
+ // Ensure css is a string (handle cases where it might be an object)
1893
+ const cssString = typeof css === 'string' ? css : (css && typeof css === 'object' && css.css ? css.css : String(css));
1894
+ const cssContent = isImportant ?
1895
+ (cssString.includes(';') ? cssString.replace(/;/g, ' !important;') : cssString + ' !important') :
1896
+ cssString;
1897
+
1898
+ // Build selector by applying variants
1899
+ let selector = `.${escapeClassName(variantClass)}`;
1900
+ let hasMediaQuery = false;
1901
+ let mediaQueryRule = '';
1902
+
1903
+ for (const variant of parsed.variants) {
1904
+ if (variant.isArbitrary) {
1905
+ // Handle arbitrary selectors like [&_figure] or [&_fieldset:has(legend):not(.whatever)]
1906
+ // For selectors starting with &, replace & with the base class and use as regular selector
1907
+ let arbitrarySelector = variant.selector;
1908
+
1909
+ if (arbitrarySelector.startsWith('&')) {
1910
+ // Replace & with the base class selector and convert _ to spaces
1911
+ arbitrarySelector = arbitrarySelector.replace(/_/g, ' ').replace(/&/g, selector);
1912
+ selector = arbitrarySelector;
1913
+ } else {
1914
+ // For other arbitrary selectors (like data attributes), use nested CSS
1915
+ arbitrarySelector = arbitrarySelector.replace(/_/g, ' ');
1916
+ selector = { baseClass: selector, arbitrarySelector };
1917
+ }
1918
+ } else if (variant.selector.includes('&')) {
1919
+ // Substitute & with the current selector. This produces flat CSS that
1920
+ // works for both ancestor patterns (`.dark &` → `.dark .X`) and
1921
+ // self-extending patterns (`& > p` → `.X > p`).
1922
+ selector = variant.selector.replace(/&/g, selector);
1923
+ } else if (variant.selector.startsWith(':')) {
1924
+ // For pseudo-classes, append to selector
1925
+ selector = `${selector}${variant.selector}`;
1926
+ } else if (variant.selector.startsWith('@')) {
1927
+ // For media queries, wrap the whole rule
1928
+ hasMediaQuery = true;
1929
+ mediaQueryRule = variant.selector;
1930
+ }
1931
+ }
1932
+
1933
+ // Generate the final rule
1934
+ let rule;
1935
+ if (typeof selector === 'object' && selector.arbitrarySelector) {
1936
+ // Handle arbitrary selectors with nested CSS (for non-& selectors)
1937
+ rule = `${selector.baseClass} {\n ${selector.arbitrarySelector} {\n ${cssContent}\n }\n}`;
1938
+ } else {
1939
+ // Regular selector
1940
+ rule = `${selector} { ${cssContent} }`;
1941
+ }
1942
+
1943
+ const finalRule = hasMediaQuery ?
1944
+ `${mediaQueryRule} { ${rule} }` :
1945
+ rule;
1946
+
1947
+ if (!generatedRules.has(finalRule)) {
1948
+ utilities.push(finalRule);
1949
+ generatedRules.add(finalRule);
1950
+ }
1951
+ }
1952
+ };
1953
+
1954
+ // Generate utilities based on variable prefix
1955
+ for (const [varName, varValue] of variables.entries()) {
1956
+ if (!varName.match(this.regexPatterns.tailwindPrefix)) {
1957
+ continue;
1958
+ }
1959
+
1960
+ const suffix = varName.split('-').slice(1).join('-');
1961
+ const value = `var(--${varName})`;
1962
+ const prefix = varName.split('-')[0] + '-';
1963
+ const generator = this.utilityGenerators[prefix];
1964
+
1965
+ if (generator) {
1966
+ const utilityPairs = generator(suffix, value);
1967
+ for (const [className, css] of utilityPairs) {
1968
+ // Check if this specific utility class is actually used (including variants and important)
1969
+ const isUsed = usedClasses.some(cls => {
1970
+ // Parse the class to extract the base utility name
1971
+ const parsed = this.parseClassName(cls);
1972
+ const baseClass = parsed.baseClass;
1973
+
1974
+ // Check both normal and important versions
1975
+ return baseClass === className ||
1976
+ baseClass === '!' + className ||
1977
+ (baseClass.startsWith('!') && baseClass.slice(1) === className);
1978
+ });
1979
+ if (isUsed) {
1980
+ generateUtility(className, css);
1981
+ }
1982
+
1983
+ // Opacity variants: collect the bare base (e.g. `bg-black/10`),
1984
+ // not the prefixed class — generateUtility discovers variant
1985
+ // prefixes itself, so passing the bare base emits the correct
1986
+ // selector (`.backdrop\:bg-black\/10::backdrop`).
1987
+ const opacityBaseClasses = new Set();
1988
+ for (const cls of usedClasses) {
1989
+ // Parse the class to extract the base utility name
1990
+ const parsed = this.parseClassName(cls);
1991
+ const baseClass = parsed.baseClass;
1992
+
1993
+ // Check if this class has an opacity modifier and matches our base class
1994
+ if (baseClass.includes('/')) {
1995
+ const baseWithoutOpacity = baseClass.split('/')[0];
1996
+ if (baseWithoutOpacity === className) {
1997
+ const opacity = baseClass.split('/')[1];
1998
+ // Validate that the opacity is a number between 0-100
1999
+ if (!isNaN(opacity) && opacity >= 0 && opacity <= 100) {
2000
+ opacityBaseClasses.add(baseClass);
2001
+ }
2002
+ }
2003
+ }
2004
+ }
2005
+
2006
+ // Generate opacity utilities for each opacity value found
2007
+ for (const opacityBaseClass of opacityBaseClasses) {
2008
+ const opacity = opacityBaseClass.split('/')[1];
2009
+ const opacityValue = `color-mix(in oklch, ${value} ${opacity}%, transparent)`;
2010
+ const opacityCss = css.replace(value, opacityValue);
2011
+ generateUtility(opacityBaseClass, opacityCss);
2012
+ }
2013
+ }
2014
+ }
2015
+ }
2016
+
2017
+ return utilities.join('\n');
2018
+ } catch (error) {
2019
+ console.error('Error generating utilities:', error);
2020
+ return '';
2021
+ }
2022
+ };
2023
+
2024
+ // Generate custom utilities from discovered custom utility classes
2025
+ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2026
+ try {
2027
+ const utilities = [];
2028
+ const generatedRules = new Set();
2029
+ const { classes: usedClasses } = usedData;
2030
+
2031
+ // Helper to clean up [object Object] from CSS strings
2032
+ const cleanCssString = (css) => {
2033
+ if (typeof css !== 'string') return css;
2034
+ return css.replace(/\[object Object\](;?\s*)/g, '').trim();
2035
+ };
2036
+
2037
+ if (this.customUtilities.size === 0) {
2038
+ return '';
2039
+ }
2040
+
2041
+ // Helper to escape special characters in class names
2042
+ const escapeClassName = (className) => {
2043
+ return className.replace(/[^a-zA-Z0-9-]/g, '\\$&');
2044
+ };
2045
+
2046
+ // Replace & in selectors — legacy/flattened CSS only; nested CSS keeps &
2047
+ // as-is so native nesting works.
2048
+ const replaceAmpersandInSelectors = (cssText, replacement) => {
2049
+ const hasNestedSelectors =
2050
+ /&\s*[:\.\[{]/.test(cssText) || // &:not(), &::before, &[attr], & {
2051
+ /&\s*\n\s*[:\.\[{]/.test(cssText) || // & on new line followed by selector
2052
+ /\]\s*&/.test(cssText) || // [dir=rtl] & pattern
2053
+ /&\s*$/.test(cssText.split('\n').find(line => line.trim().startsWith('&')) || ''); // & on its own line
2054
+
2055
+ if (hasNestedSelectors) {
2056
+ // This is nested CSS - don't replace &, preserve it as-is
2057
+ return cssText;
2058
+ }
2059
+
2060
+ // Legacy behavior: replace & for flattened CSS (shouldn't be needed for nested CSS)
2061
+ let result = '';
2062
+ let i = 0;
2063
+ let inString = false;
2064
+ let stringChar = '';
2065
+ let inComment = false;
2066
+
2067
+ while (i < cssText.length) {
2068
+ const char = cssText[i];
2069
+ const nextChar = i + 1 < cssText.length ? cssText[i + 1] : '';
2070
+ const prevChar = i > 0 ? cssText[i - 1] : '';
2071
+
2072
+ // Handle strings
2073
+ if ((char === '"' || char === "'") && !inComment) {
2074
+ if (!inString) {
2075
+ inString = true;
2076
+ stringChar = char;
2077
+ } else if (char === stringChar && prevChar !== '\\') {
2078
+ inString = false;
2079
+ stringChar = '';
2080
+ }
2081
+ result += char;
2082
+ i++;
2083
+ continue;
2084
+ }
2085
+
2086
+ if (inString) {
2087
+ result += char;
2088
+ i++;
2089
+ continue;
2090
+ }
2091
+
2092
+ // Handle comments
2093
+ if (char === '/' && nextChar === '*') {
2094
+ inComment = true;
2095
+ result += char;
2096
+ i++;
2097
+ continue;
2098
+ }
2099
+
2100
+ if (inComment) {
2101
+ if (char === '*' && nextChar === '/') {
2102
+ inComment = false;
2103
+ result += char;
2104
+ i++;
2105
+ continue;
2106
+ }
2107
+ result += char;
2108
+ i++;
2109
+ continue;
2110
+ }
2111
+
2112
+ // Replace & when it's in a selector context (only for legacy/flattened CSS)
2113
+ if (char === '&') {
2114
+ const lookAhead = cssText.slice(i + 1, Math.min(i + 10, cssText.length));
2115
+ const isSelector =
2116
+ lookAhead.match(/^[:\.\[\+\>~,)]/) ||
2117
+ lookAhead.match(/^\s+[:\.\[\+\>~,{]/) ||
2118
+ lookAhead === '' ||
2119
+ lookAhead[0] === '\n' ||
2120
+ (prevChar === '\n' && (lookAhead[0] === ':' || lookAhead[0] === '.' || lookAhead[0] === '[' || lookAhead[0] === ' '));
2121
+
2122
+ if (isSelector) {
2123
+ result += replacement;
2124
+ i++;
2125
+ continue;
2126
+ }
2127
+ }
2128
+
2129
+ result += char;
2130
+ i++;
2131
+ }
2132
+
2133
+ return result;
2134
+ };
2135
+
2136
+ // Helper to generate a single utility with its variants
2137
+ const generateUtility = (baseClass, css, selectorInfo) => {
2138
+ // Ensure css is a string at the start
2139
+ if (typeof css !== 'string') {
2140
+ css = typeof css === 'object' && css && css.css ? css.css : String(css);
2141
+ }
2142
+
2143
+ // Find all variants of this base class that are actually used
2144
+ const usedVariants = usedClasses
2145
+ .filter(cls => {
2146
+ const parts = cls.split(':');
2147
+ const basePart = parts[parts.length - 1];
2148
+ const isMatch = basePart === baseClass || (basePart.startsWith('!') && basePart.slice(1) === baseClass);
2149
+ return isMatch;
2150
+ });
2151
+
2152
+ // Skip generating base utility - it already exists in the CSS
2153
+ // Only generate variants and important versions
2154
+
2155
+ // Generate important version if used
2156
+ if (usedClasses.includes('!' + baseClass)) {
2157
+ // Check if CSS already has !important to avoid double !important
2158
+ const alreadyHasImportant = /\s!important/.test(css);
2159
+ const importantCss = alreadyHasImportant ? css :
2160
+ (css.includes(';') ?
2161
+ css.replace(/;/g, ' !important;') :
2162
+ css + ' !important');
2163
+ let rule;
2164
+ if (selectorInfo && selectorInfo.selector) {
2165
+ // If the original selector contains :where(), don't try to modify it
2166
+ // Generate a separate rule for the important version
2167
+ // This prevents :where(.row, .col) from becoming :where(.row, .!col) with !important on all
2168
+ if (selectorInfo.selector.includes(':where(')) {
2169
+ // For :where() selectors, generate individual class selector for important version
2170
+ rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
2171
+ } else {
2172
+ // For other contextual selectors, do the replacement
2173
+ const variantSel = `.${escapeClassName('!' + baseClass)}`;
2174
+ let contextual = selectorInfo.selector.replace(new RegExp(`\\.${baseClass}(?=[^a-zA-Z0-9_-]|$)`), variantSel);
2175
+ if (contextual === selectorInfo.selector) {
2176
+ // Fallback: append class to the end if base token not found
2177
+ contextual = `${selectorInfo.selector}${variantSel}`;
2178
+ }
2179
+ rule = `${contextual} { ${importantCss} }`;
2180
+ }
2181
+ } else {
2182
+ rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
2183
+ }
2184
+ if (!generatedRules.has(rule)) {
2185
+ utilities.push(rule);
2186
+ generatedRules.add(rule);
2187
+ }
2188
+ }
2189
+
2190
+ // Generate each variant as a separate class
2191
+ for (const variantClass of usedVariants) {
2192
+ if (variantClass === baseClass) continue;
2193
+
2194
+ const parsed = this.parseClassName(variantClass);
2195
+
2196
+ // Check if this is an important variant
2197
+ const isImportant = parsed.important;
2198
+ // Ensure css is a string (handle cases where it might be an object)
2199
+ const cssString = typeof css === 'string' ? css : (css && typeof css === 'object' && css.css ? css.css : String(css));
2200
+ const cssContent = isImportant ?
2201
+ (cssString.includes(';') ? cssString.replace(/;/g, ' !important;') : cssString + ' !important') :
2202
+ cssString;
2203
+
2204
+ // Build selector by applying variants
2205
+ let selector = `.${escapeClassName(variantClass)}`;
2206
+ let hasMediaQuery = false;
2207
+ let mediaQueryRule = '';
2208
+ let nestedSelector = null;
2209
+
2210
+ for (const variant of parsed.variants) {
2211
+ if (variant.isArbitrary) {
2212
+ // Handle arbitrary selectors like [&_figure] or [&_fieldset:has(legend):not(.whatever)]
2213
+ // For selectors starting with &, replace & with the base class and use as regular selector
2214
+ let arbitrarySelector = variant.selector;
2215
+
2216
+ if (arbitrarySelector.startsWith('&')) {
2217
+ // Replace & with the base class selector and convert _ to spaces
2218
+ arbitrarySelector = arbitrarySelector.replace(/_/g, ' ').replace(/&/g, selector);
2219
+ selector = arbitrarySelector;
2220
+ } else {
2221
+ // For other arbitrary selectors (like data attributes), use nested CSS
2222
+ arbitrarySelector = arbitrarySelector.replace(/_/g, ' ');
2223
+ selector = { baseClass: selector, arbitrarySelector };
2224
+ }
2225
+ } else if (variant.selector.includes('&')) {
2226
+ // Substitute & with the current selector. This produces flat CSS that
2227
+ // works for both ancestor patterns (`.dark &` → `.dark .X`) and
2228
+ // self-extending patterns (`& > p` → `.X > p`).
2229
+ selector = variant.selector.replace(/&/g, selector);
2230
+ } else if (variant.selector.startsWith(':')) {
2231
+ // For pseudo-classes, append to selector
2232
+ selector = `${selector}${variant.selector}`;
2233
+ } else if (variant.selector.startsWith('@')) {
2234
+ // For media queries, wrap the whole rule
2235
+ hasMediaQuery = true;
2236
+ mediaQueryRule = variant.selector;
2237
+ }
2238
+ }
2239
+
2240
+ // Generate the final rule
2241
+ let rule;
2242
+ // Ensure cssContent is a string before using it anywhere
2243
+ let cssContentStr = typeof cssContent === 'string' ? cssContent : String(cssContent);
2244
+ // Clean up any [object Object] that might have snuck in
2245
+ cssContentStr = cssContentStr.replace(/\[object Object\](;?\s*)/g, '').trim();
2246
+
2247
+ if (typeof selector === 'object' && selector.arbitrarySelector) {
2248
+ // Handle arbitrary selectors with nested CSS (for non-& selectors)
2249
+ rule = `${selector.baseClass} {\n ${selector.arbitrarySelector} {\n ${cssContentStr}\n }\n}`;
2250
+ } else if (nestedSelector) {
2251
+ // Handle nested selectors (variants ending with &)
2252
+ // Check if CSS is a full block (contains nested blocks like @starting-style)
2253
+ const isFullBlock = selectorInfo && selectorInfo.fullBlock !== undefined ? selectorInfo.fullBlock :
2254
+ (cssContentStr.includes('@starting-style') ||
2255
+ cssContentStr.includes('@media') ||
2256
+ cssContentStr.includes('@supports') ||
2257
+ (cssContentStr.includes('{') && cssContentStr.includes('}')));
2258
+
2259
+ // Regular selector or contextual replacement using original selector info
2260
+ if (selectorInfo && selectorInfo.selector) {
2261
+ // If the original selector contains :where(), don't try to modify it
2262
+ if (selectorInfo.selector.includes(':where(')) {
2263
+ // For :where() selectors, generate individual class selector
2264
+ if (isFullBlock) {
2265
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, selector);
2266
+ rule = `${selector} {\n ${nestedSelector} {\n${resolvedCss}\n }\n}`;
2267
+ } else {
2268
+ rule = `${selector} {\n ${nestedSelector} {\n ${cssContentStr}\n }\n}`;
2269
+ }
2270
+ } else {
2271
+ // For other contextual selectors, do the replacement
2272
+ const contextualRe = new RegExp(`\\.${baseClass}(?=[^a-zA-Z0-9_-]|$)`);
2273
+ let contextual = selectorInfo.selector.replace(contextualRe, selector);
2274
+ if (contextual === selectorInfo.selector) {
2275
+ // Fallback when base token not directly present
2276
+ contextual = `${selectorInfo.selector}${selector}`;
2277
+ }
2278
+ if (isFullBlock) {
2279
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, contextual);
2280
+ rule = `${contextual} {\n ${nestedSelector} {\n${resolvedCss}\n }\n}`;
2281
+ } else {
2282
+ rule = `${contextual} {\n ${nestedSelector} {\n ${cssContentStr}\n }\n}`;
2283
+ }
2284
+ }
2285
+ } else {
2286
+ if (isFullBlock) {
2287
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, selector);
2288
+ rule = `${selector} {\n ${nestedSelector} {\n${resolvedCss}\n }\n}`;
2289
+ } else {
2290
+ rule = `${selector} {\n ${nestedSelector} {\n ${cssContentStr}\n }\n}`;
2291
+ }
2292
+ }
2293
+ } else {
2294
+ // Check if CSS is a full block (contains nested blocks like @starting-style)
2295
+ // Use selectorInfo.fullBlock if available, otherwise check CSS content
2296
+ const isFullBlock = selectorInfo && selectorInfo.fullBlock !== undefined ? selectorInfo.fullBlock :
2297
+ (cssContentStr.includes('@starting-style') ||
2298
+ cssContentStr.includes('@media') ||
2299
+ cssContentStr.includes('@supports') ||
2300
+ (cssContentStr.includes('{') && cssContentStr.includes('}')));
2301
+
2302
+ // Regular selector or contextual replacement using original selector info
2303
+ if (selectorInfo && selectorInfo.selector) {
2304
+ // If the original selector contains :where(), don't try to modify it
2305
+ // Instead, generate a separate rule for this specific class
2306
+ // This prevents issues where :where(.row, .col) would become :where(.row, .!col)
2307
+ // with !important applied to all classes
2308
+ if (selectorInfo.selector.includes(':where(')) {
2309
+ // For :where() selectors, generate individual class selector
2310
+ // This ensures !important is only applied to the specific class
2311
+ if (isFullBlock) {
2312
+ // Full block CSS includes nested content with & references
2313
+ // Replace & in selectors with the actual selector (handles all selector contexts)
2314
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, selector);
2315
+ rule = `${selector} {\n${resolvedCss}\n}`;
2316
+ } else {
2317
+ rule = `${selector} { ${cssContentStr} }`;
2318
+ }
2319
+ } else {
2320
+ // For other contextual selectors, do the replacement
2321
+ const contextualRe = new RegExp(`\\.${baseClass}(?=[^a-zA-Z0-9_-]|$)`);
2322
+ let contextual = selectorInfo.selector.replace(contextualRe, selector);
2323
+ if (contextual === selectorInfo.selector) {
2324
+ // Fallback when base token not directly present
2325
+ contextual = `${selectorInfo.selector}${selector}`;
2326
+ }
2327
+ if (isFullBlock) {
2328
+ // Replace & in selectors with the contextual selector (handles all selector contexts)
2329
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, contextual);
2330
+ rule = `${contextual} {\n${resolvedCss}\n}`;
2331
+ } else {
2332
+ rule = `${contextual} { ${cssContentStr} }`;
2333
+ }
2334
+ }
2335
+ } else {
2336
+ if (isFullBlock) {
2337
+ // Replace & in selectors with the actual selector (handles all selector contexts)
2338
+ const resolvedCss = replaceAmpersandInSelectors(cssContentStr, selector);
2339
+ rule = `${selector} {\n${resolvedCss}\n}`;
2340
+ } else {
2341
+ rule = `${selector} { ${cssContentStr} }`;
2342
+ }
2343
+ }
2344
+ }
2345
+
2346
+ let finalRule;
2347
+ if (hasMediaQuery) {
2348
+ // Wrap once for responsive variants unless the rule already contains @media
2349
+ if (typeof rule === 'string' && rule.trim().startsWith('@media')) {
2350
+ finalRule = rule;
2351
+ } else {
2352
+ finalRule = `${mediaQueryRule} { ${rule} }`;
2353
+ }
2354
+ } else {
2355
+ finalRule = rule;
2356
+ }
2357
+
2358
+ if (!generatedRules.has(finalRule)) {
2359
+ utilities.push(finalRule);
2360
+ generatedRules.add(finalRule);
2361
+ }
2362
+ }
2363
+ };
2364
+
2365
+ // Generate utilities for each custom class that's actually used
2366
+ for (const [className, cssOrSelector] of this.customUtilities.entries()) {
2367
+ // Normalize class name: if it starts with !, extract the base name
2368
+ const hasImportantPrefix = className.startsWith('!');
2369
+ const baseClassName = hasImportantPrefix ? className.slice(1) : className;
2370
+
2371
+ // Check if this specific utility class is actually used (including variants and important)
2372
+ const isUsed = usedClasses.some(cls => {
2373
+ // Parse the class to extract the base utility name
2374
+ const parsed = this.parseClassName(cls);
2375
+ const baseClass = parsed.baseClass;
2376
+
2377
+ // Check both normal and important versions
2378
+ return baseClass === className ||
2379
+ baseClass === baseClassName ||
2380
+ baseClass === '!' + baseClassName ||
2381
+ (baseClass.startsWith('!') && baseClass.slice(1) === baseClassName);
2382
+ });
2383
+
2384
+ if (isUsed) {
2385
+ // Normalize CSS: if className has ! prefix, the CSS should already have !important
2386
+ // But we need to pass the base class name to generateUtility
2387
+ let normalizedCss = cssOrSelector;
2388
+ if (typeof cssOrSelector === 'string') {
2389
+ normalizedCss = cleanCssString(cssOrSelector);
2390
+ } else if (Array.isArray(cssOrSelector)) {
2391
+ // For arrays, we'll handle each entry separately below
2392
+ } else if (cssOrSelector && cssOrSelector.css) {
2393
+ // Ensure we extract the CSS string, not an object
2394
+ const extracted = typeof cssOrSelector.css === 'string' ? cssOrSelector.css :
2395
+ (cssOrSelector.css && typeof cssOrSelector.css === 'object' && cssOrSelector.css.css ? cssOrSelector.css.css :
2396
+ String(cssOrSelector.css));
2397
+ normalizedCss = cleanCssString(extracted);
2398
+ } else {
2399
+ // Fallback: convert to string
2400
+ normalizedCss = cleanCssString(String(cssOrSelector));
2401
+ }
2402
+
2403
+ // Generate utility with base class name (without !)
2404
+ // The CSS already has !important if className started with !
2405
+ if (typeof cssOrSelector === 'string') {
2406
+ generateUtility(baseClassName, normalizedCss, null);
2407
+ } else if (Array.isArray(cssOrSelector)) {
2408
+ for (const entry of cssOrSelector) {
2409
+ if (entry && entry.css && entry.selector) {
2410
+ // Ensure entry.css is a string (not an object)
2411
+ const extracted = typeof entry.css === 'string' ? entry.css :
2412
+ (entry.css && typeof entry.css === 'object' && entry.css.css ? entry.css.css :
2413
+ String(entry.css));
2414
+ const entryCss = cleanCssString(extracted);
2415
+
2416
+ generateUtility(baseClassName, entryCss, {
2417
+ selector: entry.selector,
2418
+ fullBlock: entry.fullBlock || false
2419
+ });
2420
+ }
2421
+ }
2422
+ } else if (cssOrSelector && cssOrSelector.css && cssOrSelector.selector) {
2423
+ // Ensure cssOrSelector.css is a string (not an object)
2424
+ const extracted = typeof cssOrSelector.css === 'string' ? cssOrSelector.css :
2425
+ (cssOrSelector.css && typeof cssOrSelector.css === 'object' && cssOrSelector.css.css ? cssOrSelector.css.css :
2426
+ String(cssOrSelector.css));
2427
+ const selectorCss = cleanCssString(extracted);
2428
+
2429
+ generateUtility(baseClassName, selectorCss, {
2430
+ selector: cssOrSelector.selector,
2431
+ fullBlock: cssOrSelector.fullBlock || false
2432
+ });
2433
+ }
2434
+ }
2435
+ }
2436
+
2437
+ return utilities.join('\n');
2438
+ } catch (error) {
2439
+ console.error('Error generating custom utilities:', error);
2440
+ return '';
2441
+ }
2442
+ };
2443
+
2444
+ // Helper function to sort utilities so base classes come before variants
2445
+ TailwindCompiler.prototype.sortUtilities = function(utilitiesText) {
2446
+ if (!utilitiesText) return '';
2447
+
2448
+ // Split into individual rules (each rule starts with . or @media)
2449
+ const rules = [];
2450
+ let currentRule = '';
2451
+ const lines = utilitiesText.split('\n');
2452
+
2453
+ for (const line of lines) {
2454
+ // Check if this line starts a new rule
2455
+ if (line.trim().match(/^(\.|@media|@layer|@supports)/)) {
2456
+ // Save previous rule if exists
2457
+ if (currentRule.trim()) {
2458
+ rules.push(currentRule.trim());
2459
+ }
2460
+ currentRule = line;
2461
+ } else {
2462
+ // Continue current rule
2463
+ currentRule += '\n' + line;
2464
+ }
2465
+ }
2466
+ // Add last rule
2467
+ if (currentRule.trim()) {
2468
+ rules.push(currentRule.trim());
2469
+ }
2470
+
2471
+ // Sort: base utilities (no @media) come before variants (with @media)
2472
+ rules.sort((a, b) => {
2473
+ const aHasMedia = a.startsWith('@media');
2474
+ const bHasMedia = b.startsWith('@media');
2475
+
2476
+ // Base utilities come before variants
2477
+ if (!aHasMedia && bHasMedia) return -1;
2478
+ if (aHasMedia && !bHasMedia) return 1;
2479
+
2480
+ // Same type, maintain order
2481
+ return 0;
2482
+ });
2483
+
2484
+ return rules.join('\n\n');
2485
+ };
2486
+
2487
+ // Helper function to filter critical utilities that are already in layer utilities
2488
+ TailwindCompiler.prototype.filterCriticalUtilities = function(criticalText, layerUtilities) {
2489
+ if (!criticalText || !layerUtilities) return '';
2490
+
2491
+ // Extract base class names from layer utilities (including variants)
2492
+ const layerClassMatches = layerUtilities.match(/\.([a-zA-Z0-9_:!\\-]+)\s*{/g) || [];
2493
+ const layerClasses = new Set();
2494
+
2495
+ for (const match of layerClassMatches) {
2496
+ // Remove escaping and extract class name
2497
+ const className = match.replace(/^\./, '').replace(/\s*{.*$/, '').replace(/\\/g, '');
2498
+ // Extract base class name (last part after colons for variants)
2499
+ const parts = className.split(':');
2500
+ const baseClass = parts.length > 1 ? parts[parts.length - 1] : className;
2501
+ // Remove ! prefix if present
2502
+ const cleanBase = baseClass.startsWith('!') ? baseClass.slice(1) : baseClass;
2503
+ layerClasses.add(cleanBase);
2504
+ }
2505
+
2506
+ // Filter critical CSS to exclude utilities already in layer
2507
+ const criticalLines = criticalText.split('\n');
2508
+ const filteredCritical = [];
2509
+ let inRule = false;
2510
+ let currentRule = '';
2511
+ let ruleClass = '';
2512
+
2513
+ for (const line of criticalLines) {
2514
+ // Check if line starts a new rule
2515
+ const ruleMatch = line.match(/^\.([a-zA-Z0-9_:!\\-]+)\s*{/);
2516
+ if (ruleMatch) {
2517
+ // Save previous rule if it wasn't filtered
2518
+ if (currentRule) {
2519
+ const prevBaseClass = ruleClass.replace(/\\/g, '').split(':').pop().replace(/^!/, '');
2520
+ if (!layerClasses.has(prevBaseClass)) {
2521
+ filteredCritical.push(currentRule);
2522
+ }
2523
+ }
2524
+ // Start new rule
2525
+ ruleClass = ruleMatch[1];
2526
+ currentRule = line;
2527
+ inRule = true;
2528
+ } else if (inRule) {
2529
+ currentRule += '\n' + line;
2530
+ if (line.trim() === '}') {
2531
+ // End of rule - check if we should keep it
2532
+ const baseClass = ruleClass.replace(/\\/g, '').split(':').pop().replace(/^!/, '');
2533
+ if (!layerClasses.has(baseClass)) {
2534
+ filteredCritical.push(currentRule);
2535
+ }
2536
+ currentRule = '';
2537
+ ruleClass = '';
2538
+ inRule = false;
2539
+ }
2540
+ } else {
2541
+ // Not in a rule, keep as-is (comments, etc.)
2542
+ filteredCritical.push(line);
2543
+ }
2544
+ }
2545
+
2546
+ // Add any remaining rule
2547
+ if (currentRule) {
2548
+ const baseClass = ruleClass.replace(/\\/g, '').split(':').pop().replace(/^!/, '');
2549
+ if (!layerClasses.has(baseClass)) {
2550
+ filteredCritical.push(currentRule);
2551
+ }
2552
+ }
2553
+
2554
+ return filteredCritical.join('\n').trim();
2555
+ };
2556
+
2557
+ // Main compilation method
2558
+ TailwindCompiler.prototype.compile = async function () {
2559
+ if (this.usesStaticPrerenderUtilities) {
2560
+ // Static utilities shipped with the page — nothing to compile.
2561
+ if (!window.__manifestUtilitiesReady) {
2562
+ window.__manifestUtilitiesReady = true;
2563
+ window.dispatchEvent(new CustomEvent('manifest:utilities-ready'));
2564
+ }
2565
+ return;
2566
+ }
2567
+
2568
+ const compileStart = performance.now();
2569
+
2570
+ try {
2571
+ // Throttled or busy: don't DROP the request — queue exactly one retry
2572
+ // so late-discovered classes (md: variants on swapped-in components)
2573
+ // always get compiled. The pending counter lets manifest:ready and the
2574
+ // prerenderer's settle hold until utilities are actually current.
2575
+ const now = Date.now();
2576
+ if (now - this.lastCompileTime < this.minCompileInterval || this.isCompiling) {
2577
+ if (!this._retryQueued) {
2578
+ this._retryQueued = true;
2579
+ window.__manifestUtilitiesPending = (window.__manifestUtilitiesPending || 0) + 1;
2580
+ setTimeout(() => {
2581
+ this._retryQueued = false;
2582
+ window.__manifestUtilitiesPending = Math.max(0, (window.__manifestUtilitiesPending || 1) - 1);
2583
+ this.compile();
2584
+ }, this.minCompileInterval + 50);
2585
+ }
2586
+ return;
2587
+ }
2588
+ this.lastCompileTime = now;
2589
+ this.isCompiling = true;
2590
+ window.__manifestUtilitiesPending = (window.__manifestUtilitiesPending || 0) + 1;
2591
+ this._compileCounted = true;
2592
+
2593
+ // On first run, scan static classes and CSS variables
2594
+ if (!this.hasScannedStatic) {
2595
+ await this.scanStaticClasses();
2596
+
2597
+ // Fetch CSS content once for initial compilation
2598
+ const themeCss = await this.fetchThemeContent();
2599
+ if (themeCss) {
2600
+ // Extract custom utilities. Framework CSS is scanned too so the
2601
+ // generator can emit variants of semantic classes (md:row,
2602
+ // hover:brand); base forms are suppressed in generateCustomUtilities.
2603
+ const discoveredCustomUtilities = this.extractCustomUtilities(themeCss);
2604
+ for (const [name, value] of discoveredCustomUtilities.entries()) {
2605
+ this.customUtilities.set(name, value);
2606
+ }
2607
+
2608
+ const variables = this.extractThemeVariables(themeCss);
2609
+ for (const [name, value] of variables.entries()) {
2610
+ this.currentThemeVars.set(name, value);
2611
+ }
2612
+
2613
+ // Generate utilities for all static classes (minus anything the
2614
+ // static utilities sheet already covers).
2615
+ const staticUsedData = {
2616
+ classes: this.filterStaticallyCoveredClasses(Array.from(this.staticClassCache)),
2617
+ variableSuffixes: []
2618
+ };
2619
+ // Process static classes for variable suffixes
2620
+ for (const cls of this.staticClassCache) {
2621
+ const parts = cls.split(':');
2622
+ const baseClass = parts[parts.length - 1];
2623
+ const classParts = baseClass.split('-');
2624
+ if (classParts.length > 1) {
2625
+ staticUsedData.variableSuffixes.push(classParts.slice(1).join('-'));
2626
+ }
2627
+ }
2628
+
2629
+ // Generate both variable-based and custom utilities
2630
+ const varUtilities = this.generateUtilitiesFromVars(themeCss, staticUsedData);
2631
+ const customUtilitiesGenerated = this.generateCustomUtilities(staticUsedData);
2632
+
2633
+ let allUtilities = [varUtilities, customUtilitiesGenerated].filter(Boolean).join('\n\n');
2634
+ // Sort utilities so base classes come before variants
2635
+ allUtilities = this.sortUtilities(allUtilities);
2636
+
2637
+ if (allUtilities) {
2638
+ const finalCss = `@layer utilities {\n${allUtilities}\n}`;
2639
+
2640
+ this.styleElement.textContent = finalCss;
2641
+ this.ensureUtilityStylesLast();
2642
+ this.scheduleEnsureUtilityStylesLast();
2643
+
2644
+ // Remove critical style element entirely after compilation
2645
+ // Use requestAnimationFrame to ensure styles are painted before removing
2646
+ requestAnimationFrame(() => {
2647
+ requestAnimationFrame(() => {
2648
+ // Double RAF ensures paint has occurred
2649
+ if (this.criticalStyleElement && this.criticalStyleElement.parentNode) {
2650
+ this.criticalStyleElement.parentNode.removeChild(this.criticalStyleElement);
2651
+ this.criticalStyleElement = null;
2652
+ }
2653
+ this.ensureUtilityStylesLast();
2654
+ });
2655
+ });
2656
+ this.lastClassesHash = staticUsedData.classes.sort().join(',');
2657
+
2658
+ // Save to cache for next page load
2659
+ const themeHash = this.generateThemeHash(themeCss);
2660
+ const cacheKey = `${this.lastClassesHash}-${themeHash}`;
2661
+ this.cache.set(cacheKey, {
2662
+ css: finalCss,
2663
+ timestamp: Date.now(),
2664
+ themeHash: themeHash
2665
+ });
2666
+ this.savePersistentCache();
2667
+ }
2668
+ }
2669
+
2670
+ this.hasInitialized = true;
2671
+ this.isCompiling = false;
2672
+ return;
2673
+ }
2674
+
2675
+ // For subsequent compilations, check for new dynamic classes
2676
+ const usedData = this.getUsedClasses();
2677
+ const dynamicClasses = Array.from(this.dynamicClassCache);
2678
+
2679
+ // Create a hash of current dynamic classes to detect changes
2680
+ const dynamicClassesHash = dynamicClasses.sort().join(',');
2681
+
2682
+ // Check if dynamic classes have actually changed
2683
+ if (dynamicClassesHash !== this.lastClassesHash || !this.hasInitialized) {
2684
+ // Fetch CSS content for dynamic compilation
2685
+ const themeCss = await this.fetchThemeContent();
2686
+ if (!themeCss) {
2687
+ this.isCompiling = false;
2688
+ return;
2689
+ }
2690
+
2691
+ // Update custom utilities cache if needed
2692
+ const discoveredCustomUtilities = this.extractCustomUtilities(themeCss);
2693
+ for (const [name, value] of discoveredCustomUtilities.entries()) {
2694
+ this.customUtilities.set(name, value);
2695
+ }
2696
+
2697
+ // Check for variable changes
2698
+ const variables = this.extractThemeVariables(themeCss);
2699
+ let hasVariableChanges = false;
2700
+ for (const [name, value] of variables.entries()) {
2701
+ const currentValue = this.currentThemeVars.get(name);
2702
+ if (currentValue !== value) {
2703
+ hasVariableChanges = true;
2704
+ this.currentThemeVars.set(name, value);
2705
+ }
2706
+ }
2707
+
2708
+ // Generate utilities for all classes (static + dynamic) if needed
2709
+ if (hasVariableChanges || dynamicClassesHash !== this.lastClassesHash) {
2710
+
2711
+ // Generate both variable-based and custom utilities
2712
+ const varUtilities = this.generateUtilitiesFromVars(themeCss, usedData);
2713
+ const customUtilitiesGenerated = this.generateCustomUtilities(usedData);
2714
+
2715
+ let allUtilities = [varUtilities, customUtilitiesGenerated].filter(Boolean).join('\n\n');
2716
+ // Sort utilities so base classes come before variants
2717
+ allUtilities = this.sortUtilities(allUtilities);
2718
+
2719
+ if (allUtilities) {
2720
+ const finalCss = `@layer utilities {\n${allUtilities}\n}`;
2721
+
2722
+ this.styleElement.textContent = finalCss;
2723
+ this.ensureUtilityStylesLast();
2724
+ this.scheduleEnsureUtilityStylesLast();
2725
+
2726
+ // Remove critical style element entirely after compilation
2727
+ // Use requestAnimationFrame to ensure styles are painted before removing
2728
+ requestAnimationFrame(() => {
2729
+ requestAnimationFrame(() => {
2730
+ // Double RAF ensures paint has occurred
2731
+ if (this.criticalStyleElement && this.criticalStyleElement.parentNode) {
2732
+ this.criticalStyleElement.parentNode.removeChild(this.criticalStyleElement);
2733
+ this.criticalStyleElement = null;
2734
+ }
2735
+ this.ensureUtilityStylesLast();
2736
+ });
2737
+ });
2738
+ this.lastClassesHash = dynamicClassesHash;
2739
+
2740
+ // Save to cache for next page load
2741
+ const themeHash = this.generateThemeHash(themeCss);
2742
+ const cacheKey = `${this.lastClassesHash}-${themeHash}`;
2743
+ this.cache.set(cacheKey, {
2744
+ css: finalCss,
2745
+ timestamp: Date.now(),
2746
+ themeHash: themeHash
2747
+ });
2748
+ this.savePersistentCache();
2749
+ }
2750
+ }
2751
+ }
2752
+
2753
+ } catch (error) {
2754
+ console.error('[Manifest Utilities] Error compiling Tailwind CSS:', error);
2755
+ } finally {
2756
+ this.isCompiling = false;
2757
+ if (this._compileCounted) {
2758
+ this._compileCounted = false;
2759
+ window.__manifestUtilitiesPending = Math.max(0, (window.__manifestUtilitiesPending || 1) - 1);
2760
+ if (!window.__manifestUtilitiesPending) {
2761
+ window.dispatchEvent(new CustomEvent('manifest:utilities-idle'));
2762
+ }
2763
+ }
2764
+ // First-compile settle signal for the manifest:ready coordinator.
2765
+ if (!window.__manifestUtilitiesReady) {
2766
+ window.__manifestUtilitiesReady = true;
2767
+ window.dispatchEvent(new CustomEvent('manifest:utilities-ready'));
2768
+ }
2769
+ }
2770
+ };
2771
+
2772
+
2773
+
2774
+ // Node/Workers wrapper — combined at build time (see build.mjs
2775
+ // buildUtilitiesNodeModule) with the DOM-free subset of the browser plugin's
2776
+ // subscripts (generators, variants, main, helpers, compile) so this exports
2777
+ // the exact same generation logic the browser JIT runs, with no document.
2778
+
2779
+ /** A bare TailwindCompiler instance carrying only what the pure generation
2780
+ * methods (generateUtilitiesFromVars, generateCustomUtilities, parseClassName,
2781
+ * ...) read from `this` — mirrors the constructor's generator/variant setup
2782
+ * (manifest.utilities.main.js) without any of its DOM work, which this never
2783
+ * runs: `new TailwindCompiler()` is never called here. */
2784
+ function createNodeCompiler() {
2785
+ const instance = Object.create(TailwindCompiler.prototype);
2786
+ instance.options = { rootSelector: ':root', themeSelector: '@theme' };
2787
+ instance.regexPatterns = {
2788
+ root: new RegExp(`${instance.options.rootSelector}\\s*{([^}]*)}`, 'g'),
2789
+ theme: new RegExp(`${instance.options.themeSelector}\\s*{([^}]*)}`, 'g'),
2790
+ variable: /--([\w-]+):\s*([^;]+);/g,
2791
+ tailwindPrefix: /^(color|font|text|font-weight|tracking|leading|breakpoint|container|spacing|radius|shadow|inset-shadow|drop-shadow|blur|perspective|aspect|ease|animate|border-width|border-style|outline|outline-width|outline-style|ring|ring-offset|divide|accent|caret|decoration|placeholder|selection|scrollbar)-/
2792
+ };
2793
+ instance.utilityGenerators = createUtilityGenerators();
2794
+ instance.variants = createVariants();
2795
+ instance.variantGroups = createVariantGroups();
2796
+ instance.classCache = new Map();
2797
+ instance.customUtilities = new Map();
2798
+ return instance;
2799
+ }
2800
+
2801
+ /**
2802
+ * Compile the utility CSS the browser plugin would produce for exactly
2803
+ * `classes` — variants, theme-variable-driven utilities (generateUtilitiesFromVars)
2804
+ * and custom utilities (generateCustomUtilities) discovered in `themeCss`/`baseCss`.
2805
+ * Deterministic for a given (classes, themeCss, baseCss) triple.
2806
+ *
2807
+ * @param {{classes?: string[], themeCss?: string, baseCss?: string}} options
2808
+ * @returns {Promise<string>}
2809
+ */
2810
+ export async function compileUtilities({ classes = [], themeCss = '', baseCss = '' } = {}) {
2811
+ const compiler = createNodeCompiler();
2812
+ const cssText = [baseCss, themeCss].filter(Boolean).join('\n');
2813
+
2814
+ const discovered = compiler.extractCustomUtilities(cssText);
2815
+ for (const [name, value] of discovered) compiler.customUtilities.set(name, value);
2816
+
2817
+ const usedData = { classes: Array.from(new Set(classes)), variableSuffixes: [] };
2818
+
2819
+ const varUtilities = compiler.generateUtilitiesFromVars(cssText, usedData);
2820
+ const customUtilities = compiler.generateCustomUtilities(usedData);
2821
+
2822
+ let allUtilities = [varUtilities, customUtilities].filter(Boolean).join('\n\n');
2823
+ allUtilities = compiler.sortUtilities(allUtilities);
2824
+
2825
+ return allUtilities ? `@layer utilities {\n${allUtilities}\n}` : '';
2826
+ }
2827
+
2828
+ // `class:token=` (per-class conditional binding) and the `:class="..."`
2829
+ // shorthand aren't matched by extractClassesFromHTML's `x-(data|bind:class|class)=`
2830
+ // regex (no `x-` prefix), so pick those up here too — cheap regex pass, not a
2831
+ // JS/expression parser, so only literal-looking quoted tokens are kept.
2832
+ const CLASS_TOKEN_BINDING_RE = /\bclass:([a-zA-Z0-9_-]+(?:\\.[a-zA-Z0-9_-]+)*)\s*=/g;
2833
+ // Backreference to the opening quote so a nested quote char (`:class="'a b'"`)
2834
+ // doesn't truncate the match early.
2835
+ const SHORTHAND_CLASS_BINDING_RE = /\B:class=(["'])((?:(?!\1)[\s\S])*)\1/g;
2836
+
2837
+ /**
2838
+ * Scan HTML for utility class tokens using the same extraction the browser
2839
+ * plugin uses (extractClassesFromHTML), plus `class:`/`:class` static tokens.
2840
+ * Skips `x-`/`$` tokens. Returns a sorted, deduplicated array.
2841
+ *
2842
+ * @param {string} html
2843
+ * @returns {string[]}
2844
+ */
2845
+ export function scanClasses(html) {
2846
+ const compiler = createNodeCompiler();
2847
+ const set = new Set();
2848
+ compiler.extractClassesFromHTML(html, set);
2849
+
2850
+ let m;
2851
+ CLASS_TOKEN_BINDING_RE.lastIndex = 0;
2852
+ while ((m = CLASS_TOKEN_BINDING_RE.exec(html)) !== null) {
2853
+ const cls = m[1].replace(/\\(.)/g, '$1');
2854
+ if (cls && !cls.startsWith('x-') && !cls.startsWith('$')) set.add(cls);
2855
+ }
2856
+
2857
+ SHORTHAND_CLASS_BINDING_RE.lastIndex = 0;
2858
+ while ((m = SHORTHAND_CLASS_BINDING_RE.exec(html)) !== null) {
2859
+ const content = m[2];
2860
+ const quoted = content.match(/['"`]([^'"`\s]+)['"`]/g);
2861
+ if (quoted) {
2862
+ for (const q of quoted) {
2863
+ const cls = q.replace(/['"`]/g, '');
2864
+ if (cls && !cls.startsWith('$') && !cls.startsWith('x-') && !cls.includes('(')) set.add(cls);
2865
+ }
2866
+ }
2867
+ }
2868
+
2869
+ return Array.from(set).sort();
2870
+ }