@bookklik/senangstart-css 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * SenangStart CSS - Dev Command
3
+ * Watch mode with live compilation
4
+ */
5
+
6
+ import chokidar from 'chokidar';
7
+ import { build } from './build.js';
8
+ import logger from '../../utils/logger.js';
9
+
10
+ /**
11
+ * Dev command handler - watches files and rebuilds on changes
12
+ */
13
+ export async function dev(options = {}) {
14
+ logger.watch('Starting development mode...');
15
+
16
+ // Initial build
17
+ await build(options);
18
+
19
+ // Watch patterns
20
+ const watchPatterns = [
21
+ './**/*.html',
22
+ './**/*.htm',
23
+ './src/**/*.{html,jsx,tsx,vue,svelte}',
24
+ './pages/**/*.{html,jsx,tsx}',
25
+ './components/**/*.{html,jsx,tsx,vue,svelte}'
26
+ ];
27
+
28
+ // Ignore patterns
29
+ const ignorePatterns = [
30
+ '**/node_modules/**',
31
+ '**/dist/**',
32
+ '**/.git/**',
33
+ '**/public/**'
34
+ ];
35
+
36
+ // Create watcher
37
+ const watcher = chokidar.watch(watchPatterns, {
38
+ ignored: ignorePatterns,
39
+ persistent: true,
40
+ ignoreInitial: true
41
+ });
42
+
43
+ // Debounce timer
44
+ let debounceTimer = null;
45
+
46
+ function debouncedBuild() {
47
+ if (debounceTimer) {
48
+ clearTimeout(debounceTimer);
49
+ }
50
+
51
+ debounceTimer = setTimeout(async () => {
52
+ logger.watch('Change detected, rebuilding...');
53
+ await build(options);
54
+ }, 100);
55
+ }
56
+
57
+ // Watch events
58
+ watcher
59
+ .on('change', (path) => {
60
+ logger.info(`Changed: ${path}`);
61
+ debouncedBuild();
62
+ })
63
+ .on('add', (path) => {
64
+ logger.info(`Added: ${path}`);
65
+ debouncedBuild();
66
+ })
67
+ .on('unlink', (path) => {
68
+ logger.info(`Removed: ${path}`);
69
+ debouncedBuild();
70
+ });
71
+
72
+ logger.watch('Watching for changes... (Ctrl+C to stop)');
73
+ }
74
+
75
+ export default dev;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * SenangStart CSS - Init Command
3
+ * Creates senangstart.config.js in the project root
4
+ */
5
+
6
+ import { writeFileSync, existsSync, readFileSync } from 'fs';
7
+ import { join, dirname } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import logger from '../../utils/logger.js';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ export async function init() {
15
+ const configPath = join(process.cwd(), 'senangstart.config.js');
16
+
17
+ // Check if config already exists
18
+ if (existsSync(configPath)) {
19
+ logger.warn('senangstart.config.js already exists');
20
+ return;
21
+ }
22
+
23
+ // Read template
24
+ const templatePath = join(__dirname, '../../../templates/senangstart.config.js');
25
+ let template;
26
+
27
+ try {
28
+ template = readFileSync(templatePath, 'utf-8');
29
+ } catch (e) {
30
+ // Fallback template if file not found
31
+ template = `/**
32
+ * SenangStart CSS Configuration
33
+ */
34
+
35
+ export default {
36
+ content: [
37
+ './**/*.html',
38
+ './src/**/*.{html,jsx,tsx,vue,svelte}',
39
+ './components/**/*.{html,jsx,tsx}'
40
+ ],
41
+
42
+ output: {
43
+ css: './public/senangstart.css',
44
+ minify: true,
45
+ aiContext: './.cursorrules',
46
+ typescript: './types/senang.d.ts'
47
+ },
48
+
49
+ theme: {
50
+ // Add custom spacing, colors, etc.
51
+ }
52
+ }
53
+ `;
54
+ }
55
+
56
+ // Write config file
57
+ writeFileSync(configPath, template);
58
+
59
+ logger.success('Created senangstart.config.js');
60
+ logger.info('Edit this file to customize your theme');
61
+ logger.info('Run "senangstart dev" to start watching files');
62
+ }
63
+
64
+ export default init;
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SenangStart CSS - CLI Entry Point
5
+ * The Intent-First CSS Engine
6
+ */
7
+
8
+ import { program } from 'commander';
9
+ import { init } from './commands/init.js';
10
+ import { build } from './commands/build.js';
11
+ import { dev } from './commands/dev.js';
12
+
13
+ program
14
+ .name('senangstart')
15
+ .description('SenangStart CSS - The Intent-First CSS Engine\n\n "Speak Human. Compile to Logic."')
16
+ .version('1.0.0');
17
+
18
+ program
19
+ .command('init')
20
+ .description('Initialize a new senangstart.config.js')
21
+ .action(init);
22
+
23
+ program
24
+ .command('build')
25
+ .description('Compile CSS from source files')
26
+ .option('--minify', 'Minify CSS output')
27
+ .option('--config <path>', 'Path to config file', 'senangstart.config.js')
28
+ .action(build);
29
+
30
+ program
31
+ .command('dev')
32
+ .description('Watch mode with live compilation')
33
+ .option('--config <path>', 'Path to config file', 'senangstart.config.js')
34
+ .action(dev);
35
+
36
+ program.parse();
@@ -0,0 +1,128 @@
1
+ /**
2
+ * SenangStart CSS - AI Context Generator
3
+ * Generates .cursorrules or ai-context.md for LLM assistance
4
+ */
5
+
6
+ /**
7
+ * Generate AI context file content
8
+ * @param {Object} config - Configuration object
9
+ * @returns {string} - AI context markdown content
10
+ */
11
+ export function generateAIContext(config) {
12
+ const { theme } = config;
13
+
14
+ return `# SenangStart CSS - AI Context Guide
15
+
16
+ > This file helps AI assistants understand the SenangStart CSS framework.
17
+
18
+ ## Philosophy
19
+ "Speak Human. Compile to Logic."
20
+ Use natural adjectives instead of arbitrary numbers.
21
+
22
+ ## Tri-Attribute Syntax
23
+
24
+ | Attribute | Purpose | Example |
25
+ |-----------|---------|---------|
26
+ | \`layout\` | Structure & position | \`layout="flex col center"\` |
27
+ | \`space\` | Sizing & spacing | \`space="p:medium g:small"\` |
28
+ | \`visual\` | Colors & appearance | \`visual="bg:white rounded:big"\` |
29
+
30
+ ## Spacing Scale (Natural Objects)
31
+
32
+ | Keyword | Size | Mental Model |
33
+ |---------|------|--------------|
34
+ | \`none\` | ${theme.spacing.none} | No space |
35
+ | \`tiny\` | ${theme.spacing.tiny} | Pebble - Borders, offsets |
36
+ | \`small\` | ${theme.spacing.small} | Matchbox - Group related items |
37
+ | \`medium\` | ${theme.spacing.medium} | Smartphone - Standard default |
38
+ | \`big\` | ${theme.spacing.big} | Laptop - Separate sections |
39
+ | \`giant\` | ${theme.spacing.giant} | Door - Layout divisions |
40
+ | \`vast\` | ${theme.spacing.vast} | House - Hero sections |
41
+
42
+ ## Natural Language Mapping
43
+
44
+ When users say... | Do this...
45
+ ------------------|------------
46
+ "tighten it up" | Scale DOWN (medium → small → tiny)
47
+ "give it air" / "breathe" | Scale UP (medium → big → giant)
48
+ "make it pop" | Add \`shadow:big\`, increase \`rounded\`
49
+ "subtle" | Use \`shadow:small\`, \`rounded:small\`
50
+ "compact" | Use \`p:small g:tiny\`
51
+ "spacious" | Use \`p:big g:medium\` or larger
52
+
53
+ ## Layout Keywords
54
+
55
+ - **Display:** \`flex\`, \`grid\`, \`block\`, \`hidden\`
56
+ - **Direction:** \`row\`, \`col\`, \`row-reverse\`, \`col-reverse\`
57
+ - **Alignment:** \`center\`, \`between\`, \`start\`, \`end\`
58
+ - **Position:** \`absolute\`, \`relative\`, \`fixed\`, \`sticky\`
59
+ - **Z-Index:** \`z:base\`, \`z:low\`, \`z:mid\`, \`z:high\`, \`z:top\`
60
+
61
+ ## Space Syntax
62
+
63
+ Pattern: \`[breakpoint]:[property]:[scale]\`
64
+
65
+ - \`p:medium\` → padding: 16px
66
+ - \`m-b:big\` → margin-bottom: 32px
67
+ - \`g:small\` → gap: 8px
68
+ - \`tab:p:big\` → padding: 32px on tablet+
69
+ - \`w:[350px]\` → width: 350px (escape hatch)
70
+
71
+ ## Visual Keywords
72
+
73
+ - **Background:** \`bg:white\`, \`bg:primary\`, \`bg:[#hex]\`
74
+ - **Text:** \`text:dark\`, \`text:grey\`, \`text-size:big\`
75
+ - **Font:** \`font:bold\`, \`font:medium\`, \`font:normal\`
76
+ - **Rounded:** \`rounded:small\`, \`rounded:medium\`, \`rounded:big\`, \`rounded:round\`
77
+ - **Shadow:** \`shadow:small\`, \`shadow:medium\`, \`shadow:big\`
78
+
79
+ ## Responsive Prefixes
80
+
81
+ | Prefix | Breakpoint |
82
+ |--------|------------|
83
+ | \`mob:\` | ${theme.screens.mob}+ |
84
+ | \`tab:\` | ${theme.screens.tab}+ |
85
+ | \`lap:\` | ${theme.screens.lap}+ |
86
+ | \`desk:\` | ${theme.screens.desk}+ |
87
+
88
+ ## Color Palette
89
+
90
+ ${Object.entries(theme.colors).map(([key, value]) => `- \`${key}\`: ${value}`).join('\n')}
91
+
92
+ ## Common Patterns
93
+
94
+ ### Card
95
+ \`\`\`html
96
+ <div
97
+ layout="flex col"
98
+ space="p:medium"
99
+ visual="bg:white rounded:big shadow:medium"
100
+ >
101
+ Content
102
+ </div>
103
+ \`\`\`
104
+
105
+ ### Centered Container
106
+ \`\`\`html
107
+ <div space="max-w:[1200px] m-x:auto p-x:medium">
108
+ Content
109
+ </div>
110
+ \`\`\`
111
+
112
+ ### Navigation
113
+ \`\`\`html
114
+ <nav
115
+ layout="flex between center fixed z:top"
116
+ space="w:[100%] p-x:big p-y:small"
117
+ visual="bg:white shadow:small"
118
+ >
119
+ Navigation items
120
+ </nav>
121
+ \`\`\`
122
+
123
+ ---
124
+ *Generated by SenangStart CSS*
125
+ `;
126
+ }
127
+
128
+ export default { generateAIContext };
@@ -0,0 +1,344 @@
1
+ /**
2
+ * SenangStart CSS - CSS Generator
3
+ * Generates CSS from tokens using attribute selectors
4
+ */
5
+
6
+ /**
7
+ * Generate CSS custom properties from config
8
+ * @param {Object} config - Configuration object
9
+ * @returns {string} - CSS custom properties block
10
+ */
11
+ export function generateCSSVariables(config) {
12
+ const { theme } = config;
13
+ let css = ':root {\n';
14
+
15
+ // Spacing variables
16
+ for (const [key, value] of Object.entries(theme.spacing)) {
17
+ css += ` --s-${key}: ${value};\n`;
18
+ }
19
+
20
+ // Radius variables
21
+ for (const [key, value] of Object.entries(theme.radius)) {
22
+ css += ` --r-${key}: ${value};\n`;
23
+ }
24
+
25
+ // Shadow variables
26
+ for (const [key, value] of Object.entries(theme.shadow)) {
27
+ css += ` --shadow-${key}: ${value};\n`;
28
+ }
29
+
30
+ // Font size variables
31
+ for (const [key, value] of Object.entries(theme.fontSize)) {
32
+ css += ` --font-${key}: ${value};\n`;
33
+ }
34
+
35
+ // Font weight variables
36
+ for (const [key, value] of Object.entries(theme.fontWeight)) {
37
+ css += ` --fw-${key}: ${value};\n`;
38
+ }
39
+
40
+ // Color variables
41
+ for (const [key, value] of Object.entries(theme.colors)) {
42
+ css += ` --c-${key}: ${value};\n`;
43
+ }
44
+
45
+ // Z-index variables
46
+ for (const [key, value] of Object.entries(theme.zIndex)) {
47
+ css += ` --z-${key}: ${value};\n`;
48
+ }
49
+
50
+ css += '}\n\n';
51
+ return css;
52
+ }
53
+
54
+ /**
55
+ * Generate CSS rule for a layout token
56
+ */
57
+ function generateLayoutRule(token, config) {
58
+ const { property, value } = token;
59
+
60
+ const layoutMap = {
61
+ // Display
62
+ 'flex': 'display: flex;',
63
+ 'grid': 'display: grid;',
64
+ 'block': 'display: block;',
65
+ 'inline': 'display: inline-block;',
66
+ 'hidden': 'display: none;',
67
+
68
+ // Flex direction
69
+ 'row': 'flex-direction: row;',
70
+ 'col': 'flex-direction: column;',
71
+ 'row-reverse': 'flex-direction: row-reverse;',
72
+ 'col-reverse': 'flex-direction: column-reverse;',
73
+
74
+ // Alignment
75
+ 'center': 'justify-content: center; align-items: center;',
76
+ 'start': 'justify-content: flex-start; align-items: flex-start;',
77
+ 'end': 'justify-content: flex-end; align-items: flex-end;',
78
+ 'between': 'justify-content: space-between;',
79
+ 'around': 'justify-content: space-around;',
80
+ 'evenly': 'justify-content: space-evenly;',
81
+
82
+ // Wrap
83
+ 'wrap': 'flex-wrap: wrap;',
84
+ 'nowrap': 'flex-wrap: nowrap;',
85
+
86
+ // Position
87
+ 'absolute': 'position: absolute;',
88
+ 'relative': 'position: relative;',
89
+ 'fixed': 'position: fixed;',
90
+ 'sticky': 'position: sticky;'
91
+ };
92
+
93
+ // Z-index
94
+ if (property === 'z') {
95
+ return `z-index: var(--z-${value});`;
96
+ }
97
+
98
+ // Overflow
99
+ if (property === 'overflow') {
100
+ return `overflow: ${value};`;
101
+ }
102
+
103
+ return layoutMap[property] || '';
104
+ }
105
+
106
+ /**
107
+ * Generate CSS rule for a space token
108
+ */
109
+ function generateSpaceRule(token, config) {
110
+ const { property, value, isArbitrary } = token;
111
+
112
+ // Determine the CSS value
113
+ const cssValue = isArbitrary ? value : `var(--s-${value})`;
114
+
115
+ // Handle special values
116
+ if (value === 'auto') {
117
+ const autoValue = 'auto';
118
+
119
+ const propertyMap = {
120
+ 'm': `margin: ${autoValue};`,
121
+ 'm-x': `margin-left: ${autoValue}; margin-right: ${autoValue};`,
122
+ 'm-y': `margin-top: ${autoValue}; margin-bottom: ${autoValue};`,
123
+ 'm-t': `margin-top: ${autoValue};`,
124
+ 'm-r': `margin-right: ${autoValue};`,
125
+ 'm-b': `margin-bottom: ${autoValue};`,
126
+ 'm-l': `margin-left: ${autoValue};`
127
+ };
128
+
129
+ return propertyMap[property] || '';
130
+ }
131
+
132
+ const propertyMap = {
133
+ // Padding
134
+ 'p': `padding: ${cssValue};`,
135
+ 'p-t': `padding-top: ${cssValue};`,
136
+ 'p-r': `padding-right: ${cssValue};`,
137
+ 'p-b': `padding-bottom: ${cssValue};`,
138
+ 'p-l': `padding-left: ${cssValue};`,
139
+ 'p-x': `padding-left: ${cssValue}; padding-right: ${cssValue};`,
140
+ 'p-y': `padding-top: ${cssValue}; padding-bottom: ${cssValue};`,
141
+
142
+ // Margin
143
+ 'm': `margin: ${cssValue};`,
144
+ 'm-t': `margin-top: ${cssValue};`,
145
+ 'm-r': `margin-right: ${cssValue};`,
146
+ 'm-b': `margin-bottom: ${cssValue};`,
147
+ 'm-l': `margin-left: ${cssValue};`,
148
+ 'm-x': `margin-left: ${cssValue}; margin-right: ${cssValue};`,
149
+ 'm-y': `margin-top: ${cssValue}; margin-bottom: ${cssValue};`,
150
+
151
+ // Gap
152
+ 'g': `gap: ${cssValue};`,
153
+ 'g-x': `column-gap: ${cssValue};`,
154
+ 'g-y': `row-gap: ${cssValue};`,
155
+
156
+ // Sizing
157
+ 'w': `width: ${cssValue};`,
158
+ 'h': `height: ${cssValue};`,
159
+ 'min-w': `min-width: ${cssValue};`,
160
+ 'max-w': `max-width: ${cssValue};`,
161
+ 'min-h': `min-height: ${cssValue};`,
162
+ 'max-h': `max-height: ${cssValue};`
163
+ };
164
+
165
+ return propertyMap[property] || '';
166
+ }
167
+
168
+ /**
169
+ * Generate CSS rule for a visual token
170
+ */
171
+ function generateVisualRule(token, config) {
172
+ const { property, value, isArbitrary } = token;
173
+
174
+ const rules = {
175
+ // Background
176
+ 'bg': () => {
177
+ const cssValue = isArbitrary ? value : `var(--c-${value})`;
178
+ return `background-color: ${cssValue};`;
179
+ },
180
+
181
+ // Text color
182
+ 'text': () => {
183
+ // Check if it's alignment
184
+ if (['left', 'center', 'right'].includes(value)) {
185
+ return `text-align: ${value};`;
186
+ }
187
+ const cssValue = isArbitrary ? value : `var(--c-${value})`;
188
+ return `color: ${cssValue};`;
189
+ },
190
+
191
+ // Font size
192
+ 'text-size': () => {
193
+ const cssValue = isArbitrary ? value : `var(--font-${value})`;
194
+ return `font-size: ${cssValue};`;
195
+ },
196
+
197
+ // Font weight
198
+ 'font': () => {
199
+ const cssValue = `var(--fw-${value})`;
200
+ return `font-weight: ${cssValue};`;
201
+ },
202
+
203
+ // Border color
204
+ 'border': () => {
205
+ const cssValue = isArbitrary ? value : `var(--c-${value})`;
206
+ return `border-color: ${cssValue}; border-style: solid;`;
207
+ },
208
+
209
+ // Border width
210
+ 'border-w': () => {
211
+ const cssValue = isArbitrary ? value : `var(--s-${value})`;
212
+ return `border-width: ${cssValue}; border-style: solid;`;
213
+ },
214
+
215
+ // Border radius
216
+ 'rounded': () => {
217
+ const cssValue = `var(--r-${value})`;
218
+ return `border-radius: ${cssValue};`;
219
+ },
220
+
221
+ // Box shadow
222
+ 'shadow': () => {
223
+ const cssValue = `var(--shadow-${value})`;
224
+ return `box-shadow: ${cssValue};`;
225
+ },
226
+
227
+ // Opacity
228
+ 'opacity': () => {
229
+ const cssValue = isArbitrary ? value : value;
230
+ return `opacity: ${cssValue};`;
231
+ }
232
+ };
233
+
234
+ const generator = rules[property];
235
+ return generator ? generator() : '';
236
+ }
237
+
238
+ /**
239
+ * Generate a single CSS rule from a token
240
+ */
241
+ export function generateRule(token, config) {
242
+ const { raw, attrType, breakpoint, state } = token;
243
+
244
+ let cssDeclaration = '';
245
+
246
+ switch (attrType) {
247
+ case 'layout':
248
+ cssDeclaration = generateLayoutRule(token, config);
249
+ break;
250
+ case 'space':
251
+ cssDeclaration = generateSpaceRule(token, config);
252
+ break;
253
+ case 'visual':
254
+ cssDeclaration = generateVisualRule(token, config);
255
+ break;
256
+ }
257
+
258
+ if (!cssDeclaration) return '';
259
+
260
+ // Build selector
261
+ let selector = `[${attrType}~="${raw}"]`;
262
+
263
+ // Add state pseudo-class
264
+ if (state) {
265
+ selector += `:${state}`;
266
+ }
267
+
268
+ return `${selector} { ${cssDeclaration} }\n`;
269
+ }
270
+
271
+ /**
272
+ * Generate complete CSS from tokens
273
+ * @param {Array} tokens - Array of token objects
274
+ * @param {Object} config - Configuration object
275
+ * @returns {string} - Complete CSS string
276
+ */
277
+ export function generateCSS(tokens, config) {
278
+ let css = '';
279
+
280
+ // Add CSS variables
281
+ css += generateCSSVariables(config);
282
+
283
+ // Add base reset styles
284
+ css += `/* SenangStart CSS - Base Reset */
285
+ *, *::before, *::after {
286
+ box-sizing: border-box;
287
+ }
288
+
289
+ /* Layout utilities */
290
+ `;
291
+
292
+ // Group tokens by breakpoint
293
+ const baseTokens = [];
294
+ const breakpointTokens = {
295
+ mob: [],
296
+ tab: [],
297
+ lap: [],
298
+ desk: []
299
+ };
300
+
301
+ for (const token of tokens) {
302
+ if (token.breakpoint) {
303
+ breakpointTokens[token.breakpoint]?.push(token);
304
+ } else {
305
+ baseTokens.push(token);
306
+ }
307
+ }
308
+
309
+ // Generate base rules
310
+ for (const token of baseTokens) {
311
+ css += generateRule(token, config);
312
+ }
313
+
314
+ // Generate responsive rules
315
+ const { screens } = config.theme;
316
+
317
+ for (const [bp, bpTokens] of Object.entries(breakpointTokens)) {
318
+ if (bpTokens.length > 0) {
319
+ css += `\n@media (min-width: ${screens[bp]}) {\n`;
320
+ for (const token of bpTokens) {
321
+ css += ' ' + generateRule(token, config);
322
+ }
323
+ css += '}\n';
324
+ }
325
+ }
326
+
327
+ return css;
328
+ }
329
+
330
+ /**
331
+ * Minify CSS by removing whitespace and comments
332
+ */
333
+ export function minifyCSS(css) {
334
+ return css
335
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
336
+ .replace(/\s+/g, ' ') // Collapse whitespace
337
+ .replace(/\s*{\s*/g, '{') // Remove space around {
338
+ .replace(/\s*}\s*/g, '}') // Remove space around }
339
+ .replace(/\s*;\s*/g, ';') // Remove space around ;
340
+ .replace(/\s*:\s*/g, ':') // Remove space around :
341
+ .trim();
342
+ }
343
+
344
+ export default { generateCSS, generateCSSVariables, generateRule, minifyCSS };