@bookklik/senangstart-css 0.2.8 → 0.2.10

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.
Files changed (72) hide show
  1. package/dist/senangstart-css.js +2751 -1952
  2. package/dist/senangstart-css.min.js +266 -225
  3. package/dist/senangstart-tw.js +440 -77
  4. package/dist/senangstart-tw.min.js +1 -1
  5. package/docs/SYNTAX-REFERENCE.md +1731 -1590
  6. package/docs/guide/configuration.md +2 -2
  7. package/docs/guide/preflight.md +20 -1
  8. package/docs/guide/states.md +60 -0
  9. package/docs/ms/guide/configuration.md +2 -2
  10. package/docs/ms/guide/preflight.md +19 -0
  11. package/docs/ms/guide/states.md +60 -0
  12. package/docs/ms/reference/breakpoints.md +14 -0
  13. package/docs/ms/reference/colors.md +2 -2
  14. package/docs/ms/reference/space/height.md +10 -10
  15. package/docs/ms/reference/space/width.md +12 -12
  16. package/docs/ms/reference/visual/border-radius.md +50 -10
  17. package/docs/ms/reference/visual/contain.md +57 -0
  18. package/docs/ms/reference/visual/content-visibility.md +53 -0
  19. package/docs/ms/reference/visual/placeholder-color.md +92 -0
  20. package/docs/ms/reference/visual/writing-mode.md +53 -0
  21. package/docs/ms/reference/visual.md +6 -0
  22. package/docs/public/assets/senangstart-css.min.js +266 -225
  23. package/docs/public/llms.txt +63 -2
  24. package/docs/reference/breakpoints.md +14 -0
  25. package/docs/reference/colors.md +2 -2
  26. package/docs/reference/space/height.md +10 -10
  27. package/docs/reference/space/width.md +12 -12
  28. package/docs/reference/visual/border-radius.md +50 -10
  29. package/docs/reference/visual/contain.md +57 -0
  30. package/docs/reference/visual/content-visibility.md +53 -0
  31. package/docs/reference/visual/placeholder-color.md +92 -0
  32. package/docs/reference/visual/writing-mode.md +53 -0
  33. package/docs/reference/visual.md +7 -0
  34. package/docs/syntax-reference.json +2185 -2009
  35. package/package.json +1 -1
  36. package/public/senangstart.css +1 -1
  37. package/scripts/convert-tailwind.js +486 -89
  38. package/scripts/generate-docs.js +403 -403
  39. package/scripts/generate-llms-txt.js +28 -0
  40. package/src/cdn/senangstart-engine.js +37 -1927
  41. package/src/cdn/tw-conversion-engine.js +504 -78
  42. package/src/cli/commands/build.js +10 -0
  43. package/src/compiler/generators/css.js +400 -67
  44. package/src/compiler/generators/preflight.js +26 -13
  45. package/src/compiler/generators/typescript.js +3 -1
  46. package/src/compiler/index.js +27 -3
  47. package/src/compiler/parser.js +24 -7
  48. package/src/config/defaults.js +4 -1
  49. package/src/core/constants.js +5 -3
  50. package/src/definitions/index.js +7 -3
  51. package/src/definitions/layout.js +2 -2
  52. package/src/definitions/space.js +45 -19
  53. package/src/definitions/visual-performance.js +126 -0
  54. package/src/definitions/visual.js +25 -9
  55. package/src/index.js +47 -0
  56. package/src/utils/common.js +17 -5
  57. package/templates/senangstart.config.js +1 -1
  58. package/tests/helpers/test-utils.js +1 -1
  59. package/tests/integration/compiler.test.js +12 -1
  60. package/tests/unit/compiler/generators/css.coverage.test.js +833 -0
  61. package/tests/unit/compiler/generators/css.test.js +1520 -6
  62. package/tests/unit/compiler/generators/preflight.test.js +31 -0
  63. package/tests/unit/compiler/parser.test.js +26 -0
  64. package/tests/unit/config/defaults.test.js +2 -2
  65. package/tests/unit/convert-tailwind.cli.test.js +95 -0
  66. package/tests/unit/convert-tailwind.coverage.test.js +225 -0
  67. package/tests/unit/convert-tailwind.test.js +61 -21
  68. package/tests/unit/core/tokenizer-core.test.js +102 -0
  69. package/tests/unit/definitions/index.test.js +108 -0
  70. package/tests/unit/definitions/layout_definitions.test.js +40 -0
  71. package/tests/unit/utils/common.test.js +26 -0
  72. package/scripts/bundle-jit.js +0 -45
