@bookklik/senangstart-css 0.2.9 → 0.2.12

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 (69) hide show
  1. package/.agent/skills/add-utility/SKILL.md +65 -0
  2. package/.agent/workflows/add-utility.md +2 -0
  3. package/.agent/workflows/build.md +2 -0
  4. package/.agent/workflows/dev.md +2 -0
  5. package/AGENTS.md +30 -0
  6. package/dist/senangstart-css.js +607 -180
  7. package/dist/senangstart-css.min.js +234 -195
  8. package/dist/senangstart-tw.js +274 -8
  9. package/dist/senangstart-tw.min.js +1 -1
  10. package/docs/SYNTAX-REFERENCE.md +1731 -1590
  11. package/docs/guide/preflight.md +20 -1
  12. package/docs/ms/guide/preflight.md +19 -0
  13. package/docs/ms/reference/breakpoints.md +14 -0
  14. package/docs/ms/reference/visual/border-radius.md +50 -10
  15. package/docs/ms/reference/visual/contain.md +57 -0
  16. package/docs/ms/reference/visual/content-visibility.md +53 -0
  17. package/docs/ms/reference/visual/placeholder-color.md +92 -0
  18. package/docs/ms/reference/visual/ring-color.md +2 -2
  19. package/docs/ms/reference/visual/ring-offset.md +3 -3
  20. package/docs/ms/reference/visual/ring.md +5 -5
  21. package/docs/ms/reference/visual/writing-mode.md +53 -0
  22. package/docs/ms/reference/visual.md +6 -0
  23. package/docs/public/assets/senangstart-css.min.js +234 -195
  24. package/docs/public/llms.txt +45 -12
  25. package/docs/reference/breakpoints.md +14 -0
  26. package/docs/reference/visual/border-radius.md +50 -10
  27. package/docs/reference/visual/contain.md +57 -0
  28. package/docs/reference/visual/content-visibility.md +53 -0
  29. package/docs/reference/visual/placeholder-color.md +92 -0
  30. package/docs/reference/visual/ring-color.md +2 -2
  31. package/docs/reference/visual/ring-offset.md +3 -3
  32. package/docs/reference/visual/ring.md +5 -5
  33. package/docs/reference/visual/writing-mode.md +53 -0
  34. package/docs/reference/visual.md +7 -0
  35. package/docs/syntax-reference.json +2185 -2009
  36. package/package.json +1 -1
  37. package/scripts/convert-tailwind.js +300 -26
  38. package/scripts/generate-docs.js +403 -403
  39. package/src/cdn/senangstart-engine.js +5 -5
  40. package/src/cdn/tw-conversion-engine.js +305 -8
  41. package/src/cli/commands/build.js +51 -13
  42. package/src/cli/commands/dev.js +157 -93
  43. package/src/compiler/generators/css.js +467 -208
  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 +13 -6
  48. package/src/compiler/tokenizer.js +25 -23
  49. package/src/config/defaults.js +3 -0
  50. package/src/core/tokenizer-core.js +46 -19
  51. package/src/definitions/index.js +4 -1
  52. package/src/definitions/visual-borders.js +10 -10
  53. package/src/definitions/visual-performance.js +126 -0
  54. package/src/definitions/visual.js +25 -9
  55. package/src/utils/common.js +456 -27
  56. package/src/utils/node-io.js +82 -0
  57. package/tests/integration/dev-recovery.test.js +231 -0
  58. package/tests/unit/cli/memory-limits.test.js +169 -0
  59. package/tests/unit/compiler/css-generation-error-handling.test.js +204 -0
  60. package/tests/unit/compiler/generators/css-errors.test.js +102 -0
  61. package/tests/unit/compiler/generators/css.test.js +102 -5
  62. package/tests/unit/convert-tailwind.test.js +518 -431
  63. package/tests/unit/utils/common.test.js +376 -26
  64. package/tests/unit/utils/file-timeout.test.js +154 -0
  65. package/tests/unit/utils/theme-validation.test.js +181 -0
  66. package/tests/unit/compiler/generators/css.coverage.test.js +0 -833
  67. package/tests/unit/convert-tailwind.cli.test.js +0 -95
  68. package/tests/unit/security.test.js +0 -206
  69. /package/tests/unit/{convert-tailwind.coverage.test.js → convert-tailwind-edgecases.test.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bookklik/senangstart-css",
3
- "version": "0.2.9",
3
+ "version": "0.2.12",
4
4
  "description": "Fluent Style Utilities for Humans and AI",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -79,6 +79,32 @@ const spacingScale = {
79
79
  'auto': 'auto'
80
80
  };
81
81
 
82
+ // ======================
83
+ // LINE HEIGHT MAPPING
84
+ // ======================
85
+ const lineHeightScale = {
86
+ 'none': 'none', // line-height: 1
87
+ 'tight': 'tight', // line-height: 1.25
88
+ 'snug': 'snug', // line-height: 1.375
89
+ 'normal': 'normal', // line-height: 1.5
90
+ 'relaxed': 'relaxed', // line-height: 1.625
91
+ 'loose': 'loose' // line-height: 2
92
+ };
93
+
94
+ // ======================
95
+ // LETTER SPACING MAPPING
96
+ // ======================
97
+ const letterSpacingScale = {
98
+ 'tighter': 'tighter', // letter-spacing: -0.05em
99
+ 'tight': 'tight', // letter-spacing: -0.025em
100
+ 'normal': 'normal', // letter-spacing: 0
101
+ 'wide': 'wide', // letter-spacing: 0.025em
102
+ 'wider': 'wider', // letter-spacing: 0.05em
103
+ 'widest': 'widest' // letter-spacing: 0.1em
104
+ };
105
+
106
+ // ======================
107
+ // LAYOUT CLASS MAPPINGS
82
108
  // ======================
83
109
  // PERCENTAGE ADJECTIVES
84
110
  // ======================
@@ -88,7 +114,7 @@ const percentageAdjectives = {
88
114
  '1/3': 'third',
89
115
  '2/3': 'third-2x',
90
116
  '1/4': 'quarter',
91
- '2/4': 'quarter-2x',
117
+ '2/4': 'half', // 50%, same as 1/2
92
118
  '3/4': 'quarter-3x',
93
119
  // Less common fractions - keep as arbitrary for precision
94
120
  '1/5': '[20%]',
@@ -135,19 +161,37 @@ const shadowScale = {
135
161
  // FONT SIZE MAPPING
136
162
  // ======================
137
163
  const fontSizeScale = {
138
- 'xs': 'tiny',
139
- 'sm': 'small',
140
- 'base': 'medium',
141
- 'lg': 'big',
142
- 'xl': 'big',
143
- '2xl': 'giant',
144
- '3xl': 'giant',
145
- '4xl': 'vast',
146
- '5xl': 'vast',
147
- '6xl': 'vast',
148
- '7xl': 'vast',
149
- '8xl': 'vast',
150
- '9xl': 'vast'
164
+ 'xs': 'mini', // 0.75rem → mini
165
+ 'sm': 'small', // 0.875rem → small
166
+ 'base': 'base', // 1rem → base
167
+ 'lg': 'large', // 1.125rem → large
168
+ 'xl': 'big', // 1.25rem → big
169
+ '2xl': 'huge', // 1.5rem → huge
170
+ '3xl': 'grand', // 1.875rem → grand
171
+ '4xl': 'giant', // 2.25rem → giant
172
+ '5xl': 'mount', // 3rem → mount
173
+ '6xl': 'mega', // 3.75rem → mega
174
+ '7xl': 'giga', // 4.5rem → giga
175
+ '8xl': 'tera', // 6rem → tera
176
+ '9xl': 'hero' // 8rem → hero
177
+ };
178
+
179
+ // ======================
180
+ // Z-INDEX MAPPING
181
+ // ======================
182
+ const zIndexScale = {
183
+ '0': 'base', // z-index: 0
184
+ '10': 'low', // z-index: 10
185
+ '20': 'low', // z-index: 20
186
+ '30': 'low', // z-index: 30
187
+ '40': 'low', // z-index: 40
188
+ '50': 'mid', // z-index: 50
189
+ '60': 'high', // z-index: 60
190
+ '70': 'high', // z-index: 70
191
+ '80': 'high', // z-index: 80
192
+ '90': 'high', // z-index: 90
193
+ '100': 'high', // z-index: 100
194
+ 'auto': 'auto' // z-index: auto
151
195
  };
152
196
 
153
197
  // ======================
@@ -452,17 +496,17 @@ function getBorderWidth(value, options = {}) {
452
496
  }
453
497
 
454
498
 
455
- /**
456
- * Convert a single Tailwind class to SenangStart
457
- * Returns { category: 'layout'|'space'|'visual'|null, value: string }
458
- * @param {string} twClass - The Tailwind class to convert
459
- * @param {Object} options - Conversion options
460
- * @param {boolean} options.exact - If true, output tw-{value} prefix instead of semantic scale
461
- */
499
+ /**
500
+ * Convert a single Tailwind class to SenangStart
501
+ * Returns { category: 'layout'|'space'|'visual'|null, value: string }
502
+ * @param {string} twClass - The Tailwind class to convert
503
+ * @param {Object} options - Conversion options
504
+ * @param {boolean} options.exact - If true, output tw-{value} prefix instead of semantic scale
505
+ */
462
506
  function convertClass(twClass, options = {}) {
463
507
  // Handle prefixes (hover:, sm:, md:, etc.)
464
508
  const prefixMatch = twClass.match(
465
- /^(sm:|md:|lg:|xl:|2xl:|hover:|focus:|active:|disabled:|dark:)(.+)$/
509
+ /^(sm:|md:|lg:|xl:|2xl:|hover:|focus:|focus-visible:|active:|disabled:|dark:|group-hover:|peer-hover:|group-focus:|peer-focus:|group-active:|peer-active:|group-open:|peer-open:)(.+)$/
466
510
  );
467
511
  let prefix = "",
468
512
  baseClass = twClass;
@@ -515,6 +559,20 @@ function convertClass(twClass, options = {}) {
515
559
  return { category: 'visual', value: prefix + "text-size:" + size };
516
560
  }
517
561
 
562
+ // Line height
563
+ const leadingMatch = baseClass.match(/^leading-(\[.+\]|none|tight|snug|normal|relaxed|loose)$/);
564
+ if (leadingMatch) {
565
+ const val = leadingMatch[1];
566
+ return { category: 'visual', value: prefix + "leading:" + (lineHeightScale[val] || val) };
567
+ }
568
+
569
+ // Letter spacing
570
+ const trackingMatch = baseClass.match(/^tracking-(\[.+\]|tighter|tight|normal|wide|wider|widest)$/);
571
+ if (trackingMatch) {
572
+ const val = trackingMatch[1];
573
+ return { category: 'visual', value: prefix + "tracking:" + (letterSpacingScale[val] || val) };
574
+ }
575
+
518
576
  // Background color
519
577
  const bgMatch = baseClass.match(
520
578
  /^bg-((?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|white|black|transparent|current|inherit)(?:-\d+)?)$/
@@ -614,6 +672,18 @@ function convertClass(twClass, options = {}) {
614
672
  }
615
673
 
616
674
  // Border radius
675
+ // Handle directional variants: rounded-t-lg, rounded-tl-3xl, etc.
676
+ const directionalRoundedMatch = baseClass.match(/^rounded-([tblr]{1,2})-(.+)$/);
677
+ if (directionalRoundedMatch) {
678
+ const direction = directionalRoundedMatch[1];
679
+ const size = directionalRoundedMatch[2];
680
+ const scale = options.exact
681
+ ? `tw-${size}`
682
+ : radiusScale[size] || "medium";
683
+ return { category: 'visual', value: prefix + `rounded-${direction}:` + scale };
684
+ }
685
+
686
+ // Handle standard rounded: rounded, rounded-lg, rounded-full, etc.
617
687
  const roundedMatch = baseClass.match(/^rounded(?:-(.+))?$/);
618
688
  if (roundedMatch) {
619
689
  const size = roundedMatch[1] || "";
@@ -669,6 +739,33 @@ function convertClass(twClass, options = {}) {
669
739
  return { category: 'layout', value: prefix + "order:" + orderMatch[1] };
670
740
  }
671
741
 
742
+ // Z-index
743
+ const zIndexMatch = baseClass.match(/^-?z-(\d+|auto)$/);
744
+ if (zIndexMatch) {
745
+ const isNeg = baseClass.startsWith("-");
746
+ const val = zIndexMatch[1];
747
+ let zIndexVal = zIndexScale[val] || val;
748
+ if (isNeg) {
749
+ zIndexVal = `-${zIndexVal}`;
750
+ }
751
+ return { category: 'layout', value: prefix + "z:" + zIndexVal };
752
+ }
753
+
754
+ // Flex basis
755
+ const basisMatch = baseClass.match(/^basis-(\[.+\]|\d+\.?\d*|auto|full|1\/2|1\/3|2\/3|1\/4|2\/4|3\/4)$/);
756
+ if (basisMatch) {
757
+ let val = basisMatch[1];
758
+ if (val.startsWith('[') && val.endsWith(']')) {
759
+ // Keep arbitrary values as-is
760
+ } else if (percentageAdjectives[val]) {
761
+ // Map fractions to semantic names (1/2 → half, etc.)
762
+ val = percentageAdjectives[val];
763
+ } else if (val === '0') {
764
+ val = '0';
765
+ }
766
+ return { category: 'layout', value: prefix + "basis:" + val };
767
+ }
768
+
672
769
  // Grid columns
673
770
  const gridColsMatch = baseClass.match(/^grid-cols-(\d+|none)$/);
674
771
  if (gridColsMatch) {
@@ -699,14 +796,14 @@ function convertClass(twClass, options = {}) {
699
796
  if (positionMatch) {
700
797
  const prop = positionMatch[1];
701
798
  let val = positionMatch[2];
702
- // Handle 0 specially
703
- if (val === '0') {
704
- val = 'none';
705
- } else if (val.startsWith('[') && val.endsWith(']')) {
799
+ if (val.startsWith('[') && val.endsWith(']')) {
706
800
  // Keep arbitrary values as-is
707
801
  } else if (percentageAdjectives[val]) {
708
802
  // Map fractions to semantic names (1/2 → half, etc.)
709
803
  val = percentageAdjectives[val];
804
+ } else if (val === '0') {
805
+ // Keep 0 as-is for positioning (CSS: top: 0, not top: none)
806
+ val = '0';
710
807
  } else {
711
808
  val = getSpacingScale(val, options);
712
809
  }
@@ -981,6 +1078,183 @@ function convertClass(twClass, options = {}) {
981
1078
  };
982
1079
  }
983
1080
 
1081
+ // Border style
1082
+ const borderStyleMatch = baseClass.match(/^border-(solid|dashed|dotted|double|none)$/);
1083
+ if (borderStyleMatch) {
1084
+ return {
1085
+ category: "visual",
1086
+ value: prefix + "border-style:" + borderStyleMatch[1],
1087
+ };
1088
+ }
1089
+
1090
+ // Filter utilities
1091
+ // Blur
1092
+ const blurMatch = baseClass.match(/^blur-(0|sm|md|lg|xl|2xl|3xl)$/);
1093
+ if (blurMatch) {
1094
+ const blurScale = {
1095
+ '0': 'none',
1096
+ 'sm': 'tiny',
1097
+ 'md': 'small',
1098
+ 'lg': 'medium',
1099
+ 'xl': 'big',
1100
+ '2xl': 'giant',
1101
+ '3xl': 'vast'
1102
+ };
1103
+ return {
1104
+ category: "visual",
1105
+ value: prefix + "blur:" + blurScale[blurMatch[1]],
1106
+ };
1107
+ }
1108
+
1109
+ // Brightness
1110
+ const brightnessMatch = baseClass.match(/^brightness-(0|50|75|90|95|100|105|110|125|150|200)$/);
1111
+ if (brightnessMatch) {
1112
+ const brightnessScale = {
1113
+ '0': 'dim',
1114
+ '50': 'dim',
1115
+ '75': 'dark',
1116
+ '90': 'dark',
1117
+ '95': 'dark',
1118
+ '100': 'normal',
1119
+ '105': 'bright',
1120
+ '110': 'bright',
1121
+ '125': 'vivid',
1122
+ '150': 'vivid',
1123
+ '200': 'vivid'
1124
+ };
1125
+ return {
1126
+ category: "visual",
1127
+ value: prefix + "brightness:" + brightnessScale[brightnessMatch[1]],
1128
+ };
1129
+ }
1130
+
1131
+ // Contrast
1132
+ const contrastMatch = baseClass.match(/^contrast-(0|50|75|100|125|150|200)$/);
1133
+ if (contrastMatch) {
1134
+ const contrastScale = {
1135
+ '0': 'low',
1136
+ '50': 'low',
1137
+ '75': 'reduced',
1138
+ '100': 'normal',
1139
+ '125': 'high',
1140
+ '150': 'high',
1141
+ '200': 'max'
1142
+ };
1143
+ return {
1144
+ category: "visual",
1145
+ value: prefix + "contrast:" + contrastScale[contrastMatch[1]],
1146
+ };
1147
+ }
1148
+
1149
+ // Grayscale
1150
+ const grayscaleMatch = baseClass.match(/^grayscale(0)?$/);
1151
+ if (grayscaleMatch) {
1152
+ const val = grayscaleMatch[1] === '0' ? 'none' : 'full';
1153
+ return {
1154
+ category: "visual",
1155
+ value: prefix + "grayscale:" + val,
1156
+ };
1157
+ }
1158
+
1159
+ // Hue rotate
1160
+ const hueRotateMatch = baseClass.match(/^hue-rotate-(0|15|30|60|90|180)$/);
1161
+ if (hueRotateMatch) {
1162
+ return {
1163
+ category: "visual",
1164
+ value: prefix + "hue-rotate:" + hueRotateMatch[1],
1165
+ };
1166
+ }
1167
+
1168
+ // Invert
1169
+ const invertMatch = baseClass.match(/^invert(0)?$/);
1170
+ if (invertMatch) {
1171
+ const val = invertMatch[1] === '0' ? 'none' : 'full';
1172
+ return {
1173
+ category: "visual",
1174
+ value: prefix + "invert:" + val,
1175
+ };
1176
+ }
1177
+
1178
+ // Saturate
1179
+ const saturateMatch = baseClass.match(/^saturate-(0|50|100|150|200)$/);
1180
+ if (saturateMatch) {
1181
+ const saturateScale = {
1182
+ '0': 'none',
1183
+ '50': 'low',
1184
+ '100': 'normal',
1185
+ '150': 'high',
1186
+ '200': 'vivid'
1187
+ };
1188
+ return {
1189
+ category: "visual",
1190
+ value: prefix + "saturate:" + saturateScale[saturateMatch[1]],
1191
+ };
1192
+ }
1193
+
1194
+ // Sepia
1195
+ const sepiaMatch = baseClass.match(/^sepia(0)?$/);
1196
+ if (sepiaMatch) {
1197
+ const val = sepiaMatch[1] === '0' ? 'none' : 'full';
1198
+ return {
1199
+ category: "visual",
1200
+ value: prefix + "sepia:" + val,
1201
+ };
1202
+ }
1203
+
1204
+ // Animation utilities
1205
+ const animateMatch = baseClass.match(/^animate-(none|spin|ping|pulse|bounce)$/);
1206
+ if (animateMatch) {
1207
+ return {
1208
+ category: "visual",
1209
+ value: prefix + "animate:" + animateMatch[1],
1210
+ };
1211
+ }
1212
+
1213
+ // Transition utilities
1214
+ const transitionMatch = baseClass.match(/^transition(?:-(all|colors|opacity|shadow|transform|none))?$/);
1215
+ if (transitionMatch) {
1216
+ const type = transitionMatch[1] || 'all';
1217
+ return {
1218
+ category: "visual",
1219
+ value: prefix + "transition:" + type,
1220
+ };
1221
+ }
1222
+
1223
+ // Duration utilities
1224
+ const durationMatch = baseClass.match(/^duration-(\d+)$/);
1225
+ if (durationMatch) {
1226
+ const ms = parseInt(durationMatch[1]);
1227
+ let durationVal;
1228
+ if (ms <= 75) durationVal = 'instant';
1229
+ else if (ms <= 100) durationVal = 'quick';
1230
+ else if (ms <= 150) durationVal = 'fast';
1231
+ else if (ms <= 200) durationVal = 'normal';
1232
+ else if (ms <= 300) durationVal = 'slow';
1233
+ else if (ms <= 500) durationVal = 'slower';
1234
+ else durationVal = 'lazy';
1235
+ return {
1236
+ category: "visual",
1237
+ value: prefix + "duration:" + durationVal,
1238
+ };
1239
+ }
1240
+
1241
+ // Ease utilities
1242
+ const easeMatch = baseClass.match(/^ease-(linear|in|out|in-out)$/);
1243
+ if (easeMatch) {
1244
+ return {
1245
+ category: "visual",
1246
+ value: prefix + "ease:" + easeMatch[1],
1247
+ };
1248
+ }
1249
+
1250
+ // Outline
1251
+ if (baseClass === 'outline-none') {
1252
+ return {
1253
+ category: "visual",
1254
+ value: prefix + "outline:none",
1255
+ };
1256
+ }
1257
+
984
1258
  return null;
985
1259
  }
986
1260