@kbach/react 0.6.30 → 0.6.32

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.
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  defaultTheme: () => defaultTheme,
32
32
  disableRuntimeCSS: () => disableRuntimeCSS,
33
33
  escapeCSSSelector: () => escapeCSSSelector,
34
+ expandModeAwareColorClasses: () => expandModeAwareColorClasses,
34
35
  flatten: () => flatten,
35
36
  generateClassCSS: () => generateClassCSS,
36
37
  getActiveBreakpoints: () => getActiveBreakpoints,
@@ -50,6 +51,7 @@ __export(index_exports, {
50
51
  initConfig: () => initConfig,
51
52
  isKnownModifier: () => isKnownModifier,
52
53
  isKnownUtility: () => isKnownUtility,
54
+ isModeAwareColor: () => isModeAwareColor,
53
55
  isNative: () => isNative,
54
56
  isRuntimeCSSDisabled: () => isRuntimeCSSDisabled,
55
57
  isWeb: () => isWeb,
@@ -82,595 +84,41 @@ __export(index_exports, {
82
84
  });
83
85
  module.exports = __toCommonJS(index_exports);
84
86
 
85
- // src/core/theme.ts
86
- var defaultColors = {
87
- transparent: "transparent",
88
- current: "currentColor",
89
- black: "#000000",
90
- white: "#ffffff",
91
- slate: {
92
- 1: "#f8fafc",
93
- 2: "#f1f5f9",
94
- 3: "#e2e8f0",
95
- 4: "#cbd5e1",
96
- 5: "#94a3b8",
97
- 6: "#64748b",
98
- 7: "#475569",
99
- 8: "#334155",
100
- 9: "#1e293b",
101
- 10: "#0f172a",
102
- 11: "#020617",
103
- 12: "#01020a"
104
- },
105
- gray: {
106
- 1: "#f9fafb",
107
- 2: "#f3f4f6",
108
- 3: "#e5e7eb",
109
- 4: "#d1d5db",
110
- 5: "#9ca3af",
111
- 6: "#6b7280",
112
- 7: "#4b5563",
113
- 8: "#374151",
114
- 9: "#1f2937",
115
- 10: "#111827",
116
- 11: "#030712",
117
- 12: "#020409"
118
- },
119
- zinc: {
120
- 1: "#fafafa",
121
- 2: "#f4f4f5",
122
- 3: "#e4e4e7",
123
- 4: "#d4d4d8",
124
- 5: "#a1a1aa",
125
- 6: "#71717a",
126
- 7: "#52525b",
127
- 8: "#3f3f46",
128
- 9: "#27272a",
129
- 10: "#18181b",
130
- 11: "#09090b",
131
- 12: "#050506"
132
- },
133
- neutral: {
134
- 1: "#fafafa",
135
- 2: "#f5f5f5",
136
- 3: "#e5e5e5",
137
- 4: "#d4d4d4",
138
- 5: "#a3a3a3",
139
- 6: "#737373",
140
- 7: "#525252",
141
- 8: "#404040",
142
- 9: "#262626",
143
- 10: "#171717",
144
- 11: "#0a0a0a",
145
- 12: "#050505"
146
- },
147
- stone: {
148
- 1: "#fafaf9",
149
- 2: "#f5f5f4",
150
- 3: "#e7e5e4",
151
- 4: "#d6d3d1",
152
- 5: "#a8a29e",
153
- 6: "#78716c",
154
- 7: "#57534e",
155
- 8: "#44403c",
156
- 9: "#292524",
157
- 10: "#1c1917",
158
- 11: "#0c0a09",
159
- 12: "#070605"
160
- },
161
- red: {
162
- 1: "#fef2f2",
163
- 2: "#fee2e2",
164
- 3: "#fecaca",
165
- 4: "#fca5a5",
166
- 5: "#f87171",
167
- 6: "#ef4444",
168
- 7: "#dc2626",
169
- 8: "#b91c1c",
170
- 9: "#991b1b",
171
- 10: "#7f1d1d",
172
- 11: "#450a0a",
173
- 12: "#280606"
174
- },
175
- orange: {
176
- 1: "#fff7ed",
177
- 2: "#ffedd5",
178
- 3: "#fed7aa",
179
- 4: "#fdba74",
180
- 5: "#fb923c",
181
- 6: "#f97316",
182
- 7: "#ea580c",
183
- 8: "#c2410c",
184
- 9: "#9a3412",
185
- 10: "#7c2d12",
186
- 11: "#431407",
187
- 12: "#270c04"
188
- },
189
- amber: {
190
- 1: "#fffbeb",
191
- 2: "#fef3c7",
192
- 3: "#fde68a",
193
- 4: "#fcd34d",
194
- 5: "#fbbf24",
195
- 6: "#f59e0b",
196
- 7: "#d97706",
197
- 8: "#b45309",
198
- 9: "#92400e",
199
- 10: "#78350f",
200
- 11: "#451a03",
201
- 12: "#291002"
202
- },
203
- yellow: {
204
- 1: "#fefce8",
205
- 2: "#fef9c3",
206
- 3: "#fef08a",
207
- 4: "#fde047",
208
- 5: "#facc15",
209
- 6: "#eab308",
210
- 7: "#ca8a04",
211
- 8: "#a16207",
212
- 9: "#854d0e",
213
- 10: "#713f12",
214
- 11: "#422006",
215
- 12: "#271304"
216
- },
217
- lime: {
218
- 1: "#f7fee7",
219
- 2: "#ecfccb",
220
- 3: "#d9f99d",
221
- 4: "#bef264",
222
- 5: "#a3e635",
223
- 6: "#84cc16",
224
- 7: "#65a30d",
225
- 8: "#4d7c0f",
226
- 9: "#3f6212",
227
- 10: "#365314",
228
- 11: "#1a2e05",
229
- 12: "#0f1b03"
230
- },
231
- green: {
232
- 1: "#f0fdf4",
233
- 2: "#dcfce7",
234
- 3: "#bbf7d0",
235
- 4: "#86efac",
236
- 5: "#4ade80",
237
- 6: "#22c55e",
238
- 7: "#16a34a",
239
- 8: "#15803d",
240
- 9: "#166534",
241
- 10: "#14532d",
242
- 11: "#052e16",
243
- 12: "#031b0d"
244
- },
245
- emerald: {
246
- 1: "#ecfdf5",
247
- 2: "#d1fae5",
248
- 3: "#a7f3d0",
249
- 4: "#6ee7b7",
250
- 5: "#34d399",
251
- 6: "#10b981",
252
- 7: "#059669",
253
- 8: "#047857",
254
- 9: "#065f46",
255
- 10: "#064e3b",
256
- 11: "#022c22",
257
- 12: "#011a14"
258
- },
259
- teal: {
260
- 1: "#f0fdfa",
261
- 2: "#ccfbf1",
262
- 3: "#99f6e4",
263
- 4: "#5eead4",
264
- 5: "#2dd4bf",
265
- 6: "#14b8a6",
266
- 7: "#0d9488",
267
- 8: "#0f766e",
268
- 9: "#115e59",
269
- 10: "#134e4a",
270
- 11: "#042f2e",
271
- 12: "#021c1b"
272
- },
273
- cyan: {
274
- 1: "#ecfeff",
275
- 2: "#cffafe",
276
- 3: "#a5f3fc",
277
- 4: "#67e8f9",
278
- 5: "#22d3ee",
279
- 6: "#06b6d4",
280
- 7: "#0891b2",
281
- 8: "#0e7490",
282
- 9: "#155e75",
283
- 10: "#164e63",
284
- 11: "#083344",
285
- 12: "#041e28"
286
- },
287
- sky: {
288
- 1: "#f0f9ff",
289
- 2: "#e0f2fe",
290
- 3: "#bae6fd",
291
- 4: "#7dd3fc",
292
- 5: "#38bdf8",
293
- 6: "#0ea5e9",
294
- 7: "#0284c7",
295
- 8: "#0369a1",
296
- 9: "#075985",
297
- 10: "#0c4a6e",
298
- 11: "#082f49",
299
- 12: "#041b2b"
300
- },
301
- blue: {
302
- 1: "#eff6ff",
303
- 2: "#dbeafe",
304
- 3: "#bfdbfe",
305
- 4: "#93c5fd",
306
- 5: "#60a5fa",
307
- 6: "#3b82f6",
308
- 7: "#2563eb",
309
- 8: "#1d4ed8",
310
- 9: "#1e40af",
311
- 10: "#1e3a8a",
312
- 11: "#172554",
313
- 12: "#0d1633"
314
- },
315
- indigo: {
316
- 1: "#eef2ff",
317
- 2: "#e0e7ff",
318
- 3: "#c7d2fe",
319
- 4: "#a5b4fc",
320
- 5: "#818cf8",
321
- 6: "#6366f1",
322
- 7: "#4f46e5",
323
- 8: "#4338ca",
324
- 9: "#3730a3",
325
- 10: "#312e81",
326
- 11: "#1e1b4b",
327
- 12: "#12102d"
328
- },
329
- violet: {
330
- 1: "#f5f3ff",
331
- 2: "#ede9fe",
332
- 3: "#ddd6fe",
333
- 4: "#c4b5fd",
334
- 5: "#a78bfa",
335
- 6: "#8b5cf6",
336
- 7: "#7c3aed",
337
- 8: "#6d28d9",
338
- 9: "#5b21b6",
339
- 10: "#4c1d95",
340
- 11: "#2e1065",
341
- 12: "#1c0a3d"
342
- },
343
- purple: {
344
- 1: "#faf5ff",
345
- 2: "#f3e8ff",
346
- 3: "#e9d5ff",
347
- 4: "#d8b4fe",
348
- 5: "#c084fc",
349
- 6: "#a855f7",
350
- 7: "#9333ea",
351
- 8: "#7e22ce",
352
- 9: "#6b21a8",
353
- 10: "#581c87",
354
- 11: "#3b0764",
355
- 12: "#23043c"
356
- },
357
- fuchsia: {
358
- 1: "#fdf4ff",
359
- 2: "#fae8ff",
360
- 3: "#f5d0fe",
361
- 4: "#f0abfc",
362
- 5: "#e879f9",
363
- 6: "#d946ef",
364
- 7: "#c026d3",
365
- 8: "#a21caf",
366
- 9: "#86198f",
367
- 10: "#701a75",
368
- 11: "#4a044e",
369
- 12: "#2d022f"
370
- },
371
- pink: {
372
- 1: "#fdf2f8",
373
- 2: "#fce7f3",
374
- 3: "#fbcfe8",
375
- 4: "#f9a8d4",
376
- 5: "#f472b6",
377
- 6: "#ec4899",
378
- 7: "#db2777",
379
- 8: "#be185d",
380
- 9: "#9d174d",
381
- 10: "#831843",
382
- 11: "#500724",
383
- 12: "#300415"
384
- },
385
- rose: {
386
- 1: "#fff1f2",
387
- 2: "#ffe4e6",
388
- 3: "#fecdd3",
389
- 4: "#fda4af",
390
- 5: "#fb7185",
391
- 6: "#f43f5e",
392
- 7: "#e11d48",
393
- 8: "#be123c",
394
- 9: "#9f1239",
395
- 10: "#881337",
396
- 11: "#4c0519",
397
- 12: "#2d030e"
398
- }
399
- };
400
- var defaultTheme = {
401
- colors: defaultColors,
402
- // 1 unit = 4px
403
- spacing: {
404
- px: 1,
405
- 0: 0,
406
- "0.5": 2,
407
- 1: 4,
408
- "1.5": 6,
409
- 2: 8,
410
- "2.5": 10,
411
- 3: 12,
412
- "3.5": 14,
413
- 4: 16,
414
- 5: 20,
415
- 6: 24,
416
- 7: 28,
417
- 8: 32,
418
- 9: 36,
419
- 10: 40,
420
- 11: 44,
421
- 12: 48,
422
- 14: 56,
423
- 16: 64,
424
- 20: 80,
425
- 24: 96,
426
- 28: 112,
427
- 32: 128,
428
- 36: 144,
429
- 40: 160,
430
- 44: 176,
431
- 48: 192,
432
- 52: 208,
433
- 56: 224,
434
- 60: 240,
435
- 64: 256,
436
- 72: 288,
437
- 80: 320,
438
- 96: 384,
439
- auto: "auto",
440
- full: "100%",
441
- "1/2": "50%",
442
- "1/3": "33.333333%",
443
- "2/3": "66.666667%",
444
- "1/4": "25%",
445
- "3/4": "75%",
446
- // Dynamic viewport unit — see the comment on the 'screen' standalone utilities
447
- // in utilities.ts for why dvh beats vh on mobile. Used by h-screen (w-screen
448
- // has its own standalone entry so it never reaches this spacing lookup).
449
- screen: "100dvh",
450
- min: "min-content",
451
- max: "max-content",
452
- fit: "fit-content"
453
- },
454
- fontSize: {
455
- xs: 12,
456
- sm: 14,
457
- base: 16,
458
- lg: 18,
459
- xl: 20,
460
- "2xl": 24,
461
- "3xl": 30,
462
- "4xl": 36,
463
- "5xl": 48,
464
- "6xl": 60,
465
- "7xl": 72,
466
- "8xl": 96,
467
- "9xl": 128
468
- },
469
- fontFamily: {
470
- sans: "System",
471
- mono: "Courier New",
472
- serif: "Georgia"
473
- },
474
- fontWeight: {
475
- thin: "100",
476
- extralight: "200",
477
- light: "300",
478
- normal: "400",
479
- medium: "500",
480
- semibold: "600",
481
- bold: "700",
482
- extrabold: "800",
483
- black: "900"
484
- },
485
- borderRadius: {
486
- none: 0,
487
- sm: 2,
488
- DEFAULT: 4,
489
- md: 6,
490
- lg: 8,
491
- xl: 12,
492
- "2xl": 16,
493
- "3xl": 24,
494
- full: 9999
495
- },
496
- borderWidth: {
497
- DEFAULT: 1,
498
- 0: 0,
499
- 2: 2,
500
- 4: 4,
501
- 8: 8
502
- },
503
- opacity: {
504
- 0: 0,
505
- 5: 0.05,
506
- 10: 0.1,
507
- 15: 0.15,
508
- 20: 0.2,
509
- 25: 0.25,
510
- 30: 0.3,
511
- 40: 0.4,
512
- 50: 0.5,
513
- 60: 0.6,
514
- 70: 0.7,
515
- 75: 0.75,
516
- 80: 0.8,
517
- 90: 0.9,
518
- 95: 0.95,
519
- 100: 1
520
- },
521
- lineHeight: {
522
- none: 1,
523
- tight: 1.25,
524
- snug: 1.375,
525
- normal: 1.5,
526
- relaxed: 1.625,
527
- loose: 2,
528
- // Pixel values stored as strings; styleValueToCSS passes them through, toNativeValue strips the unit
529
- 3: "12px",
530
- 4: "16px",
531
- 5: "20px",
532
- 6: "24px",
533
- 7: "28px",
534
- 8: "32px",
535
- 9: "36px",
536
- 10: "40px"
537
- },
538
- letterSpacing: {
539
- tighter: -0.8,
540
- tight: -0.4,
541
- normal: 0,
542
- wide: 0.4,
543
- wider: 0.8,
544
- widest: 1.6
545
- },
546
- zIndex: {
547
- auto: "auto",
548
- 0: 0,
549
- 10: 10,
550
- 20: 20,
551
- 30: 30,
552
- 40: 40,
553
- 50: 50
554
- },
555
- flex: {
556
- 1: 1,
557
- auto: "auto",
558
- // CSS: flex: auto = 1 1 auto; native: mapped to 1 in resolver
559
- initial: "initial",
560
- // CSS: flex: initial = 0 1 auto; native: mapped to 1
561
- none: "none"
562
- // CSS: flex: none = 0 0 auto; native: mapped to 0
563
- },
564
- shadow: {
565
- sm: {
566
- shadowColor: "#000",
567
- shadowOffset: { width: 0, height: 1 },
568
- shadowOpacity: 0.05,
569
- shadowRadius: 2,
570
- elevation: 1
571
- },
572
- DEFAULT: {
573
- shadowColor: "#000",
574
- shadowOffset: { width: 0, height: 2 },
575
- shadowOpacity: 0.1,
576
- shadowRadius: 4,
577
- elevation: 2
578
- },
579
- md: {
580
- shadowColor: "#000",
581
- shadowOffset: { width: 0, height: 4 },
582
- shadowOpacity: 0.1,
583
- shadowRadius: 8,
584
- elevation: 3
585
- },
586
- lg: {
587
- shadowColor: "#000",
588
- shadowOffset: { width: 0, height: 8 },
589
- shadowOpacity: 0.1,
590
- shadowRadius: 15,
591
- elevation: 4
592
- },
593
- xl: {
594
- shadowColor: "#000",
595
- shadowOffset: { width: 0, height: 16 },
596
- shadowOpacity: 0.1,
597
- shadowRadius: 24,
598
- elevation: 6
599
- },
600
- "2xl": {
601
- shadowColor: "#000",
602
- shadowOffset: { width: 0, height: 24 },
603
- shadowOpacity: 0.25,
604
- shadowRadius: 48,
605
- elevation: 8
606
- },
607
- none: {
608
- shadowColor: "transparent",
609
- shadowOffset: { width: 0, height: 0 },
610
- shadowOpacity: 0,
611
- shadowRadius: 0,
612
- elevation: 0
613
- }
614
- },
615
- screens: {
616
- sm: 576,
617
- // small tablets / large phones (landscape)
618
- md: 768,
619
- // tablets (iPad and up)
620
- lg: 1024,
621
- // large tablets (iPad Pro)
622
- xl: 1280,
623
- // desktop
624
- "2xl": 1536
625
- },
626
- // Empty by default — the 4 built-in presets (spin/ping/pulse/bounce) are handled
627
- // directly in the `animate` resolver, not through this theme table. This exists so
628
- // user-defined ones (kbach.config.js theme.extend.keyframes/animation) merge in
629
- // alongside them without needing to redeclare the built-ins.
630
- keyframes: {},
631
- animation: {}
632
- };
87
+ // src/core/colorValue.ts
88
+ function isModeAwareColor(v) {
89
+ return typeof v === "object" && v !== null && "light" in v && "dark" in v;
90
+ }
633
91
 
634
- // src/core/reset.ts
635
- var RESET_STYLE_ID = "kbach-reset";
636
- var BASE_RESET = [
637
- // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
638
- // border-width: 0 keeps all elements borderless by default.
639
- "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }",
640
- "body { margin: 0; padding: 0; }",
641
- "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
642
- "p { margin: 0; }",
643
- "a { color: inherit; text-decoration: none; }",
644
- "ul, ol { margin: 0; padding: 0; list-style: none; }",
645
- "img, video, svg { display: block; max-width: 100%; }",
646
- // appearance: none is deliberately NOT applied to checkbox/radio/select below —
647
- // stripping it hides their native checkmark/arrow with nothing rendered in its
648
- // place, leaving an invisible checkbox or an arrow-less <select> that looks like
649
- // plain text. Text-like inputs, textarea, and button don't have that problem
650
- // (their native chrome is just a skin around content utilities can fully
651
- // restyle), so they keep the blank-canvas treatment.
652
- "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
653
- // Native checkbox/radio/range still get typography + spacing normalized, and
654
- // accent-color re-themes their native indicator to the current text color
655
- // instead of the browser/OS default blue, so they stay on-brand without
656
- // needing to be rebuilt from scratch.
657
- "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
658
- // select keeps its native chrome (border/background/arrow are all part of the
659
- // same OS-drawn widget that appearance: none would blank out) — only typography
660
- // and spacing are normalized so it still matches surrounding text.
661
- "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
662
- "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
663
- "button, [role='button'] { cursor: pointer; }",
664
- ":disabled { cursor: default; }",
665
- "textarea { resize: vertical; }",
666
- // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
667
- // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
668
- "::placeholder { opacity: 1; }",
669
- "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
670
- "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
671
- "fieldset { padding: 0; margin: 0; }",
672
- "table { border-collapse: collapse; border-spacing: 0; }"
673
- ].join("\n");
92
+ // src/core/platform.ts
93
+ var isWeb = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
94
+ var isNative = !isWeb && (typeof globalThis.HermesInternal !== "undefined" || typeof globalThis.__fbBatchedBridge !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative" || typeof globalThis.__REACT_NATIVE__ !== "undefined");
95
+ var _resolveTargetOverride = null;
96
+ function setResolveTarget(target) {
97
+ _resolveTargetOverride = target;
98
+ }
99
+ function getEffectiveIsWeb() {
100
+ if (_resolveTargetOverride) return _resolveTargetOverride === "web";
101
+ return isWeb || !isNative;
102
+ }
103
+ function toNativeValue(raw) {
104
+ if (/^-?\d+(\.\d+)?px$/.test(raw)) return parseFloat(raw);
105
+ if (/^-?\d+(\.\d+)?rem$/.test(raw)) return parseFloat(raw) * 16;
106
+ if (/^-?\d+(\.\d+)?em$/.test(raw)) return parseFloat(raw) * 16;
107
+ if (/^-?\d+(\.\d+)?$/.test(raw)) return parseFloat(raw);
108
+ return raw;
109
+ }
110
+ function escapeCSSSelector(cls) {
111
+ return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
112
+ }
113
+
114
+ // src/core/devWarn.ts
115
+ function kbachWarn(message) {
116
+ console.warn(
117
+ "%c[kbach]%c " + message,
118
+ "color:#8b5cf6;font-weight:700",
119
+ "color:inherit;font-weight:400"
120
+ );
121
+ }
674
122
 
675
123
  // src/core/cache.ts
676
124
  var LRUCache = class {
@@ -713,37 +161,6 @@ var LRUCache = class {
713
161
  }
714
162
  };
715
163
 
716
- // src/core/platform.ts
717
- var isWeb = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
718
- var isNative = !isWeb && (typeof globalThis.HermesInternal !== "undefined" || typeof globalThis.__fbBatchedBridge !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative" || typeof globalThis.__REACT_NATIVE__ !== "undefined");
719
- var _resolveTargetOverride = null;
720
- function setResolveTarget(target) {
721
- _resolveTargetOverride = target;
722
- }
723
- function getEffectiveIsWeb() {
724
- if (_resolveTargetOverride) return _resolveTargetOverride === "web";
725
- return isWeb || !isNative;
726
- }
727
- function toNativeValue(raw) {
728
- if (/^-?\d+(\.\d+)?px$/.test(raw)) return parseFloat(raw);
729
- if (/^-?\d+(\.\d+)?rem$/.test(raw)) return parseFloat(raw) * 16;
730
- if (/^-?\d+(\.\d+)?em$/.test(raw)) return parseFloat(raw) * 16;
731
- if (/^-?\d+(\.\d+)?$/.test(raw)) return parseFloat(raw);
732
- return raw;
733
- }
734
- function escapeCSSSelector(cls) {
735
- return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
736
- }
737
-
738
- // src/core/devWarn.ts
739
- function kbachWarn(message) {
740
- console.warn(
741
- "%c[kbach]%c " + message,
742
- "color:#8b5cf6;font-weight:700",
743
- "color:inherit;font-weight:400"
744
- );
745
- }
746
-
747
164
  // src/core/registry.ts
748
165
  var BUILTIN_MODIFIERS = {
749
166
  // ── Mode ─────────────────────────────────────────────────────────────────
@@ -884,6 +301,9 @@ function matchModifier(name, isDark, state, breakpoints) {
884
301
  }
885
302
 
886
303
  // src/core/resolvers/color.ts
304
+ function pickModeAwareFallback(v) {
305
+ return isModeAwareColor(v) ? v.light : v;
306
+ }
887
307
  function resolveColor(value, colors, isArbitrary) {
888
308
  if (isArbitrary) return value;
889
309
  const slashIdx = value.indexOf("/");
@@ -895,15 +315,17 @@ function resolveColor(value, colors, isArbitrary) {
895
315
  } else if (colorPart in colors) {
896
316
  const entry = colors[colorPart];
897
317
  if (typeof entry === "string") hex = entry;
898
- else if (typeof entry === "object" && "6" in entry) hex = entry["6"];
318
+ else if (isModeAwareColor(entry)) hex = pickModeAwareFallback(entry);
319
+ else if (typeof entry === "object" && "6" in entry) hex = pickModeAwareFallback(entry["6"]);
899
320
  } else {
900
321
  const lastDash = colorPart.lastIndexOf("-");
901
322
  if (lastDash > 0) {
902
323
  const colorName = colorPart.slice(0, lastDash);
903
324
  const shade = colorPart.slice(lastDash + 1);
904
325
  const scale = colors[colorName];
905
- if (scale && typeof scale === "object" && shade in scale) {
906
- hex = scale[shade] ?? null;
326
+ if (scale && typeof scale === "object" && !isModeAwareColor(scale) && shade in scale) {
327
+ const shadeVal = scale[shade];
328
+ hex = shadeVal !== void 0 ? pickModeAwareFallback(shadeVal) : null;
907
329
  }
908
330
  }
909
331
  }
@@ -1988,851 +1410,1497 @@ var layoutResolvers = {
1988
1410
  if (!value) return null;
1989
1411
  if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1990
1412
  const n = parseInt(value, 10);
1991
- if (isNaN(n) || n < 1 || n > 12) return null;
1992
- return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1413
+ if (isNaN(n) || n < 1 || n > 12) return null;
1414
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1415
+ },
1416
+ // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1417
+ "grid-cols": ({ value, isArbitrary }) => {
1418
+ if (!getEffectiveIsWeb()) return null;
1419
+ if (!value) return null;
1420
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1421
+ if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1422
+ const n = parseInt(value, 10);
1423
+ if (isNaN(n) || n < 1 || n > 12) return null;
1424
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1425
+ },
1426
+ "grid-rows": ({ value, isArbitrary }) => {
1427
+ if (!getEffectiveIsWeb()) return null;
1428
+ if (!value) return null;
1429
+ if (isArbitrary) return { gridTemplateRows: value };
1430
+ if (value === "none") return { gridTemplateRows: "none" };
1431
+ const n = parseInt(value, 10);
1432
+ if (isNaN(n) || n < 1) return null;
1433
+ return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
1434
+ },
1435
+ // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1436
+ "grid-flow": ({ value }) => {
1437
+ if (!getEffectiveIsWeb()) return null;
1438
+ const flows = {
1439
+ row: "row",
1440
+ col: "column",
1441
+ dense: "dense",
1442
+ "row-dense": "row dense",
1443
+ "col-dense": "column dense"
1444
+ };
1445
+ const flow = flows[value];
1446
+ return flow ? { gridAutoFlow: flow } : null;
1447
+ },
1448
+ // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1449
+ "auto-cols": ({ value, isArbitrary }) => {
1450
+ if (!getEffectiveIsWeb()) return null;
1451
+ if (isArbitrary) return { gridAutoColumns: value };
1452
+ const presets = {
1453
+ auto: "auto",
1454
+ min: "min-content",
1455
+ max: "max-content",
1456
+ fr: "minmax(0, 1fr)"
1457
+ };
1458
+ const v = presets[value];
1459
+ return v ? { gridAutoColumns: v } : null;
1460
+ },
1461
+ "auto-rows": ({ value, isArbitrary }) => {
1462
+ if (!getEffectiveIsWeb()) return null;
1463
+ if (isArbitrary) return { gridAutoRows: value };
1464
+ const presets = {
1465
+ auto: "auto",
1466
+ min: "min-content",
1467
+ max: "max-content",
1468
+ fr: "minmax(0, 1fr)"
1469
+ };
1470
+ const v = presets[value];
1471
+ return v ? { gridAutoRows: v } : null;
1472
+ },
1473
+ // ── Grid column placement (web-only) ──────────────────────────────────────
1474
+ "col-span": ({ value }) => {
1475
+ if (!getEffectiveIsWeb()) return null;
1476
+ if (value === "full") return { gridColumn: "1 / -1" };
1477
+ const n = parseInt(value, 10);
1478
+ return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
1479
+ },
1480
+ "col-start": ({ value, isArbitrary }) => {
1481
+ if (!getEffectiveIsWeb()) return null;
1482
+ if (value === "auto") return { gridColumnStart: "auto" };
1483
+ if (isArbitrary) return { gridColumnStart: value };
1484
+ const n = parseInt(value, 10);
1485
+ return isNaN(n) ? null : { gridColumnStart: n };
1486
+ },
1487
+ "col-end": ({ value, isArbitrary }) => {
1488
+ if (!getEffectiveIsWeb()) return null;
1489
+ if (value === "auto") return { gridColumnEnd: "auto" };
1490
+ if (isArbitrary) return { gridColumnEnd: value };
1491
+ const n = parseInt(value, 10);
1492
+ return isNaN(n) ? null : { gridColumnEnd: n };
1493
+ },
1494
+ col: ({ value, isArbitrary }) => {
1495
+ if (!getEffectiveIsWeb()) return null;
1496
+ if (!value || value === "auto") return { gridColumn: "auto" };
1497
+ if (isArbitrary) return { gridColumn: value };
1498
+ return null;
1499
+ },
1500
+ // ── Grid row placement (web-only) ─────────────────────────────────────────
1501
+ "row-span": ({ value }) => {
1502
+ if (!getEffectiveIsWeb()) return null;
1503
+ if (value === "full") return { gridRow: "1 / -1" };
1504
+ const n = parseInt(value, 10);
1505
+ return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
1993
1506
  },
1994
- // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1995
- "grid-cols": ({ value, isArbitrary }) => {
1507
+ "row-start": ({ value, isArbitrary }) => {
1996
1508
  if (!getEffectiveIsWeb()) return null;
1997
- if (!value) return null;
1998
- if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1999
- if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1509
+ if (value === "auto") return { gridRowStart: "auto" };
1510
+ if (isArbitrary) return { gridRowStart: value };
2000
1511
  const n = parseInt(value, 10);
2001
- if (isNaN(n) || n < 1 || n > 12) return null;
2002
- return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1512
+ return isNaN(n) ? null : { gridRowStart: n };
2003
1513
  },
2004
- "grid-rows": ({ value, isArbitrary }) => {
1514
+ "row-end": ({ value, isArbitrary }) => {
2005
1515
  if (!getEffectiveIsWeb()) return null;
2006
- if (!value) return null;
2007
- if (isArbitrary) return { gridTemplateRows: value };
2008
- if (value === "none") return { gridTemplateRows: "none" };
1516
+ if (value === "auto") return { gridRowEnd: "auto" };
1517
+ if (isArbitrary) return { gridRowEnd: value };
2009
1518
  const n = parseInt(value, 10);
2010
- if (isNaN(n) || n < 1) return null;
2011
- return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
1519
+ return isNaN(n) ? null : { gridRowEnd: n };
2012
1520
  },
2013
- // ── Grid auto flow (web-only) ─────────────────────────────────────────────
2014
- "grid-flow": ({ value }) => {
1521
+ row: ({ value, isArbitrary }) => {
2015
1522
  if (!getEffectiveIsWeb()) return null;
2016
- const flows = {
2017
- row: "row",
2018
- col: "column",
2019
- dense: "dense",
2020
- "row-dense": "row dense",
2021
- "col-dense": "column dense"
1523
+ if (!value || value === "auto") return { gridRow: "auto" };
1524
+ if (isArbitrary) return { gridRow: value };
1525
+ return null;
1526
+ },
1527
+ // ── Grid alignment (web-only) ──────────────────────────────────────────────
1528
+ // place-items is shorthand for align-items + justify-items on a grid container
1529
+ "place-items": ({ value }) => {
1530
+ if (!getEffectiveIsWeb()) return null;
1531
+ const map = {
1532
+ start: "start",
1533
+ end: "end",
1534
+ center: "center",
1535
+ stretch: "stretch",
1536
+ baseline: "baseline"
2022
1537
  };
2023
- const flow = flows[value];
2024
- return flow ? { gridAutoFlow: flow } : null;
1538
+ return map[value] ? { placeItems: map[value] } : null;
2025
1539
  },
2026
- // ── Grid auto sizing (web-only) ───────────────────────────────────────────
2027
- "auto-cols": ({ value, isArbitrary }) => {
1540
+ // place-content is shorthand for align-content + justify-content on a grid container
1541
+ "place-content": ({ value }) => {
2028
1542
  if (!getEffectiveIsWeb()) return null;
2029
- if (isArbitrary) return { gridAutoColumns: value };
2030
- const presets = {
1543
+ const map = {
1544
+ start: "start",
1545
+ end: "end",
1546
+ center: "center",
1547
+ stretch: "stretch",
1548
+ between: "space-between",
1549
+ around: "space-around",
1550
+ evenly: "space-evenly",
1551
+ baseline: "baseline"
1552
+ };
1553
+ return map[value] ? { placeContent: map[value] } : null;
1554
+ },
1555
+ // justify-items controls inline-axis alignment of grid items within their cells
1556
+ "justify-items": ({ value }) => {
1557
+ if (!getEffectiveIsWeb()) return null;
1558
+ const map = {
1559
+ start: "start",
1560
+ end: "end",
1561
+ center: "center",
1562
+ stretch: "stretch"
1563
+ };
1564
+ return map[value] ? { justifyItems: map[value] } : null;
1565
+ },
1566
+ // place-self is shorthand for align-self + justify-self on a grid item
1567
+ "place-self": ({ value }) => {
1568
+ if (!getEffectiveIsWeb()) return null;
1569
+ const map = {
2031
1570
  auto: "auto",
2032
- min: "min-content",
2033
- max: "max-content",
2034
- fr: "minmax(0, 1fr)"
1571
+ start: "start",
1572
+ end: "end",
1573
+ center: "center",
1574
+ stretch: "stretch"
1575
+ };
1576
+ return map[value] ? { placeSelf: map[value] } : null;
1577
+ },
1578
+ // justify-self controls inline-axis self-alignment of a grid item
1579
+ "justify-self": ({ value }) => {
1580
+ if (!getEffectiveIsWeb()) return null;
1581
+ const map = {
1582
+ auto: "auto",
1583
+ start: "start",
1584
+ end: "end",
1585
+ center: "center",
1586
+ stretch: "stretch"
2035
1587
  };
1588
+ return map[value] ? { justifySelf: map[value] } : null;
1589
+ },
1590
+ // ── Flex ───────────────────────────────────────────────────────────────────
1591
+ // flex-1/flex-auto/flex-none/grow/shrink are item-level sizing properties —
1592
+ // deliberately NOT implying display:flex here. See the "Flex grow / shrink"
1593
+ // comment in the standalone table above for why: these already work via the
1594
+ // PARENT's flex context regardless of this element's own display, so
1595
+ // forcing display:flex here would only be a surprising, unrelated side
1596
+ // effect on this element's OWN children.
1597
+ flex: ({ value, isArbitrary }, { flex }) => {
1598
+ if (!value) return { display: "flex" };
1599
+ if (isArbitrary) {
1600
+ const v = parseFloat(value);
1601
+ return isNaN(v) ? null : { flex: v };
1602
+ }
1603
+ if (value in flex) {
1604
+ const v = flex[value];
1605
+ if (typeof v === "string" && !getEffectiveIsWeb()) {
1606
+ if (v === "auto" || v === "initial") return { flex: 1 };
1607
+ if (v === "none") return { flex: 0 };
1608
+ return null;
1609
+ }
1610
+ return { flex: v };
1611
+ }
1612
+ const n = parseFloat(value);
1613
+ return isNaN(n) ? null : { flex: n };
1614
+ },
1615
+ grow: ({ value }, _) => {
1616
+ if (!value) return { flexGrow: 1 };
1617
+ const n = parseFloat(value);
1618
+ return { flexGrow: isNaN(n) ? 1 : n };
1619
+ },
1620
+ shrink: ({ value }, _) => {
1621
+ if (!value) return { flexShrink: 1 };
1622
+ const n = parseFloat(value);
1623
+ return { flexShrink: isNaN(n) ? 1 : n };
1624
+ },
1625
+ order: ({ value, negative }, _) => {
1626
+ const n = parseInt(value, 10);
1627
+ return isNaN(n) ? null : { order: negative ? -n : n };
1628
+ },
1629
+ // ── Z-index ────────────────────────────────────────────────────────────────
1630
+ z: ({ value, isArbitrary }, { zIndex }) => {
1631
+ if (isArbitrary) {
1632
+ const n2 = parseInt(value);
1633
+ return isNaN(n2) ? null : { zIndex: n2 };
1634
+ }
1635
+ const v = zIndex[value];
1636
+ if (v !== void 0) {
1637
+ if (v === "auto") return getEffectiveIsWeb() ? { zIndex: "auto" } : null;
1638
+ return { zIndex: v };
1639
+ }
1640
+ const n = parseInt(value);
1641
+ return isNaN(n) ? null : { zIndex: n };
1642
+ },
1643
+ // ── Aspect ratio ───────────────────────────────────────────────────────────
1644
+ aspect: ({ value, isArbitrary }) => {
1645
+ if (isArbitrary) return { aspectRatio: value };
1646
+ const presets = { auto: "auto", square: 1, video: 16 / 9 };
1647
+ if (!(value in presets)) return null;
2036
1648
  const v = presets[value];
2037
- return v ? { gridAutoColumns: v } : null;
1649
+ if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
1650
+ return { aspectRatio: v };
1651
+ },
1652
+ // ── Columns (web-only) ─────────────────────────────────────────────────────
1653
+ columns: ({ value, isArbitrary }) => {
1654
+ if (!getEffectiveIsWeb()) return null;
1655
+ if (isArbitrary) return { columnCount: value };
1656
+ if (value === "auto") return { columnCount: "auto" };
1657
+ const n = parseInt(value, 10);
1658
+ if (!isNaN(n)) return { columnCount: n };
1659
+ const widths = {
1660
+ "3xs": "16rem",
1661
+ "2xs": "18rem",
1662
+ xs: "20rem",
1663
+ sm: "24rem",
1664
+ md: "28rem",
1665
+ lg: "32rem",
1666
+ xl: "36rem",
1667
+ "2xl": "42rem",
1668
+ "3xl": "48rem",
1669
+ "4xl": "56rem",
1670
+ "5xl": "64rem",
1671
+ "6xl": "72rem",
1672
+ "7xl": "80rem"
1673
+ };
1674
+ return widths[value] ? { columnWidth: widths[value] } : null;
1675
+ }
1676
+ };
1677
+
1678
+ // src/core/resolvers/typography.ts
1679
+ function resolveFontSize(value, fontSizes, isArbitrary) {
1680
+ if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
1681
+ return fontSizes[value] ?? null;
1682
+ }
1683
+ var typographyResolvers = {
1684
+ // ── Text ───────────────────────────────────────────────────────────────────
1685
+ text: ({ value, isArbitrary }, { colors, fontSize }) => {
1686
+ const size = resolveFontSize(value, fontSize, false);
1687
+ if (!isArbitrary && size !== null) return { fontSize: size };
1688
+ if (isArbitrary) {
1689
+ if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1690
+ return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1691
+ }
1692
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1693
+ }
1694
+ const color = resolveColor(value, colors, false);
1695
+ if (!color) return null;
1696
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1697
+ },
1698
+ "text-opacity": ({ value, isArbitrary }, _) => {
1699
+ if (!getEffectiveIsWeb()) return null;
1700
+ const n = parseFloat(value);
1701
+ if (isNaN(n)) return null;
1702
+ const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
1703
+ return { "--text-opacity": v };
1704
+ },
1705
+ // ── Text decoration advanced (web-only) ───────────────────────────────────
1706
+ decoration: ({ value, isArbitrary }, { colors }) => {
1707
+ if (!getEffectiveIsWeb()) return null;
1708
+ if (isArbitrary) {
1709
+ if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
1710
+ return { textDecorationColor: value };
1711
+ }
1712
+ const thickTokens = {
1713
+ auto: "auto",
1714
+ "from-font": "from-font",
1715
+ "0": "0px",
1716
+ "1": "1px",
1717
+ "2": "2px",
1718
+ "4": "4px",
1719
+ "8": "8px"
1720
+ };
1721
+ if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
1722
+ const styleTokens = {
1723
+ solid: "solid",
1724
+ dashed: "dashed",
1725
+ dotted: "dotted",
1726
+ double: "double",
1727
+ wavy: "wavy"
1728
+ };
1729
+ if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
1730
+ const color = resolveColor(value, colors, false);
1731
+ return color ? { textDecorationColor: color } : null;
2038
1732
  },
2039
- "auto-rows": ({ value, isArbitrary }) => {
1733
+ "underline-offset": ({ value, isArbitrary }) => {
2040
1734
  if (!getEffectiveIsWeb()) return null;
2041
- if (isArbitrary) return { gridAutoRows: value };
2042
- const presets = {
1735
+ if (isArbitrary) return { textUnderlineOffset: value };
1736
+ const offsets = {
2043
1737
  auto: "auto",
2044
- min: "min-content",
2045
- max: "max-content",
2046
- fr: "minmax(0, 1fr)"
1738
+ "0": "0px",
1739
+ "1": "1px",
1740
+ "2": "2px",
1741
+ "4": "4px",
1742
+ "8": "8px"
2047
1743
  };
2048
- const v = presets[value];
2049
- return v ? { gridAutoRows: v } : null;
1744
+ return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
2050
1745
  },
2051
- // ── Grid column placement (web-only) ──────────────────────────────────────
2052
- "col-span": ({ value }) => {
1746
+ // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
1747
+ // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
1748
+ // the literal string 'you can see it' quotes included), so it passes straight through as a
1749
+ // valid `content: '*'` CSS value with no extra wrapping needed.
1750
+ content: ({ value, isArbitrary }) => {
2053
1751
  if (!getEffectiveIsWeb()) return null;
2054
- if (value === "full") return { gridColumn: "1 / -1" };
2055
- const n = parseInt(value, 10);
2056
- return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
1752
+ if (isArbitrary) return { content: value };
1753
+ const presets = { none: "none" };
1754
+ return value in presets ? { content: presets[value] } : null;
2057
1755
  },
2058
- "col-start": ({ value, isArbitrary }) => {
2059
- if (!getEffectiveIsWeb()) return null;
2060
- if (value === "auto") return { gridColumnStart: "auto" };
2061
- if (isArbitrary) return { gridColumnStart: value };
2062
- const n = parseInt(value, 10);
2063
- return isNaN(n) ? null : { gridColumnStart: n };
1756
+ // ── Font ───────────────────────────────────────────────────────────────────
1757
+ font: ({ value, isArbitrary }, { fontFamily, fontWeight }) => {
1758
+ if (isArbitrary) return { fontFamily: value };
1759
+ if (value in fontFamily) {
1760
+ const ff = fontFamily[value];
1761
+ return { fontFamily: Array.isArray(ff) ? ff.join(", ") : ff };
1762
+ }
1763
+ if (value in fontWeight) return { fontWeight: String(fontWeight[value]) };
1764
+ return null;
2064
1765
  },
2065
- "col-end": ({ value, isArbitrary }) => {
1766
+ // ── Line height ────────────────────────────────────────────────────────────
1767
+ leading: ({ value, isArbitrary }, { lineHeight }) => {
1768
+ if (isArbitrary) return { lineHeight: getEffectiveIsWeb() ? value : toNativeValue(value) };
1769
+ const v = lineHeight[value];
1770
+ if (v === void 0) return null;
1771
+ if (typeof v === "string" && !getEffectiveIsWeb()) return { lineHeight: toNativeValue(v) };
1772
+ return { lineHeight: v };
1773
+ },
1774
+ // ── Letter spacing ─────────────────────────────────────────────────────────
1775
+ tracking: ({ value, isArbitrary }, { letterSpacing }) => {
1776
+ if (isArbitrary) return { letterSpacing: getEffectiveIsWeb() ? value : toNativeValue(value) };
1777
+ const v = letterSpacing[value];
1778
+ return v !== void 0 ? { letterSpacing: v } : null;
1779
+ },
1780
+ // ── Line-clamp (web-only) ─────────────────────────────────────────────────
1781
+ "line-clamp": ({ value }) => {
2066
1782
  if (!getEffectiveIsWeb()) return null;
2067
- if (value === "auto") return { gridColumnEnd: "auto" };
2068
- if (isArbitrary) return { gridColumnEnd: value };
1783
+ if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
2069
1784
  const n = parseInt(value, 10);
2070
- return isNaN(n) ? null : { gridColumnEnd: n };
1785
+ if (isNaN(n) || n < 1) return null;
1786
+ return {
1787
+ overflow: "hidden",
1788
+ display: "-webkit-box",
1789
+ WebkitBoxOrient: "vertical",
1790
+ WebkitLineClamp: n
1791
+ };
1792
+ }
1793
+ };
1794
+
1795
+ // src/core/resolvers/transform.ts
1796
+ var transformResolvers = {
1797
+ // ── Transform extras (web-only) ───────────────────────────────────────────
1798
+ "skew-x": ({ value, negative, isArbitrary }) => {
1799
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
1800
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
1801
+ return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
2071
1802
  },
2072
- col: ({ value, isArbitrary }) => {
1803
+ "skew-y": ({ value, negative, isArbitrary }) => {
1804
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
1805
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
1806
+ return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
1807
+ },
1808
+ // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
1809
+ transform: ({ value, isArbitrary }) => {
2073
1810
  if (!getEffectiveIsWeb()) return null;
2074
- if (!value || value === "auto") return { gridColumn: "auto" };
2075
- if (isArbitrary) return { gridColumn: value };
1811
+ if (isArbitrary) return { transform: value.replace(/_/g, " ") };
1812
+ if (value === "none") return { transform: "none" };
2076
1813
  return null;
2077
1814
  },
2078
- // ── Grid row placement (web-only) ─────────────────────────────────────────
2079
- "row-span": ({ value }) => {
1815
+ // Transform origin
1816
+ origin: ({ value, isArbitrary }) => {
2080
1817
  if (!getEffectiveIsWeb()) return null;
2081
- if (value === "full") return { gridRow: "1 / -1" };
2082
- const n = parseInt(value, 10);
2083
- return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
1818
+ if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
1819
+ const origins = {
1820
+ center: "center",
1821
+ top: "top",
1822
+ "top-right": "top right",
1823
+ right: "right",
1824
+ "bottom-right": "bottom right",
1825
+ bottom: "bottom",
1826
+ "bottom-left": "bottom left",
1827
+ left: "left",
1828
+ "top-left": "top left"
1829
+ };
1830
+ return origins[value] ? { transformOrigin: origins[value] } : null;
2084
1831
  },
2085
- "row-start": ({ value, isArbitrary }) => {
2086
- if (!getEffectiveIsWeb()) return null;
2087
- if (value === "auto") return { gridRowStart: "auto" };
2088
- if (isArbitrary) return { gridRowStart: value };
2089
- const n = parseInt(value, 10);
2090
- return isNaN(n) ? null : { gridRowStart: n };
1832
+ // ── Scale ──────────────────────────────────────────────────────────────────
1833
+ // Non-arbitrary: scale-150 → value='150' → 150/100 = 1.5
1834
+ // Arbitrary: scale-[1.5] → value='1.5' → used as-is (already the factor)
1835
+ scale: ({ value, isArbitrary }) => {
1836
+ const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
1837
+ if (isNaN(n)) return null;
1838
+ return getEffectiveIsWeb() ? { transform: `scale(${n})` } : { transform: [{ scale: n }] };
2091
1839
  },
2092
- "row-end": ({ value, isArbitrary }) => {
2093
- if (!getEffectiveIsWeb()) return null;
2094
- if (value === "auto") return { gridRowEnd: "auto" };
2095
- if (isArbitrary) return { gridRowEnd: value };
2096
- const n = parseInt(value, 10);
2097
- return isNaN(n) ? null : { gridRowEnd: n };
1840
+ "scale-x": ({ value, isArbitrary }) => {
1841
+ const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
1842
+ if (isNaN(n)) return null;
1843
+ return getEffectiveIsWeb() ? { transform: `scaleX(${n})` } : { transform: [{ scaleX: n }] };
2098
1844
  },
2099
- row: ({ value, isArbitrary }) => {
1845
+ "scale-y": ({ value, isArbitrary }) => {
1846
+ const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
1847
+ if (isNaN(n)) return null;
1848
+ return getEffectiveIsWeb() ? { transform: `scaleY(${n})` } : { transform: [{ scaleY: n }] };
1849
+ },
1850
+ // ── Rotate ─────────────────────────────────────────────────────────────────
1851
+ rotate: ({ value, negative, isArbitrary }) => {
1852
+ if (isArbitrary) {
1853
+ const finalValue = negative ? `-${value}` : value;
1854
+ return getEffectiveIsWeb() ? { transform: `rotate(${finalValue})` } : { transform: [{ rotate: finalValue }] };
1855
+ }
1856
+ const deg = parseFloat(value);
1857
+ if (isNaN(deg)) return null;
1858
+ const finalDeg = negative ? -deg : deg;
1859
+ return getEffectiveIsWeb() ? { transform: `rotate(${finalDeg}deg)` } : { transform: [{ rotate: `${finalDeg}deg` }] };
1860
+ },
1861
+ // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
1862
+ perspective: ({ value, isArbitrary }) => {
1863
+ if (isArbitrary) {
1864
+ const numPx = parseFloat(value);
1865
+ if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
1866
+ return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
1867
+ }
1868
+ if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
1869
+ const n = parseFloat(value);
1870
+ if (isNaN(n)) return null;
1871
+ return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
1872
+ }
1873
+ };
1874
+
1875
+ // src/core/resolvers/effects.ts
1876
+ function keyframeDeclToCSS(decl) {
1877
+ return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
1878
+ }
1879
+ function buildKeyframeCSS(name, steps) {
1880
+ const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
1881
+ return `@keyframes ${name} { ${body} }`;
1882
+ }
1883
+ var BUILTIN_ANIMATIONS = {
1884
+ none: { animation: "none" },
1885
+ spin: {
1886
+ animation: "kb-spin 1s linear infinite",
1887
+ __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
1888
+ },
1889
+ ping: {
1890
+ animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
1891
+ __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
1892
+ },
1893
+ pulse: {
1894
+ animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
1895
+ __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
1896
+ },
1897
+ bounce: {
1898
+ animation: "kb-bounce 1s infinite",
1899
+ __keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
1900
+ }
1901
+ };
1902
+ function buildAnimationValue(animation, keyframes) {
1903
+ const words = animation.trim().split(/\s+/);
1904
+ const name = words[0];
1905
+ if (!name) return { animation };
1906
+ const builtin = BUILTIN_ANIMATIONS[name];
1907
+ if (builtin?.__keyframe) {
1908
+ const kbName = builtin.animation.split(/\s+/, 1)[0];
1909
+ return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
1910
+ }
1911
+ const steps = keyframes?.[name];
1912
+ return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
1913
+ }
1914
+ var effectResolvers = {
1915
+ // ── Opacity ────────────────────────────────────────────────────────────────
1916
+ opacity: ({ value, isArbitrary }, { opacity }) => {
1917
+ if (isArbitrary) {
1918
+ const v2 = parseFloat(value);
1919
+ return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
1920
+ }
1921
+ const v = opacity[value];
1922
+ if (v !== void 0) return { opacity: v };
1923
+ const n = parseFloat(value);
1924
+ return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
1925
+ },
1926
+ // ── Shadow ─────────────────────────────────────────────────────────────────
1927
+ shadow: ({ value }, { shadow }) => {
1928
+ const key = value === "" ? "DEFAULT" : value;
1929
+ return shadow[key] ?? null;
1930
+ },
1931
+ // ── Animations (CSS keyframe animations, web-only) ────────────────────────
1932
+ // Each variant injects a @keyframes rule once via the __keyframe marker.
1933
+ // resolver.ts detects __keyframe, injects the rule, then strips the marker
1934
+ // so it never reaches element inline styles.
1935
+ animate: ({ value, isArbitrary }, theme) => {
2100
1936
  if (!getEffectiveIsWeb()) return null;
2101
- if (!value || value === "auto") return { gridRow: "auto" };
2102
- if (isArbitrary) return { gridRow: value };
2103
- return null;
1937
+ if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
1938
+ if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
1939
+ const custom = theme.animation?.[value];
1940
+ return custom ? buildAnimationValue(custom, theme.keyframes) : null;
2104
1941
  },
2105
- // ── Grid alignment (web-only) ──────────────────────────────────────────────
2106
- // place-items is shorthand for align-items + justify-items on a grid container
2107
- "place-items": ({ value }) => {
1942
+ // ── Transition (web-only; use Animated API on native) ─────────────────────
1943
+ transition: ({ value }) => {
2108
1944
  if (!getEffectiveIsWeb()) return null;
2109
- const map = {
2110
- start: "start",
2111
- end: "end",
2112
- center: "center",
2113
- stretch: "stretch",
2114
- baseline: "baseline"
1945
+ const presets = {
1946
+ "": "color 150ms, background-color 150ms, border-color 150ms, text-decoration-color 150ms, fill 150ms, stroke 150ms, opacity 150ms, box-shadow 150ms, transform 150ms, filter 150ms, backdrop-filter 150ms",
1947
+ all: "all 150ms",
1948
+ none: "none",
1949
+ colors: "color 150ms, background-color 150ms, border-color 150ms",
1950
+ opacity: "opacity 150ms",
1951
+ shadow: "box-shadow 150ms",
1952
+ transform: "transform 150ms"
2115
1953
  };
2116
- return map[value] ? { placeItems: map[value] } : null;
1954
+ const v = presets[value];
1955
+ return v !== void 0 ? { transition: v } : null;
2117
1956
  },
2118
- // place-content is shorthand for align-content + justify-content on a grid container
2119
- "place-content": ({ value }) => {
1957
+ duration: ({ value, isArbitrary }) => {
2120
1958
  if (!getEffectiveIsWeb()) return null;
2121
- const map = {
2122
- start: "start",
2123
- end: "end",
2124
- center: "center",
2125
- stretch: "stretch",
2126
- between: "space-between",
2127
- around: "space-around",
2128
- evenly: "space-evenly",
2129
- baseline: "baseline"
2130
- };
2131
- return map[value] ? { placeContent: map[value] } : null;
1959
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
1960
+ const ms = isArbitrary ? value : `${value}ms`;
1961
+ return { transitionDuration: ms };
2132
1962
  },
2133
- // justify-items controls inline-axis alignment of grid items within their cells
2134
- "justify-items": ({ value }) => {
1963
+ delay: ({ value, isArbitrary }) => {
2135
1964
  if (!getEffectiveIsWeb()) return null;
2136
- const map = {
2137
- start: "start",
2138
- end: "end",
2139
- center: "center",
2140
- stretch: "stretch"
2141
- };
2142
- return map[value] ? { justifyItems: map[value] } : null;
1965
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
1966
+ const ms = isArbitrary ? value : `${value}ms`;
1967
+ return { transitionDelay: ms };
1968
+ }
1969
+ };
1970
+
1971
+ // src/core/resolvers/misc.ts
1972
+ var miscResolvers = {
1973
+ // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
1974
+ // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
1975
+ // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
1976
+ // presumably a typo) parses, which is why this only fires when the arbitrary value
1977
+ // itself contains a "property:value" pair; anything else (no colon) still resolves
1978
+ // to null exactly as before this resolver existed, rather than guessing a property.
1979
+ //
1980
+ // Only the FIRST colon splits property from value — the value itself may contain
1981
+ // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
1982
+ // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
1983
+ // custom property) key, so it can be used as the style object key as-written with no
1984
+ // extra case conversion — it only needs to look like a plausible CSS property name.
1985
+ "": ({ value, isArbitrary, negative }) => {
1986
+ if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
1987
+ if (negative) return null;
1988
+ const colonIdx = value.indexOf(":");
1989
+ if (colonIdx <= 0) return null;
1990
+ const prop = value.slice(0, colonIdx).trim();
1991
+ const cssValue = value.slice(colonIdx + 1).trim();
1992
+ if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
1993
+ return { [prop]: cssValue };
2143
1994
  },
2144
- // place-self is shorthand for align-self + justify-self on a grid item
2145
- "place-self": ({ value }) => {
1995
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
1996
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
1997
+ // take stroke/fill as component PROPS, not style entries, so there's no
1998
+ // reliable way to apply a resolved color through the `style` prop there.
1999
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2000
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2001
+ stroke: ({ value, isArbitrary }, { colors }) => {
2146
2002
  if (!getEffectiveIsWeb()) return null;
2147
- const map = {
2148
- auto: "auto",
2149
- start: "start",
2150
- end: "end",
2151
- center: "center",
2152
- stretch: "stretch"
2153
- };
2154
- return map[value] ? { placeSelf: map[value] } : null;
2003
+ if (!value) return null;
2004
+ if (value === "none") return { stroke: "none" };
2005
+ if (isArbitrary) {
2006
+ const w = toNativeValue(value);
2007
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2008
+ }
2009
+ const color = resolveColor(value, colors, false);
2010
+ if (color) return { stroke: color };
2011
+ const n = parseFloat(value);
2012
+ return isNaN(n) ? null : { strokeWidth: n };
2155
2013
  },
2156
- // justify-self controls inline-axis self-alignment of a grid item
2157
- "justify-self": ({ value }) => {
2014
+ fill: ({ value, isArbitrary }, { colors }) => {
2158
2015
  if (!getEffectiveIsWeb()) return null;
2159
- const map = {
2160
- auto: "auto",
2161
- start: "start",
2162
- end: "end",
2163
- center: "center",
2164
- stretch: "stretch"
2016
+ if (!value) return null;
2017
+ if (value === "none") return { fill: "none" };
2018
+ if (isArbitrary) return { fill: value };
2019
+ const color = resolveColor(value, colors, false);
2020
+ return color ? { fill: color } : null;
2021
+ },
2022
+ // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
2023
+ // Named presets give cross-platform shadow; arbitrary is web-only.
2024
+ "text-shadow": ({ value, isArbitrary }, { colors }) => {
2025
+ if (value === "none") {
2026
+ return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
2027
+ }
2028
+ const webPresets = {
2029
+ "": "0 2px 4px rgba(0,0,0,0.3)",
2030
+ sm: "0 1px 2px rgba(0,0,0,0.3)",
2031
+ md: "0 4px 6px rgba(0,0,0,0.3)",
2032
+ lg: "0 8px 16px rgba(0,0,0,0.5)",
2033
+ xl: "0 16px 32px rgba(0,0,0,0.5)"
2034
+ };
2035
+ const nativePresets = {
2036
+ "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
2037
+ sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
2038
+ md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
2039
+ lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
2040
+ xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
2041
+ };
2042
+ if (value in webPresets) {
2043
+ if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
2044
+ return nativePresets[value] ?? null;
2045
+ }
2046
+ if (isArbitrary) {
2047
+ return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
2048
+ }
2049
+ const color = resolveColor(value, colors, false);
2050
+ if (color) {
2051
+ return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
2052
+ }
2053
+ return null;
2054
+ }
2055
+ };
2056
+
2057
+ // src/core/utilities.ts
2058
+ var RESOLVERS = {
2059
+ ...colorResolvers,
2060
+ ...spacingResolvers,
2061
+ ...borderResolvers,
2062
+ ...layoutResolvers,
2063
+ ...typographyResolvers,
2064
+ ...filterResolvers,
2065
+ ...transformResolvers,
2066
+ ...effectResolvers,
2067
+ ...miscResolvers
2068
+ };
2069
+ var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
2070
+ function resolveUtility(parsed, theme) {
2071
+ if (!parsed.value) {
2072
+ if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
2073
+ if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
2074
+ if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
2075
+ }
2076
+ const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
2077
+ if (resolver) return resolver(parsed, theme);
2078
+ return null;
2079
+ }
2080
+ var PLUGIN_STANDALONE = {};
2081
+ var PLUGIN_RESOLVERS = {};
2082
+ function clearPluginUtilities() {
2083
+ for (const key of Object.keys(PLUGIN_STANDALONE)) delete PLUGIN_STANDALONE[key];
2084
+ for (const key of Object.keys(PLUGIN_RESOLVERS)) delete PLUGIN_RESOLVERS[key];
2085
+ _sortedPrefixes = null;
2086
+ _standaloneNames = null;
2087
+ }
2088
+ function getPluginStandaloneMap() {
2089
+ return PLUGIN_STANDALONE;
2090
+ }
2091
+ function isKnownUtility(utility) {
2092
+ return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
2093
+ }
2094
+ var _sortedPrefixes = null;
2095
+ var _standaloneNames = null;
2096
+ function getBuiltinUtilityPrefixes() {
2097
+ if (!_sortedPrefixes) {
2098
+ const all = [.../* @__PURE__ */ new Set([...Object.keys(RESOLVERS), ...Object.keys(PLUGIN_RESOLVERS)])];
2099
+ _sortedPrefixes = all.sort((a, b) => b.length - a.length);
2100
+ }
2101
+ return _sortedPrefixes;
2102
+ }
2103
+ function getBuiltinStandaloneNames() {
2104
+ if (!_standaloneNames) {
2105
+ _standaloneNames = /* @__PURE__ */ new Set([...Object.keys(getStandalone()), ...Object.keys(PLUGIN_STANDALONE)]);
2106
+ }
2107
+ return _standaloneNames;
2108
+ }
2109
+
2110
+ // src/core/parser.ts
2111
+ var UNSAFE_ARBITRARY_VALUE = /[{};\\]|\/\*|\*\//;
2112
+ function isSafeArbitraryValue(value) {
2113
+ if (!UNSAFE_ARBITRARY_VALUE.test(value)) return true;
2114
+ if (process.env.NODE_ENV !== "production") {
2115
+ kbachWarn(`Unsafe arbitrary value rejected: "${value}"`);
2116
+ }
2117
+ return false;
2118
+ }
2119
+ function parseClass(className) {
2120
+ const trimmed = className.trim();
2121
+ if (!trimmed) return null;
2122
+ const modifiers = [];
2123
+ let remaining = trimmed;
2124
+ while (true) {
2125
+ const colonIdx = findOuterColon(remaining);
2126
+ if (colonIdx === -1) break;
2127
+ const candidate = remaining.slice(0, colonIdx);
2128
+ if (!isKnownModifier(candidate)) break;
2129
+ modifiers.push(candidate);
2130
+ remaining = remaining.slice(colonIdx + 1);
2131
+ }
2132
+ let important = false;
2133
+ if (remaining.startsWith("!") && remaining.length > 1) {
2134
+ important = true;
2135
+ remaining = remaining.slice(1);
2136
+ }
2137
+ let negative = false;
2138
+ if (remaining.startsWith("-") && remaining.length > 1 && remaining[1] !== "-") {
2139
+ negative = true;
2140
+ remaining = remaining.slice(1);
2141
+ }
2142
+ let bracketDepth = 0;
2143
+ let bracketStart = -1;
2144
+ for (let i = remaining.length - 1; i >= 0; i--) {
2145
+ if (remaining[i] === "]") bracketDepth++;
2146
+ else if (remaining[i] === "[") {
2147
+ bracketDepth--;
2148
+ if (bracketDepth === 0) {
2149
+ bracketStart = i;
2150
+ break;
2151
+ }
2152
+ }
2153
+ }
2154
+ if (bracketStart > 0 && remaining[bracketStart - 1] === "-" && remaining.endsWith("]")) {
2155
+ const utility = remaining.slice(0, bracketStart - 1);
2156
+ const value = remaining.slice(bracketStart + 1, -1).replace(/_/g, " ");
2157
+ if (!isSafeArbitraryValue(value)) return null;
2158
+ return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2159
+ }
2160
+ if (remaining.endsWith("]") && bracketStart === -1) {
2161
+ if (process.env.NODE_ENV !== "production") {
2162
+ kbachWarn(`Unbalanced brackets: "${trimmed}"`);
2163
+ }
2164
+ }
2165
+ if (remaining.startsWith("[") && remaining.endsWith("]")) {
2166
+ const value = remaining.slice(1, -1).replace(/_/g, " ");
2167
+ if (!isSafeArbitraryValue(value)) return null;
2168
+ return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
2169
+ }
2170
+ if (getBuiltinStandaloneNames().has(remaining)) {
2171
+ return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2172
+ }
2173
+ for (const prefix of getBuiltinUtilityPrefixes()) {
2174
+ if (remaining === prefix) {
2175
+ return { original: trimmed, modifiers, negative, important, utility: prefix, value: "", isArbitrary: false };
2176
+ }
2177
+ if (remaining.startsWith(prefix + "-")) {
2178
+ const value = remaining.slice(prefix.length + 1);
2179
+ return { original: trimmed, modifiers, negative, important, utility: prefix, value, isArbitrary: false };
2180
+ }
2181
+ }
2182
+ const firstDash = remaining.indexOf("-");
2183
+ if (firstDash > 0) {
2184
+ return {
2185
+ original: trimmed,
2186
+ modifiers,
2187
+ negative,
2188
+ important,
2189
+ utility: remaining.slice(0, firstDash),
2190
+ value: remaining.slice(firstDash + 1),
2191
+ isArbitrary: false
2165
2192
  };
2166
- return map[value] ? { justifySelf: map[value] } : null;
2167
- },
2168
- // ── Flex ───────────────────────────────────────────────────────────────────
2169
- // flex-1/flex-auto/flex-none/grow/shrink are item-level sizing properties —
2170
- // deliberately NOT implying display:flex here. See the "Flex grow / shrink"
2171
- // comment in the standalone table above for why: these already work via the
2172
- // PARENT's flex context regardless of this element's own display, so
2173
- // forcing display:flex here would only be a surprising, unrelated side
2174
- // effect on this element's OWN children.
2175
- flex: ({ value, isArbitrary }, { flex }) => {
2176
- if (!value) return { display: "flex" };
2177
- if (isArbitrary) {
2178
- const v = parseFloat(value);
2179
- return isNaN(v) ? null : { flex: v };
2193
+ }
2194
+ return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2195
+ }
2196
+ function splitClassTokens(classString) {
2197
+ const tokens = [];
2198
+ let current = "";
2199
+ let depth = 0;
2200
+ let parenDepth = 0;
2201
+ for (let i = 0; i < classString.length; i++) {
2202
+ const ch = classString[i];
2203
+ if (ch === "[") {
2204
+ depth++;
2205
+ current += ch;
2206
+ continue;
2180
2207
  }
2181
- if (value in flex) {
2182
- const v = flex[value];
2183
- if (typeof v === "string" && !getEffectiveIsWeb()) {
2184
- if (v === "auto" || v === "initial") return { flex: 1 };
2185
- if (v === "none") return { flex: 0 };
2186
- return null;
2187
- }
2188
- return { flex: v };
2208
+ if (ch === "]") {
2209
+ depth--;
2210
+ current += ch;
2211
+ continue;
2189
2212
  }
2190
- const n = parseFloat(value);
2191
- return isNaN(n) ? null : { flex: n };
2192
- },
2193
- grow: ({ value }, _) => {
2194
- if (!value) return { flexGrow: 1 };
2195
- const n = parseFloat(value);
2196
- return { flexGrow: isNaN(n) ? 1 : n };
2197
- },
2198
- shrink: ({ value }, _) => {
2199
- if (!value) return { flexShrink: 1 };
2200
- const n = parseFloat(value);
2201
- return { flexShrink: isNaN(n) ? 1 : n };
2202
- },
2203
- order: ({ value, negative }, _) => {
2204
- const n = parseInt(value, 10);
2205
- return isNaN(n) ? null : { order: negative ? -n : n };
2206
- },
2207
- // ── Z-index ────────────────────────────────────────────────────────────────
2208
- z: ({ value, isArbitrary }, { zIndex }) => {
2209
- if (isArbitrary) {
2210
- const n2 = parseInt(value);
2211
- return isNaN(n2) ? null : { zIndex: n2 };
2213
+ if (ch === "(") {
2214
+ parenDepth++;
2215
+ current += ch;
2216
+ continue;
2212
2217
  }
2213
- const v = zIndex[value];
2214
- if (v !== void 0) {
2215
- if (v === "auto") return getEffectiveIsWeb() ? { zIndex: "auto" } : null;
2216
- return { zIndex: v };
2218
+ if (ch === ")") {
2219
+ parenDepth--;
2220
+ current += ch;
2221
+ continue;
2222
+ }
2223
+ if (/\s/.test(ch)) {
2224
+ if (depth === 0 && parenDepth === 0) {
2225
+ if (current) {
2226
+ tokens.push(current);
2227
+ current = "";
2228
+ }
2229
+ }
2230
+ } else {
2231
+ current += ch;
2217
2232
  }
2218
- const n = parseInt(value);
2219
- return isNaN(n) ? null : { zIndex: n };
2220
- },
2221
- // ── Aspect ratio ───────────────────────────────────────────────────────────
2222
- aspect: ({ value, isArbitrary }) => {
2223
- if (isArbitrary) return { aspectRatio: value };
2224
- const presets = { auto: "auto", square: 1, video: 16 / 9 };
2225
- if (!(value in presets)) return null;
2226
- const v = presets[value];
2227
- if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
2228
- return { aspectRatio: v };
2229
- },
2230
- // ── Columns (web-only) ─────────────────────────────────────────────────────
2231
- columns: ({ value, isArbitrary }) => {
2232
- if (!getEffectiveIsWeb()) return null;
2233
- if (isArbitrary) return { columnCount: value };
2234
- if (value === "auto") return { columnCount: "auto" };
2235
- const n = parseInt(value, 10);
2236
- if (!isNaN(n)) return { columnCount: n };
2237
- const widths = {
2238
- "3xs": "16rem",
2239
- "2xs": "18rem",
2240
- xs: "20rem",
2241
- sm: "24rem",
2242
- md: "28rem",
2243
- lg: "32rem",
2244
- xl: "36rem",
2245
- "2xl": "42rem",
2246
- "3xl": "48rem",
2247
- "4xl": "56rem",
2248
- "5xl": "64rem",
2249
- "6xl": "72rem",
2250
- "7xl": "80rem"
2251
- };
2252
- return widths[value] ? { columnWidth: widths[value] } : null;
2253
2233
  }
2254
- };
2255
-
2256
- // src/core/resolvers/typography.ts
2257
- function resolveFontSize(value, fontSizes, isArbitrary) {
2258
- if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
2259
- return fontSizes[value] ?? null;
2234
+ if (current) tokens.push(current);
2235
+ return tokens;
2260
2236
  }
2261
- var typographyResolvers = {
2262
- // ── Text ───────────────────────────────────────────────────────────────────
2263
- text: ({ value, isArbitrary }, { colors, fontSize }) => {
2264
- const size = resolveFontSize(value, fontSize, false);
2265
- if (!isArbitrary && size !== null) return { fontSize: size };
2266
- if (isArbitrary) {
2267
- if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
2268
- return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
2237
+ function normalizeClassString(classString) {
2238
+ return splitClassTokens(classString).join(" ");
2239
+ }
2240
+ function parseClasses(classString) {
2241
+ const results = [];
2242
+ for (const token of splitClassTokens(classString)) {
2243
+ const parsed = parseClass(token);
2244
+ if (parsed) results.push(parsed);
2245
+ }
2246
+ return results;
2247
+ }
2248
+ function findOuterColon(s) {
2249
+ let depth = 0;
2250
+ for (let i = 0; i < s.length; i++) {
2251
+ const ch = s[i];
2252
+ if (ch === "[") depth++;
2253
+ else if (ch === "]") depth--;
2254
+ else if (ch === ":" && depth === 0) return i;
2255
+ }
2256
+ return -1;
2257
+ }
2258
+
2259
+ // src/core/modeAwareColors.ts
2260
+ var _modeAwareMapCache = /* @__PURE__ */ new WeakMap();
2261
+ function buildModeAwareMap(colors) {
2262
+ const map = /* @__PURE__ */ new Map();
2263
+ for (const [name, entry] of Object.entries(colors)) {
2264
+ if (isModeAwareColor(entry)) {
2265
+ map.set(name, entry);
2266
+ } else if (entry && typeof entry === "object") {
2267
+ for (const [shade, val] of Object.entries(entry)) {
2268
+ if (isModeAwareColor(val)) map.set(`${name}-${shade}`, val);
2269
2269
  }
2270
- return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
2271
2270
  }
2272
- const color = resolveColor(value, colors, false);
2273
- if (!color) return null;
2274
- return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
2271
+ }
2272
+ return map;
2273
+ }
2274
+ function getModeAwareMap(colors) {
2275
+ let map = _modeAwareMapCache.get(colors);
2276
+ if (!map) {
2277
+ map = buildModeAwareMap(colors);
2278
+ _modeAwareMapCache.set(colors, map);
2279
+ }
2280
+ return map;
2281
+ }
2282
+ function expandModeAwareColorClasses(classString, colors) {
2283
+ const map = getModeAwareMap(colors);
2284
+ if (map.size === 0) return classString;
2285
+ const tokens = splitClassTokens(classString);
2286
+ let changed = false;
2287
+ const out = [];
2288
+ for (const token of tokens) {
2289
+ const parsed = parseClass(token);
2290
+ if (!parsed || parsed.isArbitrary) {
2291
+ out.push(token);
2292
+ continue;
2293
+ }
2294
+ if (parsed.modifiers.includes("dark") || parsed.modifiers.includes("light")) {
2295
+ out.push(token);
2296
+ continue;
2297
+ }
2298
+ const slashIdx = parsed.value.indexOf("/");
2299
+ const colorPart = slashIdx > 0 ? parsed.value.slice(0, slashIdx) : parsed.value;
2300
+ const opacitySuffix = slashIdx > 0 ? parsed.value.slice(slashIdx) : "";
2301
+ const pair = map.get(colorPart);
2302
+ if (!pair) {
2303
+ out.push(token);
2304
+ continue;
2305
+ }
2306
+ changed = true;
2307
+ const bang = parsed.important ? "!" : "";
2308
+ const modPrefix = parsed.modifiers.map((m) => `${m}:`).join("");
2309
+ out.push(`${bang}${modPrefix}${parsed.utility}-[${pair.light}]${opacitySuffix}`);
2310
+ out.push(`${bang}dark:${modPrefix}${parsed.utility}-[${pair.dark}]${opacitySuffix}`);
2311
+ }
2312
+ return changed ? out.join(" ") : classString;
2313
+ }
2314
+
2315
+ // src/core/theme.ts
2316
+ var defaultColors = {
2317
+ transparent: "transparent",
2318
+ current: "currentColor",
2319
+ black: "#000000",
2320
+ white: "#ffffff",
2321
+ slate: {
2322
+ 1: "#f8fafc",
2323
+ 2: "#f1f5f9",
2324
+ 3: "#e2e8f0",
2325
+ 4: "#cbd5e1",
2326
+ 5: "#94a3b8",
2327
+ 6: "#64748b",
2328
+ 7: "#475569",
2329
+ 8: "#334155",
2330
+ 9: "#1e293b",
2331
+ 10: "#0f172a",
2332
+ 11: "#020617",
2333
+ 12: "#01020a"
2334
+ },
2335
+ gray: {
2336
+ 1: "#f9fafb",
2337
+ 2: "#f3f4f6",
2338
+ 3: "#e5e7eb",
2339
+ 4: "#d1d5db",
2340
+ 5: "#9ca3af",
2341
+ 6: "#6b7280",
2342
+ 7: "#4b5563",
2343
+ 8: "#374151",
2344
+ 9: "#1f2937",
2345
+ 10: "#111827",
2346
+ 11: "#030712",
2347
+ 12: "#020409"
2348
+ },
2349
+ zinc: {
2350
+ 1: "#fafafa",
2351
+ 2: "#f4f4f5",
2352
+ 3: "#e4e4e7",
2353
+ 4: "#d4d4d8",
2354
+ 5: "#a1a1aa",
2355
+ 6: "#71717a",
2356
+ 7: "#52525b",
2357
+ 8: "#3f3f46",
2358
+ 9: "#27272a",
2359
+ 10: "#18181b",
2360
+ 11: "#09090b",
2361
+ 12: "#050506"
2362
+ },
2363
+ neutral: {
2364
+ 1: "#fafafa",
2365
+ 2: "#f5f5f5",
2366
+ 3: "#e5e5e5",
2367
+ 4: "#d4d4d4",
2368
+ 5: "#a3a3a3",
2369
+ 6: "#737373",
2370
+ 7: "#525252",
2371
+ 8: "#404040",
2372
+ 9: "#262626",
2373
+ 10: "#171717",
2374
+ 11: "#0a0a0a",
2375
+ 12: "#050505"
2376
+ },
2377
+ stone: {
2378
+ 1: "#fafaf9",
2379
+ 2: "#f5f5f4",
2380
+ 3: "#e7e5e4",
2381
+ 4: "#d6d3d1",
2382
+ 5: "#a8a29e",
2383
+ 6: "#78716c",
2384
+ 7: "#57534e",
2385
+ 8: "#44403c",
2386
+ 9: "#292524",
2387
+ 10: "#1c1917",
2388
+ 11: "#0c0a09",
2389
+ 12: "#070605"
2275
2390
  },
2276
- "text-opacity": ({ value, isArbitrary }, _) => {
2277
- if (!getEffectiveIsWeb()) return null;
2278
- const n = parseFloat(value);
2279
- if (isNaN(n)) return null;
2280
- const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
2281
- return { "--text-opacity": v };
2391
+ red: {
2392
+ 1: "#fef2f2",
2393
+ 2: "#fee2e2",
2394
+ 3: "#fecaca",
2395
+ 4: "#fca5a5",
2396
+ 5: "#f87171",
2397
+ 6: "#ef4444",
2398
+ 7: "#dc2626",
2399
+ 8: "#b91c1c",
2400
+ 9: "#991b1b",
2401
+ 10: "#7f1d1d",
2402
+ 11: "#450a0a",
2403
+ 12: "#280606"
2282
2404
  },
2283
- // ── Text decoration advanced (web-only) ───────────────────────────────────
2284
- decoration: ({ value, isArbitrary }, { colors }) => {
2285
- if (!getEffectiveIsWeb()) return null;
2286
- if (isArbitrary) {
2287
- if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
2288
- return { textDecorationColor: value };
2289
- }
2290
- const thickTokens = {
2291
- auto: "auto",
2292
- "from-font": "from-font",
2293
- "0": "0px",
2294
- "1": "1px",
2295
- "2": "2px",
2296
- "4": "4px",
2297
- "8": "8px"
2298
- };
2299
- if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
2300
- const styleTokens = {
2301
- solid: "solid",
2302
- dashed: "dashed",
2303
- dotted: "dotted",
2304
- double: "double",
2305
- wavy: "wavy"
2306
- };
2307
- if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
2308
- const color = resolveColor(value, colors, false);
2309
- return color ? { textDecorationColor: color } : null;
2405
+ orange: {
2406
+ 1: "#fff7ed",
2407
+ 2: "#ffedd5",
2408
+ 3: "#fed7aa",
2409
+ 4: "#fdba74",
2410
+ 5: "#fb923c",
2411
+ 6: "#f97316",
2412
+ 7: "#ea580c",
2413
+ 8: "#c2410c",
2414
+ 9: "#9a3412",
2415
+ 10: "#7c2d12",
2416
+ 11: "#431407",
2417
+ 12: "#270c04"
2310
2418
  },
2311
- "underline-offset": ({ value, isArbitrary }) => {
2312
- if (!getEffectiveIsWeb()) return null;
2313
- if (isArbitrary) return { textUnderlineOffset: value };
2314
- const offsets = {
2315
- auto: "auto",
2316
- "0": "0px",
2317
- "1": "1px",
2318
- "2": "2px",
2319
- "4": "4px",
2320
- "8": "8px"
2321
- };
2322
- return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
2419
+ amber: {
2420
+ 1: "#fffbeb",
2421
+ 2: "#fef3c7",
2422
+ 3: "#fde68a",
2423
+ 4: "#fcd34d",
2424
+ 5: "#fbbf24",
2425
+ 6: "#f59e0b",
2426
+ 7: "#d97706",
2427
+ 8: "#b45309",
2428
+ 9: "#92400e",
2429
+ 10: "#78350f",
2430
+ 11: "#451a03",
2431
+ 12: "#291002"
2323
2432
  },
2324
- // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
2325
- // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
2326
- // the literal string 'you can see it' quotes included), so it passes straight through as a
2327
- // valid `content: '*'` CSS value with no extra wrapping needed.
2328
- content: ({ value, isArbitrary }) => {
2329
- if (!getEffectiveIsWeb()) return null;
2330
- if (isArbitrary) return { content: value };
2331
- const presets = { none: "none" };
2332
- return value in presets ? { content: presets[value] } : null;
2433
+ yellow: {
2434
+ 1: "#fefce8",
2435
+ 2: "#fef9c3",
2436
+ 3: "#fef08a",
2437
+ 4: "#fde047",
2438
+ 5: "#facc15",
2439
+ 6: "#eab308",
2440
+ 7: "#ca8a04",
2441
+ 8: "#a16207",
2442
+ 9: "#854d0e",
2443
+ 10: "#713f12",
2444
+ 11: "#422006",
2445
+ 12: "#271304"
2333
2446
  },
2334
- // ── Font ───────────────────────────────────────────────────────────────────
2335
- font: ({ value, isArbitrary }, { fontFamily, fontWeight }) => {
2336
- if (isArbitrary) return { fontFamily: value };
2337
- if (value in fontFamily) {
2338
- const ff = fontFamily[value];
2339
- return { fontFamily: Array.isArray(ff) ? ff.join(", ") : ff };
2340
- }
2341
- if (value in fontWeight) return { fontWeight: String(fontWeight[value]) };
2342
- return null;
2447
+ lime: {
2448
+ 1: "#f7fee7",
2449
+ 2: "#ecfccb",
2450
+ 3: "#d9f99d",
2451
+ 4: "#bef264",
2452
+ 5: "#a3e635",
2453
+ 6: "#84cc16",
2454
+ 7: "#65a30d",
2455
+ 8: "#4d7c0f",
2456
+ 9: "#3f6212",
2457
+ 10: "#365314",
2458
+ 11: "#1a2e05",
2459
+ 12: "#0f1b03"
2343
2460
  },
2344
- // ── Line height ────────────────────────────────────────────────────────────
2345
- leading: ({ value, isArbitrary }, { lineHeight }) => {
2346
- if (isArbitrary) return { lineHeight: getEffectiveIsWeb() ? value : toNativeValue(value) };
2347
- const v = lineHeight[value];
2348
- if (v === void 0) return null;
2349
- if (typeof v === "string" && !getEffectiveIsWeb()) return { lineHeight: toNativeValue(v) };
2350
- return { lineHeight: v };
2461
+ green: {
2462
+ 1: "#f0fdf4",
2463
+ 2: "#dcfce7",
2464
+ 3: "#bbf7d0",
2465
+ 4: "#86efac",
2466
+ 5: "#4ade80",
2467
+ 6: "#22c55e",
2468
+ 7: "#16a34a",
2469
+ 8: "#15803d",
2470
+ 9: "#166534",
2471
+ 10: "#14532d",
2472
+ 11: "#052e16",
2473
+ 12: "#031b0d"
2351
2474
  },
2352
- // ── Letter spacing ─────────────────────────────────────────────────────────
2353
- tracking: ({ value, isArbitrary }, { letterSpacing }) => {
2354
- if (isArbitrary) return { letterSpacing: getEffectiveIsWeb() ? value : toNativeValue(value) };
2355
- const v = letterSpacing[value];
2356
- return v !== void 0 ? { letterSpacing: v } : null;
2475
+ emerald: {
2476
+ 1: "#ecfdf5",
2477
+ 2: "#d1fae5",
2478
+ 3: "#a7f3d0",
2479
+ 4: "#6ee7b7",
2480
+ 5: "#34d399",
2481
+ 6: "#10b981",
2482
+ 7: "#059669",
2483
+ 8: "#047857",
2484
+ 9: "#065f46",
2485
+ 10: "#064e3b",
2486
+ 11: "#022c22",
2487
+ 12: "#011a14"
2357
2488
  },
2358
- // ── Line-clamp (web-only) ─────────────────────────────────────────────────
2359
- "line-clamp": ({ value }) => {
2360
- if (!getEffectiveIsWeb()) return null;
2361
- if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
2362
- const n = parseInt(value, 10);
2363
- if (isNaN(n) || n < 1) return null;
2364
- return {
2365
- overflow: "hidden",
2366
- display: "-webkit-box",
2367
- WebkitBoxOrient: "vertical",
2368
- WebkitLineClamp: n
2369
- };
2370
- }
2371
- };
2372
-
2373
- // src/core/resolvers/transform.ts
2374
- var transformResolvers = {
2375
- // ── Transform extras (web-only) ───────────────────────────────────────────
2376
- "skew-x": ({ value, negative, isArbitrary }) => {
2377
- const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2378
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
2379
- return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
2489
+ teal: {
2490
+ 1: "#f0fdfa",
2491
+ 2: "#ccfbf1",
2492
+ 3: "#99f6e4",
2493
+ 4: "#5eead4",
2494
+ 5: "#2dd4bf",
2495
+ 6: "#14b8a6",
2496
+ 7: "#0d9488",
2497
+ 8: "#0f766e",
2498
+ 9: "#115e59",
2499
+ 10: "#134e4a",
2500
+ 11: "#042f2e",
2501
+ 12: "#021c1b"
2380
2502
  },
2381
- "skew-y": ({ value, negative, isArbitrary }) => {
2382
- const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2383
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
2384
- return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
2503
+ cyan: {
2504
+ 1: "#ecfeff",
2505
+ 2: "#cffafe",
2506
+ 3: "#a5f3fc",
2507
+ 4: "#67e8f9",
2508
+ 5: "#22d3ee",
2509
+ 6: "#06b6d4",
2510
+ 7: "#0891b2",
2511
+ 8: "#0e7490",
2512
+ 9: "#155e75",
2513
+ 10: "#164e63",
2514
+ 11: "#083344",
2515
+ 12: "#041e28"
2385
2516
  },
2386
- // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
2387
- transform: ({ value, isArbitrary }) => {
2388
- if (!getEffectiveIsWeb()) return null;
2389
- if (isArbitrary) return { transform: value.replace(/_/g, " ") };
2390
- if (value === "none") return { transform: "none" };
2391
- return null;
2517
+ sky: {
2518
+ 1: "#f0f9ff",
2519
+ 2: "#e0f2fe",
2520
+ 3: "#bae6fd",
2521
+ 4: "#7dd3fc",
2522
+ 5: "#38bdf8",
2523
+ 6: "#0ea5e9",
2524
+ 7: "#0284c7",
2525
+ 8: "#0369a1",
2526
+ 9: "#075985",
2527
+ 10: "#0c4a6e",
2528
+ 11: "#082f49",
2529
+ 12: "#041b2b"
2530
+ },
2531
+ blue: {
2532
+ 1: "#eff6ff",
2533
+ 2: "#dbeafe",
2534
+ 3: "#bfdbfe",
2535
+ 4: "#93c5fd",
2536
+ 5: "#60a5fa",
2537
+ 6: "#3b82f6",
2538
+ 7: "#2563eb",
2539
+ 8: "#1d4ed8",
2540
+ 9: "#1e40af",
2541
+ 10: "#1e3a8a",
2542
+ 11: "#172554",
2543
+ 12: "#0d1633"
2392
2544
  },
2393
- // Transform origin
2394
- origin: ({ value, isArbitrary }) => {
2395
- if (!getEffectiveIsWeb()) return null;
2396
- if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
2397
- const origins = {
2398
- center: "center",
2399
- top: "top",
2400
- "top-right": "top right",
2401
- right: "right",
2402
- "bottom-right": "bottom right",
2403
- bottom: "bottom",
2404
- "bottom-left": "bottom left",
2405
- left: "left",
2406
- "top-left": "top left"
2407
- };
2408
- return origins[value] ? { transformOrigin: origins[value] } : null;
2545
+ indigo: {
2546
+ 1: "#eef2ff",
2547
+ 2: "#e0e7ff",
2548
+ 3: "#c7d2fe",
2549
+ 4: "#a5b4fc",
2550
+ 5: "#818cf8",
2551
+ 6: "#6366f1",
2552
+ 7: "#4f46e5",
2553
+ 8: "#4338ca",
2554
+ 9: "#3730a3",
2555
+ 10: "#312e81",
2556
+ 11: "#1e1b4b",
2557
+ 12: "#12102d"
2409
2558
  },
2410
- // ── Scale ──────────────────────────────────────────────────────────────────
2411
- // Non-arbitrary: scale-150 → value='150' → 150/100 = 1.5
2412
- // Arbitrary: scale-[1.5] → value='1.5' → used as-is (already the factor)
2413
- scale: ({ value, isArbitrary }) => {
2414
- const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
2415
- if (isNaN(n)) return null;
2416
- return getEffectiveIsWeb() ? { transform: `scale(${n})` } : { transform: [{ scale: n }] };
2559
+ violet: {
2560
+ 1: "#f5f3ff",
2561
+ 2: "#ede9fe",
2562
+ 3: "#ddd6fe",
2563
+ 4: "#c4b5fd",
2564
+ 5: "#a78bfa",
2565
+ 6: "#8b5cf6",
2566
+ 7: "#7c3aed",
2567
+ 8: "#6d28d9",
2568
+ 9: "#5b21b6",
2569
+ 10: "#4c1d95",
2570
+ 11: "#2e1065",
2571
+ 12: "#1c0a3d"
2417
2572
  },
2418
- "scale-x": ({ value, isArbitrary }) => {
2419
- const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
2420
- if (isNaN(n)) return null;
2421
- return getEffectiveIsWeb() ? { transform: `scaleX(${n})` } : { transform: [{ scaleX: n }] };
2573
+ purple: {
2574
+ 1: "#faf5ff",
2575
+ 2: "#f3e8ff",
2576
+ 3: "#e9d5ff",
2577
+ 4: "#d8b4fe",
2578
+ 5: "#c084fc",
2579
+ 6: "#a855f7",
2580
+ 7: "#9333ea",
2581
+ 8: "#7e22ce",
2582
+ 9: "#6b21a8",
2583
+ 10: "#581c87",
2584
+ 11: "#3b0764",
2585
+ 12: "#23043c"
2422
2586
  },
2423
- "scale-y": ({ value, isArbitrary }) => {
2424
- const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
2425
- if (isNaN(n)) return null;
2426
- return getEffectiveIsWeb() ? { transform: `scaleY(${n})` } : { transform: [{ scaleY: n }] };
2587
+ fuchsia: {
2588
+ 1: "#fdf4ff",
2589
+ 2: "#fae8ff",
2590
+ 3: "#f5d0fe",
2591
+ 4: "#f0abfc",
2592
+ 5: "#e879f9",
2593
+ 6: "#d946ef",
2594
+ 7: "#c026d3",
2595
+ 8: "#a21caf",
2596
+ 9: "#86198f",
2597
+ 10: "#701a75",
2598
+ 11: "#4a044e",
2599
+ 12: "#2d022f"
2427
2600
  },
2428
- // ── Rotate ─────────────────────────────────────────────────────────────────
2429
- rotate: ({ value, negative, isArbitrary }) => {
2430
- if (isArbitrary) {
2431
- const finalValue = negative ? `-${value}` : value;
2432
- return getEffectiveIsWeb() ? { transform: `rotate(${finalValue})` } : { transform: [{ rotate: finalValue }] };
2433
- }
2434
- const deg = parseFloat(value);
2435
- if (isNaN(deg)) return null;
2436
- const finalDeg = negative ? -deg : deg;
2437
- return getEffectiveIsWeb() ? { transform: `rotate(${finalDeg}deg)` } : { transform: [{ rotate: `${finalDeg}deg` }] };
2601
+ pink: {
2602
+ 1: "#fdf2f8",
2603
+ 2: "#fce7f3",
2604
+ 3: "#fbcfe8",
2605
+ 4: "#f9a8d4",
2606
+ 5: "#f472b6",
2607
+ 6: "#ec4899",
2608
+ 7: "#db2777",
2609
+ 8: "#be185d",
2610
+ 9: "#9d174d",
2611
+ 10: "#831843",
2612
+ 11: "#500724",
2613
+ 12: "#300415"
2438
2614
  },
2439
- // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
2440
- perspective: ({ value, isArbitrary }) => {
2441
- if (isArbitrary) {
2442
- const numPx = parseFloat(value);
2443
- if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
2444
- return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
2445
- }
2446
- if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
2447
- const n = parseFloat(value);
2448
- if (isNaN(n)) return null;
2449
- return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
2615
+ rose: {
2616
+ 1: "#fff1f2",
2617
+ 2: "#ffe4e6",
2618
+ 3: "#fecdd3",
2619
+ 4: "#fda4af",
2620
+ 5: "#fb7185",
2621
+ 6: "#f43f5e",
2622
+ 7: "#e11d48",
2623
+ 8: "#be123c",
2624
+ 9: "#9f1239",
2625
+ 10: "#881337",
2626
+ 11: "#4c0519",
2627
+ 12: "#2d030e"
2450
2628
  }
2451
2629
  };
2452
-
2453
- // src/core/resolvers/effects.ts
2454
- function keyframeDeclToCSS(decl) {
2455
- return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
2456
- }
2457
- function buildKeyframeCSS(name, steps) {
2458
- const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
2459
- return `@keyframes ${name} { ${body} }`;
2460
- }
2461
- var BUILTIN_ANIMATIONS = {
2462
- none: { animation: "none" },
2463
- spin: {
2464
- animation: "kb-spin 1s linear infinite",
2465
- __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
2630
+ var defaultTheme = {
2631
+ colors: defaultColors,
2632
+ // 1 unit = 4px
2633
+ spacing: {
2634
+ px: 1,
2635
+ 0: 0,
2636
+ "0.5": 2,
2637
+ 1: 4,
2638
+ "1.5": 6,
2639
+ 2: 8,
2640
+ "2.5": 10,
2641
+ 3: 12,
2642
+ "3.5": 14,
2643
+ 4: 16,
2644
+ 5: 20,
2645
+ 6: 24,
2646
+ 7: 28,
2647
+ 8: 32,
2648
+ 9: 36,
2649
+ 10: 40,
2650
+ 11: 44,
2651
+ 12: 48,
2652
+ 14: 56,
2653
+ 16: 64,
2654
+ 20: 80,
2655
+ 24: 96,
2656
+ 28: 112,
2657
+ 32: 128,
2658
+ 36: 144,
2659
+ 40: 160,
2660
+ 44: 176,
2661
+ 48: 192,
2662
+ 52: 208,
2663
+ 56: 224,
2664
+ 60: 240,
2665
+ 64: 256,
2666
+ 72: 288,
2667
+ 80: 320,
2668
+ 96: 384,
2669
+ auto: "auto",
2670
+ full: "100%",
2671
+ "1/2": "50%",
2672
+ "1/3": "33.333333%",
2673
+ "2/3": "66.666667%",
2674
+ "1/4": "25%",
2675
+ "3/4": "75%",
2676
+ // Dynamic viewport unit — see the comment on the 'screen' standalone utilities
2677
+ // in utilities.ts for why dvh beats vh on mobile. Used by h-screen (w-screen
2678
+ // has its own standalone entry so it never reaches this spacing lookup).
2679
+ screen: "100dvh",
2680
+ min: "min-content",
2681
+ max: "max-content",
2682
+ fit: "fit-content"
2466
2683
  },
2467
- ping: {
2468
- animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
2469
- __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
2684
+ fontSize: {
2685
+ xs: 12,
2686
+ sm: 14,
2687
+ base: 16,
2688
+ lg: 18,
2689
+ xl: 20,
2690
+ "2xl": 24,
2691
+ "3xl": 30,
2692
+ "4xl": 36,
2693
+ "5xl": 48,
2694
+ "6xl": 60,
2695
+ "7xl": 72,
2696
+ "8xl": 96,
2697
+ "9xl": 128
2470
2698
  },
2471
- pulse: {
2472
- animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
2473
- __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
2699
+ fontFamily: {
2700
+ sans: "System",
2701
+ mono: "Courier New",
2702
+ serif: "Georgia"
2474
2703
  },
2475
- bounce: {
2476
- animation: "kb-bounce 1s infinite",
2477
- __keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
2478
- }
2479
- };
2480
- function buildAnimationValue(animation, keyframes) {
2481
- const words = animation.trim().split(/\s+/);
2482
- const name = words[0];
2483
- if (!name) return { animation };
2484
- const builtin = BUILTIN_ANIMATIONS[name];
2485
- if (builtin?.__keyframe) {
2486
- const kbName = builtin.animation.split(/\s+/, 1)[0];
2487
- return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
2488
- }
2489
- const steps = keyframes?.[name];
2490
- return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
2491
- }
2492
- var effectResolvers = {
2493
- // ── Opacity ────────────────────────────────────────────────────────────────
2494
- opacity: ({ value, isArbitrary }, { opacity }) => {
2495
- if (isArbitrary) {
2496
- const v2 = parseFloat(value);
2497
- return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
2498
- }
2499
- const v = opacity[value];
2500
- if (v !== void 0) return { opacity: v };
2501
- const n = parseFloat(value);
2502
- return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
2704
+ fontWeight: {
2705
+ thin: "100",
2706
+ extralight: "200",
2707
+ light: "300",
2708
+ normal: "400",
2709
+ medium: "500",
2710
+ semibold: "600",
2711
+ bold: "700",
2712
+ extrabold: "800",
2713
+ black: "900"
2503
2714
  },
2504
- // ── Shadow ─────────────────────────────────────────────────────────────────
2505
- shadow: ({ value }, { shadow }) => {
2506
- const key = value === "" ? "DEFAULT" : value;
2507
- return shadow[key] ?? null;
2715
+ borderRadius: {
2716
+ none: 0,
2717
+ sm: 2,
2718
+ DEFAULT: 4,
2719
+ md: 6,
2720
+ lg: 8,
2721
+ xl: 12,
2722
+ "2xl": 16,
2723
+ "3xl": 24,
2724
+ full: 9999
2508
2725
  },
2509
- // ── Animations (CSS keyframe animations, web-only) ────────────────────────
2510
- // Each variant injects a @keyframes rule once via the __keyframe marker.
2511
- // resolver.ts detects __keyframe, injects the rule, then strips the marker
2512
- // so it never reaches element inline styles.
2513
- animate: ({ value, isArbitrary }, theme) => {
2514
- if (!getEffectiveIsWeb()) return null;
2515
- if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
2516
- if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
2517
- const custom = theme.animation?.[value];
2518
- return custom ? buildAnimationValue(custom, theme.keyframes) : null;
2726
+ borderWidth: {
2727
+ DEFAULT: 1,
2728
+ 0: 0,
2729
+ 2: 2,
2730
+ 4: 4,
2731
+ 8: 8
2519
2732
  },
2520
- // ── Transition (web-only; use Animated API on native) ─────────────────────
2521
- transition: ({ value }) => {
2522
- if (!getEffectiveIsWeb()) return null;
2523
- const presets = {
2524
- "": "color 150ms, background-color 150ms, border-color 150ms, text-decoration-color 150ms, fill 150ms, stroke 150ms, opacity 150ms, box-shadow 150ms, transform 150ms, filter 150ms, backdrop-filter 150ms",
2525
- all: "all 150ms",
2526
- none: "none",
2527
- colors: "color 150ms, background-color 150ms, border-color 150ms",
2528
- opacity: "opacity 150ms",
2529
- shadow: "box-shadow 150ms",
2530
- transform: "transform 150ms"
2531
- };
2532
- const v = presets[value];
2533
- return v !== void 0 ? { transition: v } : null;
2733
+ opacity: {
2734
+ 0: 0,
2735
+ 5: 0.05,
2736
+ 10: 0.1,
2737
+ 15: 0.15,
2738
+ 20: 0.2,
2739
+ 25: 0.25,
2740
+ 30: 0.3,
2741
+ 40: 0.4,
2742
+ 50: 0.5,
2743
+ 60: 0.6,
2744
+ 70: 0.7,
2745
+ 75: 0.75,
2746
+ 80: 0.8,
2747
+ 90: 0.9,
2748
+ 95: 0.95,
2749
+ 100: 1
2534
2750
  },
2535
- duration: ({ value, isArbitrary }) => {
2536
- if (!getEffectiveIsWeb()) return null;
2537
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
2538
- const ms = isArbitrary ? value : `${value}ms`;
2539
- return { transitionDuration: ms };
2751
+ lineHeight: {
2752
+ none: 1,
2753
+ tight: 1.25,
2754
+ snug: 1.375,
2755
+ normal: 1.5,
2756
+ relaxed: 1.625,
2757
+ loose: 2,
2758
+ // Pixel values stored as strings; styleValueToCSS passes them through, toNativeValue strips the unit
2759
+ 3: "12px",
2760
+ 4: "16px",
2761
+ 5: "20px",
2762
+ 6: "24px",
2763
+ 7: "28px",
2764
+ 8: "32px",
2765
+ 9: "36px",
2766
+ 10: "40px"
2540
2767
  },
2541
- delay: ({ value, isArbitrary }) => {
2542
- if (!getEffectiveIsWeb()) return null;
2543
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
2544
- const ms = isArbitrary ? value : `${value}ms`;
2545
- return { transitionDelay: ms };
2546
- }
2547
- };
2548
-
2549
- // src/core/resolvers/misc.ts
2550
- var miscResolvers = {
2551
- // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
2552
- // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
2553
- // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
2554
- // presumably a typo) parses, which is why this only fires when the arbitrary value
2555
- // itself contains a "property:value" pair; anything else (no colon) still resolves
2556
- // to null exactly as before this resolver existed, rather than guessing a property.
2557
- //
2558
- // Only the FIRST colon splits property from value — the value itself may contain
2559
- // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
2560
- // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
2561
- // custom property) key, so it can be used as the style object key as-written with no
2562
- // extra case conversion — it only needs to look like a plausible CSS property name.
2563
- "": ({ value, isArbitrary, negative }) => {
2564
- if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
2565
- if (negative) return null;
2566
- const colonIdx = value.indexOf(":");
2567
- if (colonIdx <= 0) return null;
2568
- const prop = value.slice(0, colonIdx).trim();
2569
- const cssValue = value.slice(colonIdx + 1).trim();
2570
- if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
2571
- return { [prop]: cssValue };
2768
+ letterSpacing: {
2769
+ tighter: -0.8,
2770
+ tight: -0.4,
2771
+ normal: 0,
2772
+ wide: 0.4,
2773
+ wider: 0.8,
2774
+ widest: 1.6
2572
2775
  },
2573
- // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2574
- // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2575
- // take stroke/fill as component PROPS, not style entries, so there's no
2576
- // reliable way to apply a resolved color through the `style` prop there.
2577
- // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2578
- // color-first-then-numeric-fallback disambiguation `border` uses above.
2579
- stroke: ({ value, isArbitrary }, { colors }) => {
2580
- if (!getEffectiveIsWeb()) return null;
2581
- if (!value) return null;
2582
- if (value === "none") return { stroke: "none" };
2583
- if (isArbitrary) {
2584
- const w = toNativeValue(value);
2585
- return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2586
- }
2587
- const color = resolveColor(value, colors, false);
2588
- if (color) return { stroke: color };
2589
- const n = parseFloat(value);
2590
- return isNaN(n) ? null : { strokeWidth: n };
2776
+ zIndex: {
2777
+ auto: "auto",
2778
+ 0: 0,
2779
+ 10: 10,
2780
+ 20: 20,
2781
+ 30: 30,
2782
+ 40: 40,
2783
+ 50: 50
2591
2784
  },
2592
- fill: ({ value, isArbitrary }, { colors }) => {
2593
- if (!getEffectiveIsWeb()) return null;
2594
- if (!value) return null;
2595
- if (value === "none") return { fill: "none" };
2596
- if (isArbitrary) return { fill: value };
2597
- const color = resolveColor(value, colors, false);
2598
- return color ? { fill: color } : null;
2785
+ flex: {
2786
+ 1: 1,
2787
+ auto: "auto",
2788
+ // CSS: flex: auto = 1 1 auto; native: mapped to 1 in resolver
2789
+ initial: "initial",
2790
+ // CSS: flex: initial = 0 1 auto; native: mapped to 1
2791
+ none: "none"
2792
+ // CSS: flex: none = 0 0 auto; native: mapped to 0
2599
2793
  },
2600
- // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
2601
- // Named presets give cross-platform shadow; arbitrary is web-only.
2602
- "text-shadow": ({ value, isArbitrary }, { colors }) => {
2603
- if (value === "none") {
2604
- return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
2605
- }
2606
- const webPresets = {
2607
- "": "0 2px 4px rgba(0,0,0,0.3)",
2608
- sm: "0 1px 2px rgba(0,0,0,0.3)",
2609
- md: "0 4px 6px rgba(0,0,0,0.3)",
2610
- lg: "0 8px 16px rgba(0,0,0,0.5)",
2611
- xl: "0 16px 32px rgba(0,0,0,0.5)"
2612
- };
2613
- const nativePresets = {
2614
- "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
2615
- sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
2616
- md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
2617
- lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
2618
- xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
2619
- };
2620
- if (value in webPresets) {
2621
- if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
2622
- return nativePresets[value] ?? null;
2623
- }
2624
- if (isArbitrary) {
2625
- return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
2626
- }
2627
- const color = resolveColor(value, colors, false);
2628
- if (color) {
2629
- return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
2630
- }
2631
- return null;
2632
- }
2633
- };
2634
-
2635
- // src/core/utilities.ts
2636
- var RESOLVERS = {
2637
- ...colorResolvers,
2638
- ...spacingResolvers,
2639
- ...borderResolvers,
2640
- ...layoutResolvers,
2641
- ...typographyResolvers,
2642
- ...filterResolvers,
2643
- ...transformResolvers,
2644
- ...effectResolvers,
2645
- ...miscResolvers
2794
+ shadow: {
2795
+ sm: {
2796
+ shadowColor: "#000",
2797
+ shadowOffset: { width: 0, height: 1 },
2798
+ shadowOpacity: 0.05,
2799
+ shadowRadius: 2,
2800
+ elevation: 1
2801
+ },
2802
+ DEFAULT: {
2803
+ shadowColor: "#000",
2804
+ shadowOffset: { width: 0, height: 2 },
2805
+ shadowOpacity: 0.1,
2806
+ shadowRadius: 4,
2807
+ elevation: 2
2808
+ },
2809
+ md: {
2810
+ shadowColor: "#000",
2811
+ shadowOffset: { width: 0, height: 4 },
2812
+ shadowOpacity: 0.1,
2813
+ shadowRadius: 8,
2814
+ elevation: 3
2815
+ },
2816
+ lg: {
2817
+ shadowColor: "#000",
2818
+ shadowOffset: { width: 0, height: 8 },
2819
+ shadowOpacity: 0.1,
2820
+ shadowRadius: 15,
2821
+ elevation: 4
2822
+ },
2823
+ xl: {
2824
+ shadowColor: "#000",
2825
+ shadowOffset: { width: 0, height: 16 },
2826
+ shadowOpacity: 0.1,
2827
+ shadowRadius: 24,
2828
+ elevation: 6
2829
+ },
2830
+ "2xl": {
2831
+ shadowColor: "#000",
2832
+ shadowOffset: { width: 0, height: 24 },
2833
+ shadowOpacity: 0.25,
2834
+ shadowRadius: 48,
2835
+ elevation: 8
2836
+ },
2837
+ none: {
2838
+ shadowColor: "transparent",
2839
+ shadowOffset: { width: 0, height: 0 },
2840
+ shadowOpacity: 0,
2841
+ shadowRadius: 0,
2842
+ elevation: 0
2843
+ }
2844
+ },
2845
+ screens: {
2846
+ sm: 576,
2847
+ // small tablets / large phones (landscape)
2848
+ md: 768,
2849
+ // tablets (iPad and up)
2850
+ lg: 1024,
2851
+ // large tablets (iPad Pro)
2852
+ xl: 1280,
2853
+ // desktop
2854
+ "2xl": 1536
2855
+ },
2856
+ // Empty by default — the 4 built-in presets (spin/ping/pulse/bounce) are handled
2857
+ // directly in the `animate` resolver, not through this theme table. This exists so
2858
+ // user-defined ones (kbach.config.js theme.extend.keyframes/animation) merge in
2859
+ // alongside them without needing to redeclare the built-ins.
2860
+ keyframes: {},
2861
+ animation: {}
2646
2862
  };
2647
- var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
2648
- function resolveUtility(parsed, theme) {
2649
- if (!parsed.value) {
2650
- if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
2651
- if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
2652
- if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
2653
- }
2654
- const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
2655
- if (resolver) return resolver(parsed, theme);
2656
- return null;
2657
- }
2658
- var PLUGIN_STANDALONE = {};
2659
- var PLUGIN_RESOLVERS = {};
2660
- function clearPluginUtilities() {
2661
- for (const key of Object.keys(PLUGIN_STANDALONE)) delete PLUGIN_STANDALONE[key];
2662
- for (const key of Object.keys(PLUGIN_RESOLVERS)) delete PLUGIN_RESOLVERS[key];
2663
- _sortedPrefixes = null;
2664
- _standaloneNames = null;
2665
- }
2666
- function getPluginStandaloneMap() {
2667
- return PLUGIN_STANDALONE;
2668
- }
2669
- function isKnownUtility(utility) {
2670
- return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
2671
- }
2672
- var _sortedPrefixes = null;
2673
- var _standaloneNames = null;
2674
- function getBuiltinUtilityPrefixes() {
2675
- if (!_sortedPrefixes) {
2676
- const all = [.../* @__PURE__ */ new Set([...Object.keys(RESOLVERS), ...Object.keys(PLUGIN_RESOLVERS)])];
2677
- _sortedPrefixes = all.sort((a, b) => b.length - a.length);
2678
- }
2679
- return _sortedPrefixes;
2680
- }
2681
- function getBuiltinStandaloneNames() {
2682
- if (!_standaloneNames) {
2683
- _standaloneNames = /* @__PURE__ */ new Set([...Object.keys(getStandalone()), ...Object.keys(PLUGIN_STANDALONE)]);
2684
- }
2685
- return _standaloneNames;
2686
- }
2687
2863
 
2688
- // src/core/parser.ts
2689
- var UNSAFE_ARBITRARY_VALUE = /[{};\\]|\/\*|\*\//;
2690
- function isSafeArbitraryValue(value) {
2691
- if (!UNSAFE_ARBITRARY_VALUE.test(value)) return true;
2692
- if (process.env.NODE_ENV !== "production") {
2693
- kbachWarn(`Unsafe arbitrary value rejected: "${value}"`);
2694
- }
2695
- return false;
2696
- }
2697
- function parseClass(className) {
2698
- const trimmed = className.trim();
2699
- if (!trimmed) return null;
2700
- const modifiers = [];
2701
- let remaining = trimmed;
2702
- while (true) {
2703
- const colonIdx = findOuterColon(remaining);
2704
- if (colonIdx === -1) break;
2705
- const candidate = remaining.slice(0, colonIdx);
2706
- if (!isKnownModifier(candidate)) break;
2707
- modifiers.push(candidate);
2708
- remaining = remaining.slice(colonIdx + 1);
2709
- }
2710
- let important = false;
2711
- if (remaining.startsWith("!") && remaining.length > 1) {
2712
- important = true;
2713
- remaining = remaining.slice(1);
2714
- }
2715
- let negative = false;
2716
- if (remaining.startsWith("-") && remaining.length > 1 && remaining[1] !== "-") {
2717
- negative = true;
2718
- remaining = remaining.slice(1);
2719
- }
2720
- let bracketDepth = 0;
2721
- let bracketStart = -1;
2722
- for (let i = remaining.length - 1; i >= 0; i--) {
2723
- if (remaining[i] === "]") bracketDepth++;
2724
- else if (remaining[i] === "[") {
2725
- bracketDepth--;
2726
- if (bracketDepth === 0) {
2727
- bracketStart = i;
2728
- break;
2729
- }
2730
- }
2731
- }
2732
- if (bracketStart > 0 && remaining[bracketStart - 1] === "-" && remaining.endsWith("]")) {
2733
- const utility = remaining.slice(0, bracketStart - 1);
2734
- const value = remaining.slice(bracketStart + 1, -1).replace(/_/g, " ");
2735
- if (!isSafeArbitraryValue(value)) return null;
2736
- return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2737
- }
2738
- if (remaining.endsWith("]") && bracketStart === -1) {
2739
- if (process.env.NODE_ENV !== "production") {
2740
- kbachWarn(`Unbalanced brackets: "${trimmed}"`);
2741
- }
2742
- }
2743
- if (remaining.startsWith("[") && remaining.endsWith("]")) {
2744
- const value = remaining.slice(1, -1).replace(/_/g, " ");
2745
- if (!isSafeArbitraryValue(value)) return null;
2746
- return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
2747
- }
2748
- if (getBuiltinStandaloneNames().has(remaining)) {
2749
- return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2750
- }
2751
- for (const prefix of getBuiltinUtilityPrefixes()) {
2752
- if (remaining === prefix) {
2753
- return { original: trimmed, modifiers, negative, important, utility: prefix, value: "", isArbitrary: false };
2754
- }
2755
- if (remaining.startsWith(prefix + "-")) {
2756
- const value = remaining.slice(prefix.length + 1);
2757
- return { original: trimmed, modifiers, negative, important, utility: prefix, value, isArbitrary: false };
2758
- }
2759
- }
2760
- const firstDash = remaining.indexOf("-");
2761
- if (firstDash > 0) {
2762
- return {
2763
- original: trimmed,
2764
- modifiers,
2765
- negative,
2766
- important,
2767
- utility: remaining.slice(0, firstDash),
2768
- value: remaining.slice(firstDash + 1),
2769
- isArbitrary: false
2770
- };
2771
- }
2772
- return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2773
- }
2774
- function splitClassTokens(classString) {
2775
- const tokens = [];
2776
- let current = "";
2777
- let depth = 0;
2778
- let parenDepth = 0;
2779
- for (let i = 0; i < classString.length; i++) {
2780
- const ch = classString[i];
2781
- if (ch === "[") {
2782
- depth++;
2783
- current += ch;
2784
- continue;
2785
- }
2786
- if (ch === "]") {
2787
- depth--;
2788
- current += ch;
2789
- continue;
2790
- }
2791
- if (ch === "(") {
2792
- parenDepth++;
2793
- current += ch;
2794
- continue;
2795
- }
2796
- if (ch === ")") {
2797
- parenDepth--;
2798
- current += ch;
2799
- continue;
2800
- }
2801
- if (/\s/.test(ch)) {
2802
- if (depth === 0 && parenDepth === 0) {
2803
- if (current) {
2804
- tokens.push(current);
2805
- current = "";
2806
- }
2807
- }
2808
- } else {
2809
- current += ch;
2810
- }
2811
- }
2812
- if (current) tokens.push(current);
2813
- return tokens;
2814
- }
2815
- function normalizeClassString(classString) {
2816
- return splitClassTokens(classString).join(" ");
2817
- }
2818
- function parseClasses(classString) {
2819
- const results = [];
2820
- for (const token of splitClassTokens(classString)) {
2821
- const parsed = parseClass(token);
2822
- if (parsed) results.push(parsed);
2823
- }
2824
- return results;
2825
- }
2826
- function findOuterColon(s) {
2827
- let depth = 0;
2828
- for (let i = 0; i < s.length; i++) {
2829
- const ch = s[i];
2830
- if (ch === "[") depth++;
2831
- else if (ch === "]") depth--;
2832
- else if (ch === ":" && depth === 0) return i;
2833
- }
2834
- return -1;
2835
- }
2864
+ // src/core/reset.ts
2865
+ var RESET_STYLE_ID = "kbach-reset";
2866
+ var BASE_RESET = [
2867
+ // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
2868
+ // border-width: 0 keeps all elements borderless by default.
2869
+ "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }",
2870
+ "body { margin: 0; padding: 0; }",
2871
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
2872
+ "p { margin: 0; }",
2873
+ "a { color: inherit; text-decoration: none; }",
2874
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
2875
+ "img, video, svg { display: block; max-width: 100%; }",
2876
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
2877
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
2878
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
2879
+ // plain text. Text-like inputs, textarea, and button don't have that problem
2880
+ // (their native chrome is just a skin around content utilities can fully
2881
+ // restyle), so they keep the blank-canvas treatment.
2882
+ "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2883
+ // Native checkbox/radio/range still get typography + spacing normalized, and
2884
+ // accent-color re-themes their native indicator to the current text color
2885
+ // instead of the browser/OS default blue, so they stay on-brand without
2886
+ // needing to be rebuilt from scratch.
2887
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
2888
+ // select keeps its native chrome (border/background/arrow are all part of the
2889
+ // same OS-drawn widget that appearance: none would blank out) — only typography
2890
+ // and spacing are normalized so it still matches surrounding text.
2891
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2892
+ "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2893
+ "button, [role='button'] { cursor: pointer; }",
2894
+ ":disabled { cursor: default; }",
2895
+ "textarea { resize: vertical; }",
2896
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
2897
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
2898
+ "::placeholder { opacity: 1; }",
2899
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
2900
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
2901
+ "fieldset { padding: 0; margin: 0; }",
2902
+ "table { border-collapse: collapse; border-spacing: 0; }"
2903
+ ].join("\n");
2836
2904
 
2837
2905
  // src/core/responsiveStore.ts
2838
2906
  var store = { width: 0, notifiedWidth: 0, screens: {}, listeners: /* @__PURE__ */ new Set() };
@@ -3138,7 +3206,7 @@ function injectClassRule(cls, bucketKey, styles, darkMode, important) {
3138
3206
  }
3139
3207
  function generateClassCSS(classString, theme, darkMode = "attribute", screens = {}) {
3140
3208
  const rules = [];
3141
- for (const parsed of parseClasses(classString)) {
3209
+ for (const parsed of parseClasses(expandModeAwareColorClasses(classString, theme.colors))) {
3142
3210
  const styles = resolveUtility(parsed, theme);
3143
3211
  if (!styles) continue;
3144
3212
  const bucketKey = parsed.modifiers.length === 0 ? "base" : parsed.modifiers.join(":");
@@ -3153,7 +3221,7 @@ function resolve(classString, theme, darkMode = "attribute") {
3153
3221
  if (cached) return cached;
3154
3222
  const result = {};
3155
3223
  const onWeb = isWeb;
3156
- for (const parsed of parseClasses(classString)) {
3224
+ for (const parsed of parseClasses(expandModeAwareColorClasses(classString, theme.colors))) {
3157
3225
  const styles = resolveUtility(parsed, theme);
3158
3226
  if (!styles) {
3159
3227
  if (process.env.NODE_ENV !== "production" && !parsed.isArbitrary && !isKnownUtility(parsed.utility) && !parsed.original.startsWith("__")) {
@@ -3265,8 +3333,9 @@ function resolveOneRef(ref, colors) {
3265
3333
  const shade = ref.slice(lastDash + 1);
3266
3334
  if (!/^\d+$/.test(shade)) return null;
3267
3335
  const entry = colors[name];
3268
- if (!entry || typeof entry !== "object") return null;
3269
- return entry[shade] ?? null;
3336
+ if (!entry || typeof entry !== "object" || isModeAwareColor(entry)) return null;
3337
+ const target = entry[shade];
3338
+ return typeof target === "string" ? target : null;
3270
3339
  }
3271
3340
  function resolveColorRefs(colors) {
3272
3341
  const MAX_DEPTH = 5;
@@ -3279,14 +3348,17 @@ function resolveColorRefs(colors) {
3279
3348
  }
3280
3349
  return current;
3281
3350
  }
3351
+ function resolveValue(val) {
3352
+ return typeof val === "string" ? resolveChain(val) : { light: resolveChain(val.light), dark: resolveChain(val.dark) };
3353
+ }
3282
3354
  const out = {};
3283
3355
  for (const [key, val] of Object.entries(colors)) {
3284
- if (typeof val === "string") {
3285
- out[key] = resolveChain(val);
3356
+ if (typeof val === "string" || isModeAwareColor(val)) {
3357
+ out[key] = resolveValue(val);
3286
3358
  } else {
3287
3359
  const shades = {};
3288
- for (const [shade, hex] of Object.entries(val)) {
3289
- shades[shade] = resolveChain(hex);
3360
+ for (const [shade, v] of Object.entries(val)) {
3361
+ shades[shade] = resolveValue(v);
3290
3362
  }
3291
3363
  out[key] = shades;
3292
3364
  }
@@ -3427,6 +3499,7 @@ function subscribeGlobalDarkMode(callback) {
3427
3499
  defaultTheme,
3428
3500
  disableRuntimeCSS,
3429
3501
  escapeCSSSelector,
3502
+ expandModeAwareColorClasses,
3430
3503
  flatten,
3431
3504
  generateClassCSS,
3432
3505
  getActiveBreakpoints,
@@ -3446,6 +3519,7 @@ function subscribeGlobalDarkMode(callback) {
3446
3519
  initConfig,
3447
3520
  isKnownModifier,
3448
3521
  isKnownUtility,
3522
+ isModeAwareColor,
3449
3523
  isNative,
3450
3524
  isRuntimeCSSDisabled,
3451
3525
  isWeb,