@@ -0,0 +1,833 @@
1
+
2
+ import { test } from 'node:test';
3
+ import assert from 'node:assert';
4
+ import {
5
+ generateCSS,
6
+ generateCSSVariables,
7
+ minifyCSS
8
+ } from '../../../../src/compiler/generators/css.js';
9
+ import { createTestConfig } from '../../../helpers/test-utils.js';
10
+
11
+ test('CSS Generator Coverage', async (t) => {
12
+ const config = createTestConfig();
13
+
14
+ await t.test('generateCSSVariables - Tailwind Scales', () => {
15
+ const css = generateCSSVariables(config);
16
+ assert.ok(css.includes('--tw-text-xs: 0.75rem'));
17
+ assert.ok(css.includes('--tw-leading-xs: 1rem'));
18
+ assert.ok(css.includes('--tw-font-bold: 700'));
19
+ });
20
+
21
+ await t.test('generateCSSVariables - Custom Line Heights', () => {
22
+ const customConfig = createTestConfig({
23
+ theme: {
24
+ fontSizeLineHeight: {
25
+ 'base': '1.5',
26
+ 'large': '1.2'
27
+ }
28
+ }
29
+ });
30
+ const css = generateCSSVariables(customConfig);
31
+ assert.ok(css.includes('--font-lh-base: 1.5'));
32
+ assert.ok(css.includes('--font-lh-large: 1.2'));
33
+ });
34
+
35
+ await t.test('Layout - Grid Column Span Full', () => {
36
+ const token = { property: 'col-span', value: 'full', attrType: 'layout', raw: 'col-span:full' };
37
+ const css = generateCSS([token], config);
38
+ assert.ok(css.includes('grid-column: 1 / -1'));
39
+ });
40
+
41
+ await t.test('Layout - Grid Column Start/End', () => {
42
+ const start = { property: 'col-start', value: '2', attrType: 'layout', raw: 'col-start:2' };
43
+ const end = { property: 'col-end', value: '4', attrType: 'layout', raw: 'col-end:4' };
44
+ const css = generateCSS([start, end], config);
45
+ assert.ok(css.includes('grid-column-start: 2'));
46
+ assert.ok(css.includes('grid-column-end: 4'));
47
+ });
48
+
49
+ await t.test('Layout - Grid Row Span Full', () => {
50
+ const token = { property: 'row-span', value: 'full', attrType: 'layout', raw: 'row-span:full' };
51
+ const css = generateCSS([token], config);
52
+ assert.ok(css.includes('grid-row: 1 / -1'));
53
+ });
54
+
55
+ await t.test('Layout - Grid Row Start/End', () => {
56
+ const start = { property: 'row-start', value: '1', attrType: 'layout', raw: 'row-start:1' };
57
+ const end = { property: 'row-end', value: '3', attrType: 'layout', raw: 'row-end:3' };
58
+ const css = generateCSS([start, end], config);
59
+ assert.ok(css.includes('grid-row-start: 1'));
60
+ assert.ok(css.includes('grid-row-end: 3'));
61
+ });
62
+
63
+ await t.test('Layout - Grid Auto Columns', () => {
64
+ const auto = { property: 'auto-cols', value: 'min', attrType: 'layout', raw: 'auto-cols:min' };
65
+ const arb = { property: 'auto-cols', value: 'minmax(100px, auto)', isArbitrary: true, attrType: 'layout', raw: 'auto-cols:[minmax(100px, auto)]' };
66
+ const css = generateCSS([auto, arb], config);
67
+ assert.ok(css.includes('grid-auto-columns: min-content'));
68
+ assert.ok(css.includes('grid-auto-columns: minmax(100px, auto)'));
69
+ });
70
+
71
+ await t.test('Layout - Grid Auto Rows', () => {
72
+ const auto = { property: 'auto-rows', value: 'fr', attrType: 'layout', raw: 'auto-rows:fr' };
73
+ const arb = { property: 'auto-rows', value: '200px', isArbitrary: true, attrType: 'layout', raw: 'auto-rows:[200px]' };
74
+ const css = generateCSS([auto, arb], config);
75
+ assert.ok(css.includes('grid-auto-rows: minmax(0, 1fr)'));
76
+ assert.ok(css.includes('grid-auto-rows: 200px'));
77
+ });
78
+
79
+ await t.test('Layout - Border Spacing', () => {
80
+ const base = { property: 'border-spacing', value: 'medium', attrType: 'layout', raw: 'border-spacing:medium' };
81
+ const x = { property: 'border-spacing-x', value: 'small', attrType: 'layout', raw: 'border-spacing-x:small' };
82
+ const y = { property: 'border-spacing-y', value: 'big', attrType: 'layout', raw: 'border-spacing-y:big' };
83
+ const css = generateCSS([base, x, y], config);
84
+ assert.ok(css.includes('border-spacing: var(--s-medium)'));
85
+ assert.ok(css.includes('border-spacing: var(--s-small) 0'));
86
+ assert.ok(css.includes('border-spacing: 0 var(--s-big)'));
87
+ });
88
+
89
+ await t.test('Visual - Transforms and 3D', () => {
90
+ const tokens = [
91
+ { property: 'translate-x', value: 'full', attrType: 'visual', raw: 'translate-x:full' },
92
+ { property: 'translate-x', value: '10px', isArbitrary: true, attrType: 'visual', raw: 'translate-x:[10px]' },
93
+ { property: 'rotate-x', value: '45', attrType: 'visual', raw: 'rotate-x:45' },
94
+ { property: 'rotate-x', value: '45deg', isArbitrary: true, attrType: 'visual', raw: 'rotate-x:[45deg]' },
95
+ { property: 'rotate-y', value: '45', attrType: 'visual', raw: 'rotate-y:45' },
96
+ { property: 'rotate-y', value: '45deg', isArbitrary: true, attrType: 'visual', raw: 'rotate-y:[45deg]' },
97
+ { property: 'rotate-z', value: '45', attrType: 'visual', raw: 'rotate-z:45' },
98
+ { property: 'rotate-z', value: '45deg', isArbitrary: true, attrType: 'visual', raw: 'rotate-z:[45deg]' },
99
+ { property: 'skew-x', value: '10', attrType: 'visual', raw: 'skew-x:10' },
100
+ { property: 'skew-x', value: '10deg', isArbitrary: true, attrType: 'visual', raw: 'skew-x:[10deg]' },
101
+ { property: 'skew-y', value: '10', attrType: 'visual', raw: 'skew-y:10' },
102
+ { property: 'skew-y', value: '10deg', isArbitrary: true, attrType: 'visual', raw: 'skew-y:[10deg]' },
103
+ { property: '-skew-x', value: '10', attrType: 'visual', raw: '-skew-x:10' },
104
+ { property: '-skew-x', value: '10deg', isArbitrary: true, attrType: 'visual', raw: '-skew-x:[10deg]' },
105
+ { property: '-skew-y', value: '10', attrType: 'visual', raw: '-skew-y:10' },
106
+ { property: '-skew-y', value: '10deg', isArbitrary: true, attrType: 'visual', raw: '-skew-y:[10deg]' },
107
+ { property: 'translate-z', value: 'near', attrType: 'visual', raw: 'translate-z:near' },
108
+ { property: 'translate-z', value: '10px', isArbitrary: true, attrType: 'visual', raw: 'translate-z:[10px]' },
109
+ { property: 'origin', value: 'top-right', attrType: 'visual', raw: 'origin:top-right' },
110
+ { property: 'origin', value: 'custom', attrType: 'visual', raw: 'origin:custom' },
111
+ { property: 'origin', value: '10%_20%', isArbitrary: true, attrType: 'visual', raw: 'origin:[10%_20%]' },
112
+ { property: 'perspective', value: 'big', attrType: 'visual', raw: 'perspective:big' },
113
+ { property: 'perspective', value: 'unknown', attrType: 'visual', raw: 'perspective:unknown' },
114
+ { property: 'perspective', value: '500px', isArbitrary: true, attrType: 'visual', raw: 'perspective:[500px]' },
115
+ { property: 'perspective-origin', value: 'bottom-left', attrType: 'visual', raw: 'perspective-origin:bottom-left' },
116
+ { property: 'perspective-origin', value: 'custom', attrType: 'visual', raw: 'perspective-origin:custom' },
117
+ { property: 'perspective-origin', value: '10%_20%', isArbitrary: true, attrType: 'visual', raw: 'perspective-origin:[10%_20%]' }
118
+ ];
119
+ const css = generateCSS(tokens, config);
120
+ assert.ok(css.includes('rotateX(45deg)'));
121
+ assert.ok(css.includes('skewX(-10deg)'));
122
+
123
+ // Branch coverage: directional translation & rotation defaults
124
+ const transDefaults = [
125
+ { property: 'translate-y', value: 'unknown', attrType: 'visual', raw: 'translate-y:unknown' },
126
+ { property: 'translate-z', value: 'unknown', attrType: 'visual', raw: 'translate-z:unknown' },
127
+ { property: 'rotate-x', value: '90', attrType: 'visual', raw: 'rotate-x:90' },
128
+ { property: 'rotate-y', value: '90', attrType: 'visual', raw: 'rotate-y:90' },
129
+ { property: 'rotate-z', value: '90', attrType: 'visual', raw: 'rotate-z:90' }
130
+ ];
131
+ const cssTransDefaults = generateCSS(transDefaults, config);
132
+ assert.ok(cssTransDefaults.includes('translateY(var(--s-unknown))'));
133
+ assert.ok(cssTransDefaults.includes('translateZ(var(--s-unknown))'));
134
+ assert.ok(cssTransDefaults.includes('rotateX(90deg)'));
135
+ });
136
+
137
+ await t.test('Visual - Transitions and Animations', () => {
138
+ const tokens = [
139
+ { property: 'transition', value: 'colors', attrType: 'visual', raw: 'transition:colors' },
140
+ { property: 'transition', value: 'opacity,transform', isArbitrary: true, attrType: 'visual', raw: 'transition:[opacity,transform]' },
141
+ { property: 'duration', value: '150ms', isArbitrary: true, attrType: 'visual', raw: 'duration:[150ms]' },
142
+ { property: 'duration', value: '0.5s', isArbitrary: true, attrType: 'visual', raw: 'duration:[0.5s]' },
143
+ { property: 'ease', value: 'in-out', attrType: 'visual', raw: 'ease:in-out' },
144
+ { property: 'ease', value: 'cubic-bezier(0,0,0,0)', isArbitrary: true, attrType: 'visual', raw: 'ease:[cubic-bezier(0,0,0,0)]' },
145
+ { property: 'delay', value: '100', attrType: 'visual', raw: 'delay:100' },
146
+ { property: 'delay', value: '0.2s', isArbitrary: true, attrType: 'visual', raw: 'delay:[0.2s]' },
147
+ { property: 'animate', value: 'spin', attrType: 'visual', raw: 'animate:spin' },
148
+ { property: 'animate', value: 'none', attrType: 'visual', raw: 'animate:none' },
149
+ { property: 'animate', value: 'my-anim_2s_linear', isArbitrary: true, attrType: 'visual', raw: 'animate:[my-anim_2s_linear]' },
150
+ { property: 'animation-duration', value: 'fast', attrType: 'visual', raw: 'animation-duration:fast' },
151
+ { property: 'animation-delay', value: 'fast', attrType: 'visual', raw: 'animation-delay:fast' }
152
+ ];
153
+ const css = generateCSS(tokens, config);
154
+ assert.ok(css.includes('transition:'));
155
+ assert.ok(css.includes('transition-duration: 150ms'));
156
+ assert.ok(css.includes('transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)'));
157
+ assert.ok(css.includes('animation: my-anim 2s linear'));
158
+ assert.ok(css.includes('animation-duration: 150ms'));
159
+ assert.ok(css.includes('animation-delay: 150ms'));
160
+
161
+ // Branch coverage: fallback defaults
162
+ const defaults = [
163
+ { property: 'duration', value: 'unknown', attrType: 'visual', raw: 'duration:unknown' },
164
+ { property: 'ease', value: 'unknown', attrType: 'visual', raw: 'ease:unknown' },
165
+ { property: 'delay', value: 'unknown', attrType: 'visual', raw: 'delay:unknown' },
166
+ { property: 'animation-duration', value: 'unknown', attrType: 'visual', raw: 'animation-duration:unknown' },
167
+ { property: 'animation-duration', value: '1s', isArbitrary: true, attrType: 'visual', raw: 'animation-duration:[1s]' },
168
+ { property: 'animation-delay', value: '0.5s', isArbitrary: true, attrType: 'visual', raw: 'animation-delay:[0.5s]' }
169
+ ];
170
+ const cssDefaults = generateCSS(defaults, config);
171
+ assert.ok(cssDefaults.includes('transition-duration: 200ms'));
172
+ assert.ok(cssDefaults.includes('transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)'));
173
+ assert.ok(cssDefaults.includes('transition-delay: 200ms'));
174
+ assert.ok(cssDefaults.includes('animation-duration: 1s'));
175
+ assert.ok(cssDefaults.includes('animation-delay: 0.5s'));
176
+ });
177
+
178
+ await t.test('Visual - Filters and Blends', () => {
179
+ const tokens = [
180
+ { property: 'blur', value: 'small', attrType: 'visual', raw: 'blur:small' },
181
+ { property: 'blur', value: '10px', isArbitrary: true, attrType: 'visual', raw: 'blur:[10px]' },
182
+ { property: '-blur', value: 'small', attrType: 'visual', raw: '-blur:small' },
183
+ { property: 'brightness', value: '50', attrType: 'visual', raw: 'brightness:50' },
184
+ { property: 'brightness', value: '0.5', isArbitrary: true, attrType: 'visual', raw: 'brightness:[0.5]' },
185
+ { property: 'contrast', value: '50', attrType: 'visual', raw: 'contrast:50' },
186
+ { property: 'grayscale', value: '50', attrType: 'visual', raw: 'grayscale:50' },
187
+ { property: 'hue-rotate', value: '90', attrType: 'visual', raw: 'hue-rotate:90' },
188
+ { property: '-hue-rotate', value: '90', attrType: 'visual', raw: '-hue-rotate:90' },
189
+ { property: 'invert', value: '50', attrType: 'visual', raw: 'invert:50' },
190
+ { property: 'saturate', value: '50', attrType: 'visual', raw: 'saturate:50' },
191
+ { property: 'sepia', value: '50', attrType: 'visual', raw: 'sepia:50' },
192
+ { property: 'drop-shadow', value: 'small', attrType: 'visual', raw: 'drop-shadow:small' },
193
+ { property: 'drop-shadow', value: '0_0_5px_rgba(0,0,0,0.5)', isArbitrary: true, attrType: 'visual', raw: 'drop-shadow:[0_0_5px_rgba(0,0,0,0.5)]' },
194
+ { property: 'backdrop-blur', value: 'small', attrType: 'visual', raw: 'backdrop-blur:small' },
195
+ { property: 'brightness', value: '150', isArbitrary: true, attrType: 'visual', raw: 'brightness:[150]' },
196
+ { property: 'contrast', value: '150', isArbitrary: true, attrType: 'visual', raw: 'contrast:[150]' },
197
+ { property: 'grayscale', value: '50%', isArbitrary: true, attrType: 'visual', raw: 'grayscale:[50%]' },
198
+ { property: 'hue-rotate', value: '45deg', isArbitrary: true, attrType: 'visual', raw: 'hue-rotate:[45deg]' },
199
+ { property: 'invert', value: '50%', isArbitrary: true, attrType: 'visual', raw: 'invert:[50%]' },
200
+ { property: 'saturate', value: '150', isArbitrary: true, attrType: 'visual', raw: 'saturate:[150]' },
201
+ { property: 'sepia', value: '50%', isArbitrary: true, attrType: 'visual', raw: 'sepia:[50%]' },
202
+ { property: 'backdrop-brightness', value: '150', isArbitrary: true, attrType: 'visual', raw: 'backdrop-brightness:[150]' },
203
+ { property: 'backdrop-contrast', value: '150', isArbitrary: true, attrType: 'visual', raw: 'backdrop-contrast:[150]' },
204
+ { property: 'backdrop-grayscale', value: '50%', isArbitrary: true, attrType: 'visual', raw: 'backdrop-grayscale:[50%]' },
205
+ { property: 'backdrop-hue-rotate', value: '45deg', isArbitrary: true, attrType: 'visual', raw: 'backdrop-hue-rotate:[45deg]' },
206
+ { property: 'backdrop-invert', value: '50%', isArbitrary: true, attrType: 'visual', raw: 'backdrop-invert:[50%]' },
207
+ { property: 'backdrop-opacity', value: '0.5', isArbitrary: true, attrType: 'visual', raw: 'backdrop-opacity:[0.5]' },
208
+ { property: 'backdrop-saturate', value: '150', isArbitrary: true, attrType: 'visual', raw: 'backdrop-saturate:[150]' },
209
+ { property: 'backdrop-sepia', value: '50%', isArbitrary: true, attrType: 'visual', raw: 'backdrop-sepia:[50%]' },
210
+ { property: 'mix-blend', value: 'multiply', attrType: 'visual', raw: 'mix-blend:multiply' },
211
+ { property: 'bg-blend', value: 'screen', attrType: 'visual', raw: 'bg-blend:screen' }
212
+ ];
213
+ const css = generateCSS(tokens, config);
214
+ assert.ok(css.includes('filter: brightness(150)'));
215
+ assert.ok(css.includes('backdrop-filter: brightness(150)'));
216
+ assert.ok(css.includes('filter: blur(4px)'));
217
+ assert.ok(css.includes('filter: blur(10px)'));
218
+ assert.ok(css.includes('backdrop-filter: blur(4px)'));
219
+ assert.ok(css.includes('mix-blend-mode: multiply'));
220
+ assert.ok(css.includes('background-blend-mode: screen'));
221
+
222
+ // Branch coverage: fallback defaults // Filters unknown
223
+ assert.match(generateCSS([{ property: 'backdrop-blur', value: 'unknown', attrType: 'visual', raw: 'backdrop-blur:unknown' }], createTestConfig()), /backdrop-filter: blur\(8px\)/);
224
+ const filterDefaults = [
225
+ { property: 'brightness', value: 'unknown', attrType: 'visual', raw: 'brightness:unknown' },
226
+ { property: 'contrast', value: 'unknown', attrType: 'visual', raw: 'contrast:unknown' },
227
+ { property: 'drop-shadow', value: 'unknown', attrType: 'visual', raw: 'drop-shadow:unknown' },
228
+ { property: 'grayscale', value: 'unknown', attrType: 'visual', raw: 'grayscale:unknown' },
229
+ { property: 'invert', value: 'unknown', attrType: 'visual', raw: 'invert:unknown' },
230
+ { property: 'saturate', value: 'unknown', attrType: 'visual', raw: 'saturate:unknown' },
231
+ { property: 'sepia', value: 'unknown', attrType: 'visual', raw: 'sepia:unknown' },
232
+ { property: 'blur', value: 'unknown', attrType: 'visual', raw: 'blur:unknown' },
233
+ { property: 'backdrop-blur', value: 'unknown', attrType: 'visual', raw: 'backdrop-blur:unknown' },
234
+ { property: 'backdrop-brightness', value: 'unknown', attrType: 'visual', raw: 'backdrop-brightness:unknown' },
235
+ { property: 'backdrop-contrast', value: 'unknown', attrType: 'visual', raw: 'backdrop-contrast:unknown' },
236
+ { property: 'backdrop-grayscale', value: 'unknown', attrType: 'visual', raw: 'backdrop-grayscale:unknown' },
237
+ { property: 'backdrop-invert', value: 'unknown', attrType: 'visual', raw: 'backdrop-invert:unknown' },
238
+ { property: 'backdrop-opacity', value: 'unknown', attrType: 'visual', raw: 'backdrop-opacity:unknown' },
239
+ { property: 'backdrop-saturate', value: 'unknown', attrType: 'visual', raw: 'backdrop-saturate:unknown' },
240
+ { property: 'backdrop-sepia', value: 'unknown', attrType: 'visual', raw: 'backdrop-sepia:unknown' }
241
+ ];
242
+ const cssFilterDefaults = generateCSS(filterDefaults, config);
243
+ assert.ok(cssFilterDefaults.includes('filter: brightness(1)'));
244
+ assert.ok(cssFilterDefaults.includes('filter: blur(8px)'));
245
+ assert.ok(cssFilterDefaults.includes('backdrop-filter: blur(8px)'));
246
+ assert.ok(cssFilterDefaults.includes('backdrop-filter: opacity(1)'));
247
+ });
248
+
249
+ await t.test('Visual - Borders and Outlines', () => {
250
+ const tokens = [
251
+ { property: 'border-w', value: '2', attrType: 'visual', raw: 'border-w:2' },
252
+ { property: 'border-t-w', value: '2', attrType: 'visual', raw: 'border-t-w:2' },
253
+ { property: 'border-l-w', value: '2', attrType: 'visual', raw: 'border-l-w:2' },
254
+ { property: 'border-r-w', value: '2', attrType: 'visual', raw: 'border-r-w:2' },
255
+ { property: 'border-b-w', value: '2', attrType: 'visual', raw: 'border-b-w:2' },
256
+ { property: 'border-x-w', value: '2', attrType: 'visual', raw: 'border-x-w:2' },
257
+ { property: 'border-y-w', value: '2', attrType: 'visual', raw: 'border-y-w:2' },
258
+ { property: 'border', value: 'blue-500', attrType: 'visual', raw: 'border:blue-500' },
259
+ { property: 'border-t', value: 'blue-500', attrType: 'visual', raw: 'border-t:blue-500' },
260
+ { property: 'border-r', value: 'blue-500', attrType: 'visual', raw: 'border-r:blue-500' },
261
+ { property: 'border-l', value: 'blue-500', attrType: 'visual', raw: 'border-l:blue-500' },
262
+ { property: 'border-b', value: 'blue-500', attrType: 'visual', raw: 'border-b:blue-500' },
263
+ { property: 'border-x', value: 'blue-500', attrType: 'visual', raw: 'border-x:blue-500' },
264
+ { property: 'border-y', value: 'blue-500', attrType: 'visual', raw: 'border-y:blue-500' },
265
+ { property: 'outline', value: 'blue-500', attrType: 'visual', raw: 'outline:blue-500' },
266
+ { property: 'outline-offset', value: 'small', attrType: 'visual', raw: 'outline-offset:small' },
267
+ { property: 'outline-offset', value: '2px', isArbitrary: true, attrType: 'visual', raw: 'outline-offset:[2px]' },
268
+ { property: 'ring', value: 'none', attrType: 'visual', raw: 'ring:none' },
269
+ { property: 'ring', value: 'thin', attrType: 'visual', raw: 'ring:thin' },
270
+ { property: 'ring-color', value: 'blue-500', attrType: 'visual', raw: 'ring-color:blue-500' },
271
+ { property: 'ring-w', value: '2', attrType: 'visual', raw: 'ring-w:2' },
272
+ { property: 'ring-offset', value: 'regular', attrType: 'visual', raw: 'ring-offset:regular' },
273
+ { property: 'ring-offset-color', value: 'white', attrType: 'visual', raw: 'ring-offset-color:white' },
274
+ { property: 'rounded', value: 'medium', attrType: 'visual', raw: 'rounded:medium' },
275
+ { property: 'mask-image', value: 'custom.png', isArbitrary: true, attrType: 'visual', raw: 'mask-image:[custom.png]' },
276
+ { property: 'mask-position', value: '10px_20px', isArbitrary: true, attrType: 'visual', raw: 'mask-position:[10px_20px]' },
277
+ { property: 'mask-size', value: '50%_auto', isArbitrary: true, attrType: 'visual', raw: 'mask-size:[50%_auto]' },
278
+ { property: 'mask-repeat', value: 'custom-repeat', attrType: 'visual', raw: 'mask-repeat:custom-repeat' },
279
+ { property: 'mask-origin', value: 'custom-origin', attrType: 'visual', raw: 'mask-origin:custom-origin' }
280
+ ];
281
+ const css = generateCSS(tokens, config);
282
+ assert.ok(css.includes('mask-image: url(custom.png)'));
283
+ assert.ok(css.includes('mask-position: 10px 20px'));
284
+ assert.ok(css.includes('mask-size: 50% auto'));
285
+ assert.ok(css.includes('mask-repeat: custom-repeat'));
286
+ assert.ok(css.includes('mask-origin: custom-origin'));
287
+ assert.ok(css.includes('border-width: var(--s-2)'));
288
+ assert.ok(css.includes('border-top-width: var(--s-2)'));
289
+ assert.ok(css.includes('border-bottom-width: var(--s-2)'));
290
+ assert.ok(css.includes('border-left-width: var(--s-2)'));
291
+ assert.ok(css.includes('border-right-width: var(--s-2)'));
292
+ assert.ok(css.includes('border-color: var(--c-blue-500)'));
293
+ assert.ok(css.includes('border-top-color: var(--c-blue-500)'));
294
+ assert.ok(css.includes('border-radius: var(--r-medium)'));
295
+ assert.ok(css.includes('outline-color: var(--c-blue-500)'));
296
+ assert.ok(css.includes('outline-offset: var(--s-small)'));
297
+ assert.ok(css.includes('outline-offset: 2px'));
298
+ assert.ok(css.includes('--ss-ring-color: var(--c-blue-500)'));
299
+ assert.ok(css.includes('--ss-ring-width: 1px'));
300
+ assert.ok(css.includes('--ss-ring-offset-width: var(--s-regular)'));
301
+ });
302
+
303
+ await t.test('Space - Special Sizing (min, max, fit)', () => {
304
+ const w_min = { property: 'w', value: 'min', attrType: 'space', raw: 'w:min' };
305
+ const h_max = { property: 'h', value: 'max', attrType: 'space', raw: 'h:max' };
306
+ const css = generateCSS([w_min, h_max], config);
307
+ assert.ok(css.includes('width: min-content'));
308
+ assert.ok(css.includes('height: max-content'));
309
+ });
310
+
311
+ await t.test('Space - Tailwind Prefix & Negative', () => {
312
+ const pos = { property: 'p', value: 'tw-4', attrType: 'space', raw: 'p:tw-4' };
313
+ const neg = { property: 'm', value: '-tw-2', attrType: 'space', raw: 'm:-tw-2' };
314
+ const css = generateCSS([pos, neg], config);
315
+ assert.ok(css.includes('var(--tw-4)'));
316
+ assert.ok(css.includes('calc(var(--tw-2) * -1)'));
317
+ });
318
+
319
+ await t.test('Space - Percentage Adjectives', () => {
320
+ // Width with percentage adjectives
321
+ const w_full = { property: 'w', value: 'full', attrType: 'space', raw: 'w:full' };
322
+ const w_half = { property: 'w', value: 'half', attrType: 'space', raw: 'w:half' };
323
+ const w_third = { property: 'w', value: 'third', attrType: 'space', raw: 'w:third' };
324
+ const w_third_2x = { property: 'w', value: 'third-2x', attrType: 'space', raw: 'w:third-2x' };
325
+ const w_quarter = { property: 'w', value: 'quarter', attrType: 'space', raw: 'w:quarter' };
326
+ const w_quarter_3x = { property: 'w', value: 'quarter-3x', attrType: 'space', raw: 'w:quarter-3x' };
327
+
328
+ // Height with percentage adjectives
329
+ const h_half = { property: 'h', value: 'half', attrType: 'space', raw: 'h:half' };
330
+
331
+ // Fractional values (backwards compatibility)
332
+ const w_1_2 = { property: 'w', value: '1/2', attrType: 'space', raw: 'w:1/2' };
333
+ const h_2_3 = { property: 'h', value: '2/3', attrType: 'space', raw: 'h:2/3' };
334
+
335
+ const css = generateCSS([w_full, w_half, w_third, w_third_2x, w_quarter, w_quarter_3x, h_half, w_1_2, h_2_3], config);
336
+ assert.ok(css.includes('width: 100%'));
337
+ assert.ok(css.includes('width: 50%'));
338
+ assert.ok(css.includes('width: 33.333333%'));
339
+ assert.ok(css.includes('width: 66.666667%'));
340
+ assert.ok(css.includes('width: 25%'));
341
+ assert.ok(css.includes('width: 75%'));
342
+ assert.ok(css.includes('height: 50%'));
343
+ });
344
+
345
+ await t.test('Layout - Positioning Percentage Adjectives', () => {
346
+ // Positioning with percentage adjectives (for centering patterns)
347
+ const top_half = { property: 'top', value: 'half', attrType: 'layout', raw: 'top:half' };
348
+ const left_half = { property: 'left', value: 'half', attrType: 'layout', raw: 'left:half' };
349
+ const right_quarter = { property: 'right', value: 'quarter', attrType: 'layout', raw: 'right:quarter' };
350
+
351
+ // Negative percentage adjectives
352
+ const top_neg_half = { property: 'top', value: '-half', attrType: 'layout', raw: 'top:-half' };
353
+
354
+ // Fractional values
355
+ const left_1_3 = { property: 'left', value: '1/3', attrType: 'layout', raw: 'left:1/3' };
356
+
357
+ // Inset with percentage
358
+ const inset_full = { property: 'inset', value: 'full', attrType: 'layout', raw: 'inset:full' };
359
+
360
+ const css = generateCSS([top_half, left_half, right_quarter, top_neg_half, left_1_3, inset_full], config);
361
+ assert.ok(css.includes('top: 50%'));
362
+ assert.ok(css.includes('left: 50%'));
363
+ assert.ok(css.includes('right: 25%'));
364
+ assert.ok(css.includes('top: -50%'));
365
+ assert.ok(css.includes('left: 33.333333%'));
366
+ assert.ok(css.includes('inset: 100%'));
367
+ });
368
+
369
+ await t.test('Visual - Background Image URL and Gradients', () => {
370
+ const url = { property: 'bg-image', value: 'hero.jpg', attrType: 'visual', raw: 'bg-image:hero.jpg' };
371
+ const arb = { property: 'bg-image', value: 'custom.png', isArbitrary: true, attrType: 'visual', raw: 'bg-image:[custom.png]' };
372
+ const grad = { property: 'bg-image', value: 'gradient-to-t', attrType: 'visual', raw: 'bg-image:gradient-to-t' };
373
+ const css = generateCSS([url, arb, grad], config);
374
+ assert.ok(css.includes('background-image: url(hero.jpg)'));
375
+ assert.ok(css.includes('background-image: url(custom.png)'));
376
+ assert.ok(css.includes('linear-gradient(to top,'));
377
+ });
378
+
379
+ await t.test('Visual - Background Attachment & Clip', () => {
380
+ const attach = { property: 'bg-attachment', value: 'fixed', attrType: 'visual', raw: 'bg-attachment:fixed' };
381
+ const clip_text = { property: 'bg-clip', value: 'text', attrType: 'visual', raw: 'bg-clip:text' };
382
+ const css = generateCSS([attach, clip_text], config);
383
+ assert.ok(css.includes('background-attachment: fixed'));
384
+ assert.ok(css.includes('background-clip: text'));
385
+ });
386
+
387
+ await t.test('Visual - Opacity Edge Cases', () => {
388
+ const arb = { property: 'opacity', value: 'inherit', isArbitrary: true, attrType: 'visual', raw: 'opacity:[inherit]' };
389
+ const css = generateCSS([arb], config);
390
+ assert.ok(css.includes('opacity: inherit'));
391
+ });
392
+
393
+ await t.test('Integration - Peer Selectors', () => {
394
+ const tokens = [
395
+ { raw: 'uid1', attrType: 'interact' },
396
+ {
397
+ raw: 'bg-primary',
398
+ attrType: 'visual',
399
+ property: 'bg',
400
+ value: 'primary',
401
+ state: 'hover'
402
+ }
403
+ ];
404
+ const css = generateCSS(tokens, config);
405
+ assert.ok(css.includes('[interact~="uid1"]'), 'interact selector missing');
406
+ assert.ok(css.includes('~ [listens~="uid1"]'), 'listens selector missing');
407
+ });
408
+
409
+ await t.test('Integration - Divide with State', () => {
410
+ const token = {
411
+ raw: 'divide:primary',
412
+ attrType: 'visual',
413
+ property: 'divide',
414
+ value: 'primary',
415
+ state: 'hover'
416
+ };
417
+ const css = generateCSS([token], config);
418
+ assert.ok(css.includes('> :not([hidden]) ~ :not([hidden]):hover'));
419
+ });
420
+
421
+ await t.test('Integration - Unknown Breakpoints', () => {
422
+ const tokens = [
423
+ {
424
+ raw: 'unknown:block',
425
+ attrType: 'layout',
426
+ property: 'block',
427
+ value: 'block',
428
+ breakpoint: 'unknown-bp'
429
+ }
430
+ ];
431
+ const css = generateCSS(tokens, config);
432
+ assert.ok(css.includes('@media'));
433
+ });
434
+
435
+ await t.test('Integration - Animation Keyframes', () => {
436
+ const css = generateCSS([], config);
437
+ assert.ok(css.includes('@keyframes spin'));
438
+ });
439
+
440
+ await t.test('Integration - Custom Dark Selector', () => {
441
+ const token = { state: 'dark', property: 'bg', value: 'black', attrType: 'visual', raw: 'dark:bg:black' };
442
+ const configCustom = createTestConfig({ darkMode: ['selector', '[data-theme="dark"]'] });
443
+ const css = generateCSS([token], configCustom);
444
+ assert.ok(css.includes('[data-theme="dark"] [visual~="dark:bg:black"]'));
445
+ });
446
+
447
+ await t.test('Visual - Font Weight Base', () => {
448
+ const token = { property: 'font', value: 'bold', attrType: 'visual', raw: 'font:bold' };
449
+ const css = generateCSS([token], config);
450
+ assert.ok(css.includes('font-weight: var(--fw-bold)'));
451
+ });
452
+
453
+ await t.test('Visual - Text Size Base', () => {
454
+ const token = { property: 'text-size', value: 'medium', attrType: 'visual', raw: 'text-size:medium' };
455
+ const css = generateCSS([token], config);
456
+ assert.ok(css.includes('font-size: var(--font-medium)'));
457
+ assert.ok(css.includes('line-height: var(--font-lh-medium)'));
458
+ });
459
+
460
+ await t.test('Layout - Grid Row Span Numeric', () => {
461
+ const token = { property: 'row-span', value: '2', attrType: 'layout', raw: 'row-span:2' };
462
+ const css = generateCSS([token], config);
463
+ assert.ok(css.includes('grid-row: span 2 / span 2'));
464
+ });
465
+
466
+ await t.test('Layout - Grid Template Columns and Rows', () => {
467
+ const cols_none = { property: 'grid-cols', value: 'none', attrType: 'layout', raw: 'grid-cols:none' };
468
+ const cols_sub = { property: 'grid-cols', value: 'subgrid', attrType: 'layout', raw: 'grid-cols:subgrid' };
469
+ const cols_arb = { property: 'grid-cols', value: '200px_1fr', isArbitrary: true, attrType: 'layout', raw: 'grid-cols:[200px_1fr]' };
470
+
471
+ const rows_none = { property: 'grid-rows', value: 'none', attrType: 'layout', raw: 'grid-rows:none' };
472
+ const rows_sub = { property: 'grid-rows', value: 'subgrid', attrType: 'layout', raw: 'grid-rows:subgrid' };
473
+ const rows_arb = { property: 'grid-rows', value: '100px_auto', isArbitrary: true, attrType: 'layout', raw: 'grid-rows:[100px_auto]' };
474
+
475
+ const css = generateCSS([cols_none, cols_sub, cols_arb, rows_none, rows_sub, rows_arb], config);
476
+ assert.ok(css.includes('grid-template-columns: none'));
477
+ assert.ok(css.includes('grid-template-columns: subgrid'));
478
+ assert.ok(css.includes('grid-template-columns: 200px 1fr'));
479
+ assert.ok(css.includes('grid-template-rows: none'));
480
+ assert.ok(css.includes('grid-template-rows: subgrid'));
481
+ assert.ok(css.includes('grid-template-rows: 100px auto'));
482
+ });
483
+
484
+ await t.test('Layout - Overflow and Aspect Ratio', () => {
485
+ const ov = { property: 'overflow', value: 'hidden', attrType: 'layout', raw: 'overflow:hidden' };
486
+ const ovX = { property: 'overflow-x', value: 'scroll', attrType: 'layout', raw: 'overflow-x:scroll' };
487
+ const ovY = { property: 'overflow-y', value: 'auto', attrType: 'layout', raw: 'overflow-y:auto' };
488
+ const aspectSquare = { property: 'aspect', value: 'square', attrType: 'layout', raw: 'aspect:square' };
489
+ const aspectVideo = { property: 'aspect', value: 'video', attrType: 'layout', raw: 'aspect:video' };
490
+ const aspectArb = { property: 'aspect', value: '21/9', isArbitrary: true, attrType: 'layout', raw: 'aspect:[21/9]' };
491
+
492
+ const css = generateCSS([ov, ovX, ovY, aspectSquare, aspectVideo, aspectArb], config);
493
+ assert.ok(css.includes('overflow: hidden'));
494
+ assert.ok(css.includes('overflow-x: scroll'));
495
+ assert.ok(css.includes('overflow-y: auto'));
496
+ assert.ok(css.includes('aspect-ratio: 1 / 1'));
497
+ assert.ok(css.includes('aspect-ratio: 16 / 9'));
498
+ assert.ok(css.includes('aspect-ratio: 21/9'));
499
+ });
500
+
501
+ await t.test('Layout - Object Fit and Position', () => {
502
+ const objFit = { property: 'object', value: 'cover', attrType: 'layout', raw: 'object:cover' };
503
+ const objPos = { property: 'object-pos', value: 'center_top', isArbitrary: true, attrType: 'layout', raw: 'object-pos:[center_top]' };
504
+ const placeItems = { property: 'place-items', value: 'center', attrType: 'layout', raw: 'place-items:center' };
505
+ const placeSelf = { property: 'place-self', value: 'end', attrType: 'layout', raw: 'place-self:end' };
506
+ const justifyItems = { property: 'justify-items', value: 'center', attrType: 'layout', raw: 'justify-items:center' };
507
+ const justifySelf = { property: 'justify-self', value: 'start', attrType: 'layout', raw: 'justify-self:start' };
508
+ const alignContent = { property: 'content', value: 'center', attrType: 'layout', raw: 'content:center' };
509
+ const placeContent = { property: 'place-content', value: 'evenly', attrType: 'layout', raw: 'place-content:evenly' };
510
+ const justify = { property: 'justify', value: 'between', attrType: 'layout', raw: 'justify:between' };
511
+
512
+ const css = generateCSS([objFit, objPos, placeItems, placeSelf, justifyItems, justifySelf, alignContent, placeContent, justify], config);
513
+ assert.ok(css.includes('object-fit: cover'));
514
+ assert.ok(css.includes('object-position: center top'));
515
+ assert.ok(css.includes('place-items: center'));
516
+ assert.ok(css.includes('place-self: end'));
517
+ assert.ok(css.includes('justify-items: center'));
518
+ assert.ok(css.includes('justify-self: start'));
519
+ assert.ok(css.includes('align-content: center'));
520
+ assert.ok(css.includes('place-content: space-evenly'));
521
+ assert.ok(css.includes('justify-content: space-between'));
522
+ });
523
+
524
+ await t.test('Layout - Inset and Positioning', () => {
525
+ const insetArb = { property: 'inset', value: '10px', isArbitrary: true, attrType: 'layout', raw: 'inset:[10px]' };
526
+ const topArb = { property: 'top', value: '5px', isArbitrary: true, attrType: 'layout', raw: 'top:[5px]' };
527
+ const insetX = { property: 'inset-x', value: '0', attrType: 'layout', raw: 'inset-x:0' };
528
+ const insetY = { property: 'inset-y', value: 'medium', attrType: 'layout', raw: 'inset-y:medium' };
529
+
530
+ const css = generateCSS([insetArb, topArb, insetX, insetY], config);
531
+ assert.ok(css.includes('inset: 10px'));
532
+ assert.ok(css.includes('top: 5px'));
533
+ assert.ok(css.includes('left: 0; right: 0;'));
534
+ assert.ok(css.includes('top: var(--s-medium); bottom: var(--s-medium);'));
535
+ });
536
+
537
+ await t.test('Layout - Columns and Overscroll', () => {
538
+ const cols = { property: 'cols', value: '3', attrType: 'layout', raw: 'cols:3' };
539
+ const over = { property: 'overscroll', value: 'contain', attrType: 'layout', raw: 'overscroll:contain' };
540
+ const overX = { property: 'overscroll-x', value: 'none', attrType: 'layout', raw: 'overscroll-x:none' };
541
+ const overY = { property: 'overscroll-y', value: 'auto', attrType: 'layout', raw: 'overscroll-y:auto' };
542
+
543
+ const css = generateCSS([cols, over, overX, overY], config);
544
+ assert.ok(css.includes('columns: 3'));
545
+ assert.ok(css.includes('overscroll-behavior: contain'));
546
+ assert.ok(css.includes('overscroll-behavior-x: none'));
547
+ assert.ok(css.includes('overscroll-behavior-y: auto'));
548
+ });
549
+
550
+ await t.test('Layout - Flex and Order', () => {
551
+ const basis = { property: 'basis', value: 'medium', attrType: 'layout', raw: 'basis:medium' };
552
+ const basisArb = { property: 'basis', value: '10px', isArbitrary: true, attrType: 'layout', raw: 'basis:[10px]' };
553
+ const flex1 = { property: 'flex', value: '1', attrType: 'layout', raw: 'flex:1' };
554
+ const flexAuto = { property: 'flex', value: 'auto', attrType: 'layout', raw: 'flex:auto' };
555
+ const flexInitial = { property: 'flex', value: 'initial', attrType: 'layout', raw: 'flex:initial' };
556
+ const flexNone = { property: 'flex', value: 'none', attrType: 'layout', raw: 'flex:none' };
557
+ const orderFirst = { property: 'order', value: 'first', attrType: 'layout', raw: 'order:first' };
558
+ const orderLast = { property: 'order', value: 'last', attrType: 'layout', raw: 'order:last' };
559
+ const orderNone = { property: 'order', value: 'none', attrType: 'layout', raw: 'order:none' };
560
+ const orderVal = { property: 'order', value: '5', attrType: 'layout', raw: 'order:5' };
561
+
562
+ const css = generateCSS([basis, basisArb, flex1, flexAuto, flexInitial, flexNone, orderFirst, orderLast, orderNone, orderVal], config);
563
+ assert.ok(css.includes('flex-basis: var(--s-medium)'));
564
+ assert.ok(css.includes('flex-basis: 10px'));
565
+ assert.ok(css.includes('order: -9999'));
566
+ });
567
+
568
+ await t.test('Layout - Map Branch Coverage', () => {
569
+ const tokens = [
570
+ { property: 'items', value: 'start', attrType: 'layout', raw: 'items:start' },
571
+ { property: 'items', value: 'custom', attrType: 'layout', raw: 'items:custom' },
572
+ { property: 'self', value: 'center', attrType: 'layout', raw: 'self:center' },
573
+ { property: 'self', value: 'custom', attrType: 'layout', raw: 'self:custom' },
574
+ { property: 'place-content', value: 'between', attrType: 'layout', raw: 'place-content:between' },
575
+ { property: 'place-content', value: 'custom', attrType: 'layout', raw: 'place-content:custom' },
576
+ { property: 'justify-items', value: 'start', attrType: 'layout', raw: 'justify-items:start' },
577
+ { property: 'justify-items', value: 'custom', attrType: 'layout', raw: 'justify-items:custom' },
578
+ { property: 'justify-self', value: 'end', attrType: 'layout', raw: 'justify-self:end' },
579
+ { property: 'justify-self', value: 'custom', attrType: 'layout', raw: 'justify-self:custom' },
580
+ { property: 'align-content', value: 'center', attrType: 'layout', raw: 'align-content:center' },
581
+ { property: 'align-content', value: 'custom', attrType: 'layout', raw: 'align-content:custom' },
582
+ { property: 'object-fit', value: 'cover', attrType: 'layout', raw: 'object-fit:cover' },
583
+ { property: 'object-fit', value: 'custom', attrType: 'layout', raw: 'object-fit:custom' },
584
+ { property: 'object-position', value: 'bottom', attrType: 'layout', raw: 'object-position:bottom' },
585
+ { property: 'object-position', value: 'custom', attrType: 'layout', raw: 'object-position:custom' },
586
+ { property: 'overflow', value: 'hidden', attrType: 'layout', raw: 'overflow:hidden' },
587
+ { property: 'overflow', value: 'visible_scroll', attrType: 'layout', raw: 'overflow:visible_scroll' },
588
+ { property: 'overflow-x', value: 'auto', attrType: 'layout', raw: 'overflow-x:auto' },
589
+ { property: 'overflow-y', value: 'scroll', attrType: 'layout', raw: 'overflow-y:scroll' },
590
+ { property: 'overscroll', value: 'none', attrType: 'layout', raw: 'overscroll:none' },
591
+ { property: 'overscroll-x', value: 'contain', attrType: 'layout', raw: 'overscroll-x:contain' },
592
+ { property: 'overscroll-y', value: 'auto', attrType: 'layout', raw: 'overscroll-y:auto' }
593
+ ];
594
+ const css = generateCSS(tokens, config);
595
+ assert.ok(css.includes('align-items: flex-start'));
596
+ assert.ok(css.includes('align-items: custom'));
597
+ });
598
+
599
+ await t.test('Branch Coverage - Full Sweep', () => {
600
+ const tokens = [
601
+ // Scale
602
+ { property: 'scale', value: '50', attrType: 'visual', raw: 'scale:50' },
603
+ { property: 'scale', value: '0.5', isArbitrary: true, attrType: 'visual', raw: 'scale:[0.5]' },
604
+ { property: 'scale-x', value: '50', attrType: 'visual', raw: 'scale-x:50' },
605
+ { property: 'scale-x', value: '0.5', isArbitrary: true, attrType: 'visual', raw: 'scale-x:[0.5]' },
606
+ { property: 'scale-y', value: '50', attrType: 'visual', raw: 'scale-y:50' },
607
+ { property: 'scale-y', value: '0.5', isArbitrary: true, attrType: 'visual', raw: 'scale-y:[0.5]' },
608
+ // Typography
609
+ { property: 'font-family', value: 'sans', attrType: 'visual', raw: 'font-family:sans' },
610
+ { property: 'font-family', value: 'Arial', isArbitrary: true, attrType: 'visual', raw: 'font-family:[Arial]' },
611
+ { property: 'letter-spacing', value: 'wide', attrType: 'visual', raw: 'letter-spacing:wide' },
612
+ { property: 'letter-spacing', value: '0.1em', isArbitrary: true, attrType: 'visual', raw: 'letter-spacing:[0.1em]' },
613
+ { property: 'line-height', value: 'loose', attrType: 'visual', raw: 'line-height:loose' },
614
+ { property: 'line-height', value: '2', isArbitrary: true, attrType: 'visual', raw: 'line-height:[2]' },
615
+ // Rotate
616
+ { property: 'rotate', value: '45', attrType: 'visual', raw: 'rotate:45' },
617
+ { property: 'rotate', value: '45deg', isArbitrary: true, attrType: 'visual', raw: 'rotate:[45deg]' },
618
+ // Translation
619
+ { property: 'translate-x', value: 'medium', attrType: 'visual', raw: 'translate-x:medium' },
620
+ { property: 'translate-y', value: 'medium', attrType: 'visual', raw: 'translate-y:medium' },
621
+ { property: '-translate-x', value: 'medium', attrType: 'visual', raw: '-translate-x:medium' },
622
+ { property: '-translate-x', value: '10px', isArbitrary: true, attrType: 'visual', raw: '-translate-x:[10px]' },
623
+ { property: '-translate-y', value: 'medium', attrType: 'visual', raw: '-translate-y:medium' },
624
+ { property: '-translate-y', value: '10px', isArbitrary: true, attrType: 'visual', raw: '-translate-y:[10px]' },
625
+ // Skew
626
+ { property: 'skew-x', value: '10', attrType: 'visual', raw: 'skew-x:10' },
627
+ { property: 'skew-y', value: '10', attrType: 'visual', raw: 'skew-y:10' },
628
+ { property: '-skew-x', value: '10', attrType: 'visual', raw: '-skew-x:10' },
629
+ { property: '-skew-y', value: '10', attrType: 'visual', raw: '-skew-y:10' },
630
+ // Interactivity
631
+ { property: 'resize', value: 'both', attrType: 'visual', raw: 'resize:both' },
632
+ { property: 'resize', value: 'custom', attrType: 'visual', raw: 'resize:custom' },
633
+ // SVG
634
+ { property: 'fill', value: 'red-500', attrType: 'visual', raw: 'fill:red-500' },
635
+ { property: 'fill', value: 'none', attrType: 'visual', raw: 'fill:none' },
636
+ { property: 'fill', value: 'current', attrType: 'visual', raw: 'fill:current' },
637
+ { property: 'fill', value: '#ff0000', isArbitrary: true, attrType: 'visual', raw: 'fill:[#ff0000]' },
638
+ { property: 'stroke', value: 'red-500', attrType: 'visual', raw: 'stroke:red-500' },
639
+ { property: 'stroke', value: 'none', attrType: 'visual', raw: 'stroke:none' },
640
+ { property: 'stroke', value: 'current', attrType: 'visual', raw: 'stroke:current' },
641
+ { property: 'stroke', value: '#ff0000', isArbitrary: true, attrType: 'visual', raw: 'stroke:[#ff0000]' },
642
+ { property: 'stroke-w', value: '2', attrType: 'visual', raw: 'stroke-w:2' },
643
+ { property: 'stroke-w', value: '10px', isArbitrary: true, attrType: 'visual', raw: 'stroke-w:[10px]' }
644
+ ];
645
+ const css = generateCSS(tokens, config);
646
+ assert.ok(css.length > 0);
647
+ });
648
+
649
+ await t.test('Interactivity - Scroll and Touch Snap', () => {
650
+ // Scroll margin/padding (static and arbitrary)
651
+ const tokens = [
652
+ { property: 'scroll-m', value: 'big', attrType: 'visual', raw: 'scroll-m:big' },
653
+ { property: 'scroll-m', value: '10px', isArbitrary: true, attrType: 'visual', raw: 'scroll-m:[10px]' },
654
+ { property: 'scroll-m-t', value: '10px', isArbitrary: true, attrType: 'visual', raw: 'scroll-m-t:[10px]' },
655
+ { property: 'scroll-m-r', value: 'small', attrType: 'visual', raw: 'scroll-m-r:small' },
656
+ { property: 'scroll-m-r', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-m-r:[5px]' },
657
+ { property: 'scroll-m-b', value: 'small', attrType: 'visual', raw: 'scroll-m-b:small' },
658
+ { property: 'scroll-m-b', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-m-b:[5px]' },
659
+ { property: 'scroll-m-l', value: 'small', attrType: 'visual', raw: 'scroll-m-l:small' },
660
+ { property: 'scroll-m-l', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-m-l:[5px]' },
661
+ { property: 'scroll-m-x', value: 'small', attrType: 'visual', raw: 'scroll-m-x:small' },
662
+ { property: 'scroll-m-x', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-m-x:[5px]' },
663
+ { property: 'scroll-m-y', value: 'small', attrType: 'visual', raw: 'scroll-m-y:small' },
664
+ { property: 'scroll-m-y', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-m-y:[5px]' },
665
+ { property: 'scroll-p', value: 'big', attrType: 'visual', raw: 'scroll-p:big' },
666
+ { property: 'scroll-p', value: '20px', isArbitrary: true, attrType: 'visual', raw: 'scroll-p:[20px]' },
667
+ { property: 'scroll-p-t', value: '10px', isArbitrary: true, attrType: 'visual', raw: 'scroll-p-t:[10px]' },
668
+ { property: 'scroll-p-r', value: 'small', attrType: 'visual', raw: 'scroll-p-r:small' },
669
+ { property: 'scroll-p-r', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-p-r:[5px]' },
670
+ { property: 'scroll-p-b', value: 'small', attrType: 'visual', raw: 'scroll-p-b:small' },
671
+ { property: 'scroll-p-b', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-p-b:[5px]' },
672
+ { property: 'scroll-p-l', value: 'small', attrType: 'visual', raw: 'scroll-p-l:small' },
673
+ { property: 'scroll-p-l', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-p-l:[5px]' },
674
+ { property: 'scroll-p-x', value: 'small', attrType: 'visual', raw: 'scroll-p-x:small' },
675
+ { property: 'scroll-p-x', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-p-x:[5px]' },
676
+ { property: 'scroll-p-y', value: 'small', attrType: 'visual', raw: 'scroll-p-y:small' },
677
+ { property: 'scroll-p-y', value: '5px', isArbitrary: true, attrType: 'visual', raw: 'scroll-p-y:[5px]' },
678
+ // Snap type presets and arbitrary (covers snapMap[value] || value)
679
+ { property: 'snap', value: 'x', attrType: 'visual', raw: 'snap:x' },
680
+ { property: 'snap', value: 'custom-mandatory', attrType: 'visual', raw: 'snap:custom-mandatory' },
681
+ // Touch action presets and arbitrary
682
+ { property: 'touch', value: 'auto', attrType: 'visual', raw: 'touch:auto' },
683
+ { property: 'touch', value: 'custom-action', attrType: 'visual', raw: 'touch:custom-action' },
684
+ // Will change presets and arbitrary
685
+ { property: 'will-change', value: 'scroll', attrType: 'visual', raw: 'will-change:scroll' },
686
+ { property: 'will-change', value: 'filter', attrType: 'visual', raw: 'will-change:filter' },
687
+ // Other interactivity
688
+ { property: 'accent', value: 'red-500', attrType: 'visual', raw: 'accent:red-500' },
689
+ { property: 'accent', value: '#ff0000', isArbitrary: true, attrType: 'visual', raw: 'accent:[#ff0000]' },
690
+ { property: 'appearance', value: 'none', attrType: 'visual', raw: 'appearance:none' },
691
+ { property: 'caret', value: 'blue-500', attrType: 'visual', raw: 'caret:blue-500' },
692
+ { property: 'caret', value: '#0000ff', isArbitrary: true, attrType: 'visual', raw: 'caret:[#0000ff]' },
693
+ { property: 'cursor', value: 'pointer', attrType: 'visual', raw: 'cursor:pointer' },
694
+ { property: 'select', value: 'none', attrType: 'visual', raw: 'select:none' },
695
+ { property: 'field-sizing', value: 'content', attrType: 'visual', raw: 'field-sizing:content' },
696
+ { property: 'forced-colors', value: 'none', attrType: 'visual', raw: 'forced-colors:none' }
697
+ ];
698
+
699
+ const css = generateCSS(tokens, config);
700
+ assert.ok(css.includes('scroll-margin: var(--s-big)'));
701
+ assert.ok(css.includes('scroll-margin: 10px'));
702
+ assert.ok(css.includes('scroll-margin-top: 10px'));
703
+ assert.ok(css.includes('scroll-padding: var(--s-big)'));
704
+ assert.ok(css.includes('scroll-padding: 20px'));
705
+ assert.ok(css.includes('scroll-snap-type: x mandatory'));
706
+ assert.ok(css.includes('scroll-snap-type: custom-mandatory'));
707
+ assert.ok(css.includes('touch-action: auto'));
708
+ assert.ok(css.includes('touch-action: custom-action'));
709
+ assert.ok(css.includes('will-change: scroll-position'));
710
+ assert.ok(css.includes('will-change: filter'));
711
+ assert.ok(css.includes('accent-color: var(--c-red-500)'));
712
+ assert.ok(css.includes('accent-color: #ff0000'));
713
+ });
714
+
715
+ await t.test('Integration - Branch Edge Cases', () => {
716
+ // 1. Dark mode missing in config (defaults to 'media')
717
+ const darkToken = { property: 'bg', value: 'red-500', state: 'dark', attrType: 'visual', raw: 'dark:bg:red-500' };
718
+ const configNoDark = createTestConfig({ darkMode: undefined });
719
+ const cssDark = generateCSS([darkToken], configNoDark);
720
+ assert.ok(cssDark.includes('@media (prefers-color-scheme: dark)'));
721
+
722
+ // 2. Responsive display reset - already covered but let's ensure the 'has(bpToken.raw)' branch
723
+ const baseFlex = { property: 'flex', attrType: 'layout', raw: 'flex' };
724
+ const respFlex = { property: 'flex', breakpoint: 'tab', attrType: 'layout', raw: 'flex' }; // Same as base, should NOT trigger reset
725
+ const cssReset = generateCSS([baseFlex, respFlex], config);
726
+ // Should NOT contain revert-layer because they are the same
727
+ assert.ok(!cssReset.includes('revert-layer'));
728
+
729
+ // 3. Token without attrType or unknown category
730
+ const noAttr = { property: 'display', value: 'block', raw: 'display-block' };
731
+ const unknownCat = { property: 'display', value: 'block', attrType: 'unknown', raw: 'unknown:block' };
732
+ const cssNoAttr = generateCSS([noAttr, unknownCat], config);
733
+ assert.ok(cssNoAttr.length > 0);
734
+
735
+ // 4. Unknown visual property
736
+ const unknownProp = { property: 'unknown-visual', value: 'value', attrType: 'visual', raw: 'unknown-visual:value' };
737
+ const cssUnknown = generateCSS([unknownProp], config);
738
+ assert.ok(!cssUnknown.includes('unknown-visual'));
739
+ });
740
+
741
+ await t.test('Integration - Preflight', () => {
742
+ const configPreflight = createTestConfig({ preflight: true });
743
+ const css = generateCSS([], configPreflight);
744
+ assert.ok(css.includes('SenangStart Preflight') || css.includes('box-sizing: border-box'));
745
+ });
746
+
747
+ await t.test('Integration - Responsive Display Reset', () => {
748
+ const tokens = [
749
+ { raw: 'hidden', attrType: 'layout', property: 'hidden', value: 'hidden' },
750
+ { raw: 'tab:flex', attrType: 'layout', property: 'flex', value: 'flex', breakpoint: 'tab' }
751
+ ];
752
+ const css = generateCSS(tokens, config);
753
+ assert.ok(css.includes('[layout~="tab:flex"] { display: revert-layer; }'));
754
+ });
755
+ await t.test('Interactivity and Snap Utilities', () => {
756
+ const config = createTestConfig();
757
+ const res = generateCSS([
758
+ { property: 'transform-style', value: 'preserve-3d', attrType: 'visual', raw: 'transform-style:preserve-3d' },
759
+ { property: 'backface', value: 'hidden', attrType: 'visual', raw: 'backface:hidden' },
760
+ { property: 'color-scheme', value: 'dark', attrType: 'visual', raw: 'color-scheme:dark' },
761
+ { property: 'pointer-events', value: 'none', attrType: 'visual', raw: 'pointer-events:none' },
762
+ { property: 'scroll', value: 'smooth', attrType: 'visual', raw: 'scroll:smooth' },
763
+ { property: 'snap-align', value: 'center', attrType: 'visual', raw: 'snap-align:center' },
764
+ { property: 'snap-stop', value: 'always', attrType: 'visual', raw: 'snap-stop:always' },
765
+ { property: 'accent', value: 'blue-500', attrType: 'visual', raw: 'accent:blue-500' },
766
+ { property: 'caret', value: 'red-500', attrType: 'visual', raw: 'caret:red-500' },
767
+ { property: 'scroll-m-x', value: '4', attrType: 'visual', raw: 'scroll-m-x:4' },
768
+ { property: 'scroll-m-y', value: '4', attrType: 'visual', raw: 'scroll-m-y:4' },
769
+ { property: 'scroll-p-x', value: '4', attrType: 'visual', raw: 'scroll-p-x:4' },
770
+ { property: 'scroll-p-y', value: '4', attrType: 'visual', raw: 'scroll-p-y:4' },
771
+ { property: 'touch', value: 'none', attrType: 'visual', raw: 'touch:none' },
772
+ { property: 'select', value: 'none', attrType: 'visual', raw: 'select:none' },
773
+ { property: 'will-change', value: 'transform', attrType: 'visual', raw: 'will-change:transform' }
774
+ ], config);
775
+
776
+ assert.match(res, /transform-style: preserve-3d/);
777
+ assert.match(res, /backface-visibility: hidden/);
778
+ assert.match(res, /color-scheme: dark/);
779
+ assert.match(res, /pointer-events: none/);
780
+ assert.match(res, /scroll-behavior: smooth/);
781
+ assert.match(res, /scroll-snap-align: center/);
782
+ assert.match(res, /scroll-snap-stop: always/);
783
+ assert.match(res, /accent-color: var\(--c-blue-500\)/);
784
+ assert.match(res, /caret-color: var\(--c-red-500\)/);
785
+ assert.match(res, /scroll-margin-left: var\(--s-4\); scroll-margin-right: var\(--s-4\);/);
786
+ assert.match(res, /touch-action: none/);
787
+ assert.match(res, /user-select: none/);
788
+ assert.match(res, /will-change: transform/);
789
+ });
790
+
791
+ await t.test('Space Shortcuts', () => {
792
+ const config = createTestConfig();
793
+ const res = generateCSS([
794
+ { property: 'p-x', value: '4', attrType: 'space', raw: 'p-x:4' },
795
+ { property: 'p-y', value: '4', attrType: 'space', raw: 'p-y:4' },
796
+ { property: 'm-x', value: '4', attrType: 'space', raw: 'm-x:4' },
797
+ { property: 'm-y', value: '4', attrType: 'space', raw: 'm-y:4' },
798
+ { property: 'g-x', value: '4', attrType: 'space', raw: 'g-x:4' },
799
+ { property: 'g-y', value: '4', attrType: 'space', raw: 'g-y:4' }
800
+ ], config);
801
+
802
+ assert.match(res, /padding-left: var\(--s-4\); padding-right: var\(--s-4\);/);
803
+ assert.match(res, /padding-top: var\(--s-4\); padding-bottom: var\(--s-4\);/);
804
+ assert.match(res, /margin-left: var\(--s-4\); margin-right: var\(--s-4\);/);
805
+ assert.match(res, /margin-top: var\(--s-4\); margin-bottom: var\(--s-4\);/);
806
+ assert.match(res, /column-gap: var\(--s-4\);/);
807
+ assert.match(res, /row-gap: var\(--s-4\);/);
808
+ });
809
+
810
+ await t.test('Dark Mode Selector Strategy', () => {
811
+ const tokens = [{ property: 'opacity', value: '50', attrType: 'visual', state: 'dark', raw: 'dark:opacity:50' }];
812
+ const config = createTestConfig({ darkMode: 'selector' });
813
+ const res = generateCSS(tokens, config);
814
+ assert.match(res, /\.dark \[visual~="dark:opacity:50"\]/);
815
+
816
+ // Custom selector
817
+ const config2 = createTestConfig({ darkMode: ['selector', '[data-theme="dark"]'] });
818
+ const res2 = generateCSS(tokens, config2);
819
+ assert.match(res2, /\[data-theme="dark"\] \[visual~="dark:opacity:50"\]/);
820
+ });
821
+
822
+ await t.test('Minify CSS', () => {
823
+ const input = `
824
+ /* comment */
825
+ .foo {
826
+ color: red;
827
+ margin: 10px 20px ;
828
+ }
829
+ `;
830
+ const minified = minifyCSS(input);
831
+ assert.strictEqual(minified, '.foo{color:red;margin:10px 20px;}');
832
+ });
833
+ });