rizzo-css 0.0.3 → 0.0.5

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # rizzo-css
2
2
 
3
- A modern CSS design system with semantic theming, 14 built-in themes, and accessible components (BEM). Framework-agnostic: use with Astro, Svelte, React, Vue, or plain HTML.
3
+ A modern CSS design system with semantic theming, 14 built-in themes, and accessible components (BEM). **The same CSS and component styles** ship for every option: **Vanilla JS**, Astro, and Svelte. Framework-agnostic: use with any stack or plain HTML.
4
4
 
5
5
  ## Install
6
6
 
@@ -12,7 +12,14 @@ pnpm add rizzo-css
12
12
  yarn add rizzo-css
13
13
  ```
14
14
 
15
- **Quick start (no install):** `npx rizzo-css init` scaffolds a project (prompts for framework and optional Astro/Svelte components). `npx rizzo-css add` copies the CSS into the current project. `npx rizzo-css theme` lists themes.
15
+ **Quick start (no install):** `npx rizzo-css init` first choose **add to existing project** or **create new**. Existing: framework (auto-detect), themes, optional components. New: scaffold (Vanilla example, default Astro app, or default Svelte app; CLI colors: Vanilla = yellow, Astro = orange, Svelte = orange-red). All get the **same CSS and component styles**. To use the **official Svelte or Astro scaffold** plus Rizzo CSS, create the app with their CLI first, then run `npx rizzo-css add`:
16
+
17
+ ```bash
18
+ npm create svelte@latest my-app && cd my-app && npx rizzo-css add
19
+ npm create astro@latest my-app && cd my-app && npx rizzo-css add
20
+ ```
21
+
22
+ `add` auto-detects Svelte/Astro and copies CSS to the right place (`static/css` or `public/css`). `npx rizzo-css theme` lists themes.
16
23
 
17
24
  ## Use
18
25
 
@@ -34,7 +41,7 @@ import 'rizzo-css';
34
41
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/rizzo-css@latest" />
35
42
  ```
36
43
 
37
- Use the same class names and HTML structure as in the [component docs](https://rizzo-css.vercel.app/docs/components). For Astro or Svelte, reference components and examples are in the [documentation](https://rizzo-css.vercel.app/docs/getting-started).
44
+ Use the same class names and HTML structure as in the [component docs](https://rizzo-css.vercel.app/docs/components). **Vanilla JS**, Astro, and Svelte all use the same CSS and BEM markup; Astro/Svelte add framework component files for convenience.
38
45
 
39
46
  ## Themes
40
47
 
package/bin/rizzo-css.js CHANGED
@@ -36,6 +36,19 @@ const ASTRO_COMPONENTS = [
36
36
  'Modal', 'Toast', 'Table',
37
37
  ];
38
38
 
39
+ // ANSI colors for CLI (framework logo colors)
40
+ const C = {
41
+ reset: '\u001b[0m',
42
+ dim: '\u001b[90m',
43
+ cyan: '\u001b[36m',
44
+ vanilla: '\u001b[38;5;226m', // Vanilla JS yellow
45
+ astro: '\u001b[38;5;208m', // Astro orange #ff5d01
46
+ svelte: '\u001b[38;5;202m', // Svelte orange #ff3e00
47
+ };
48
+
49
+ const CIRCLE_EMPTY = '\u25CB '; // ○
50
+ const CIRCLE_FILLED = '\u25CF '; // ●
51
+
39
52
  // Resolve path to this package (works when run via npx or from repo)
40
53
  function getPackageRoot() {
41
54
  return dirname(require.resolve('../package.json'));
@@ -55,6 +68,189 @@ function question(prompt) {
55
68
  });
56
69
  }
57
70
 
71
+ /** Format label with optional ANSI color (item.color). */
72
+ function formatLabel(item) {
73
+ const text = item.label || item.value;
74
+ return item.color ? item.color + text + C.reset : text;
75
+ }
76
+
77
+ /** Single-select menu with circles. options: array of { value, label, color? }. Returns selected value. */
78
+ function selectMenu(options, title) {
79
+ const items = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));
80
+ const isTty = process.stdin.isTTY && process.stdout.isTTY;
81
+
82
+ if (!isTty) {
83
+ console.log('\n' + (title || 'Choose one') + ':');
84
+ items.forEach((item, i) => console.log(' ' + (i + 1) + '. ' + (item.label || item.value)));
85
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
86
+ return new Promise((resolve) => {
87
+ rl.question('\nEnter number [1]: ', (answer) => {
88
+ rl.close();
89
+ const n = parseInt((answer || '1').trim(), 10);
90
+ const idx = n >= 1 && n <= items.length ? n - 1 : 0;
91
+ resolve(items[idx].value);
92
+ });
93
+ });
94
+ }
95
+
96
+ return new Promise((resolve) => {
97
+ let index = 0;
98
+ const lineCount = (title ? 1 : 0) + items.length + 1;
99
+
100
+ const render = (first) => {
101
+ const lines = (title ? [title] : []).concat(
102
+ items.map((item, i) => {
103
+ const circle = i === index ? CIRCLE_FILLED : CIRCLE_EMPTY;
104
+ const prefix = i === index ? C.cyan + '>' + C.reset + ' ' : ' ';
105
+ return prefix + circle + formatLabel(item);
106
+ })
107
+ );
108
+ if (!first) {
109
+ process.stdout.write('\u001b[' + lineCount + 'A');
110
+ }
111
+ process.stdout.write('\u001b[?25l');
112
+ process.stdout.write(lines.join('\n') + '\n\n');
113
+ process.stdout.write('\u001b[?25h');
114
+ };
115
+
116
+ process.stdin.setRawMode(true);
117
+ process.stdin.resume();
118
+ process.stdin.setEncoding('utf8');
119
+
120
+ render(true);
121
+
122
+ let buf = '';
123
+ const onData = (ch) => {
124
+ if (ch === '\u0003') {
125
+ process.stdin.setRawMode(false);
126
+ process.stdin.removeListener('data', onData);
127
+ process.stdout.write('\n');
128
+ process.exit(130);
129
+ }
130
+ if (ch === '\r' || ch === '\n') {
131
+ process.stdin.setRawMode(false);
132
+ process.stdin.removeListener('data', onData);
133
+ process.stdin.pause();
134
+ process.stdout.write('\n');
135
+ resolve(items[index].value);
136
+ return;
137
+ }
138
+ buf += ch;
139
+ const isUp = buf === '\u001b[A' || buf === '\u001bOA' || (buf.length >= 2 && buf.endsWith('A') && buf.startsWith('\u001b'));
140
+ const isDown = buf === '\u001b[B' || buf === '\u001bOB' || (buf.length >= 2 && buf.endsWith('B') && buf.startsWith('\u001b'));
141
+ if (isUp) {
142
+ buf = '';
143
+ index = index <= 0 ? items.length - 1 : index - 1;
144
+ render(false);
145
+ } else if (isDown) {
146
+ buf = '';
147
+ index = index >= items.length - 1 ? 0 : index + 1;
148
+ render(false);
149
+ } else if (buf.length > 12 || (buf.length === 1 && ch !== '\u001b')) {
150
+ buf = '';
151
+ }
152
+ };
153
+ process.stdin.on('data', onData);
154
+ });
155
+ }
156
+
157
+ /** Multi-select menu: circles ○/●, Space toggles, Enter confirms. Returns array of selected values. */
158
+ function multiSelectMenu(options, title) {
159
+ const items = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));
160
+ const isTty = process.stdin.isTTY && process.stdout.isTTY;
161
+
162
+ if (!isTty) {
163
+ console.log('\n' + (title || 'Choose (space to toggle, enter when done') + ':');
164
+ items.forEach((item, i) => console.log(' ' + (i + 1) + '. ' + (item.label || item.value)));
165
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
166
+ return new Promise((resolve) => {
167
+ rl.question('\nEnter numbers to include (e.g. 1 3 5 or all): ', (answer) => {
168
+ rl.close();
169
+ const s = (answer || '').trim().toLowerCase();
170
+ if (s === 'all' || s === 'a') {
171
+ resolve(items.map((i) => i.value));
172
+ return;
173
+ }
174
+ const parts = s.split(/[\s,]+/).filter(Boolean);
175
+ const indices = new Set();
176
+ for (const p of parts) {
177
+ const n = parseInt(p, 10);
178
+ if (n >= 1 && n <= items.length) indices.add(n - 1);
179
+ }
180
+ resolve(Array.from(indices).sort((a, b) => a - b).map((i) => items[i].value));
181
+ });
182
+ });
183
+ }
184
+
185
+ return new Promise((resolve) => {
186
+ let index = 0;
187
+ const selected = new Set();
188
+ const lineCount = (title ? 1 : 0) + items.length + 1;
189
+
190
+ const render = (first) => {
191
+ const lines = (title ? [title] : []).concat(
192
+ items.map((item, i) => {
193
+ const circle = selected.has(i) ? CIRCLE_FILLED : CIRCLE_EMPTY;
194
+ const prefix = i === index ? C.cyan + '>' + C.reset + ' ' : ' ';
195
+ return prefix + circle + formatLabel(item);
196
+ })
197
+ );
198
+ if (!first) {
199
+ process.stdout.write('\u001b[' + lineCount + 'A');
200
+ }
201
+ process.stdout.write('\u001b[?25l');
202
+ process.stdout.write(lines.join('\n') + '\n\n');
203
+ process.stdout.write('\u001b[?25h');
204
+ };
205
+
206
+ process.stdin.setRawMode(true);
207
+ process.stdin.resume();
208
+ process.stdin.setEncoding('utf8');
209
+
210
+ render(true);
211
+
212
+ let buf = '';
213
+ const onData = (ch) => {
214
+ if (ch === '\u0003') {
215
+ process.stdin.setRawMode(false);
216
+ process.stdin.removeListener('data', onData);
217
+ process.stdout.write('\n');
218
+ process.exit(130);
219
+ }
220
+ if (ch === '\r' || ch === '\n') {
221
+ process.stdin.setRawMode(false);
222
+ process.stdin.removeListener('data', onData);
223
+ process.stdin.pause();
224
+ process.stdout.write('\n');
225
+ resolve(Array.from(selected).sort((a, b) => a - b).map((i) => items[i].value));
226
+ return;
227
+ }
228
+ if (ch === ' ') {
229
+ buf = '';
230
+ if (selected.has(index)) selected.delete(index);
231
+ else selected.add(index);
232
+ render(false);
233
+ return;
234
+ }
235
+ buf += ch;
236
+ const isUp = buf === '\u001b[A' || buf === '\u001bOA' || (buf.length >= 2 && buf.endsWith('A') && buf.startsWith('\u001b'));
237
+ const isDown = buf === '\u001b[B' || buf === '\u001bOB' || (buf.length >= 2 && buf.endsWith('B') && buf.startsWith('\u001b'));
238
+ if (isUp) {
239
+ buf = '';
240
+ index = index <= 0 ? items.length - 1 : index - 1;
241
+ render(false);
242
+ } else if (isDown) {
243
+ buf = '';
244
+ index = index >= items.length - 1 ? 0 : index + 1;
245
+ render(false);
246
+ } else if (buf.length > 12 || (buf.length === 1 && ch !== '\u001b')) {
247
+ buf = '';
248
+ }
249
+ };
250
+ process.stdin.on('data', onData);
251
+ });
252
+ }
253
+
58
254
  function printHelp() {
59
255
  console.log(`
60
256
  rizzo-css CLI — Add Rizzo CSS to your project
@@ -63,15 +259,23 @@ Usage:
63
259
  npx rizzo-css <command> [options]
64
260
 
65
261
  Commands:
66
- init Scaffold a new project (prompts: name, framework, theme; Astro/Svelte: optional component picker)
67
- add Copy Rizzo CSS into the current project
68
- theme List available themes
262
+ init Add Rizzo to existing project or scaffold new one (first menu: existing vs new)
263
+ add Copy Rizzo CSS into the current project (auto-detects Svelte/Astro)
264
+ theme List all available themes (use in init or set data-theme on <html>)
69
265
  help Show this help
70
266
 
267
+ Use framework CLI first, then add Rizzo CSS:
268
+ npm create svelte@latest my-app
269
+ cd my-app && npx rizzo-css add
270
+
271
+ npm create astro@latest my-app
272
+ cd my-app && npx rizzo-css add
273
+
71
274
  Examples:
72
275
  npx rizzo-css init
73
276
  npx rizzo-css add
74
277
  npx rizzo-css add --path public/css
278
+ npx rizzo-css add --framework svelte
75
279
  npx rizzo-css theme
76
280
 
77
281
  Docs: https://rizzo-css.vercel.app
@@ -79,16 +283,44 @@ Docs: https://rizzo-css.vercel.app
79
283
  }
80
284
 
81
285
  function cmdTheme() {
82
- console.log('\nAvailable themes (set data-theme on <html>):\n');
83
- THEMES.forEach((t) => console.log(' ' + t));
84
- console.log('\nExample: <html lang="en" data-theme="github-dark-classic">\n');
286
+ process.stdout.write('\nAvailable themes (set data-theme on <html>):\n\n');
287
+ THEMES.forEach((t) => process.stdout.write(' ' + t + '\n'));
288
+ process.stdout.write('\nExample: <html lang="en" data-theme="github-dark-classic">\n\n');
289
+ }
290
+
291
+ /** Detect framework from cwd: "svelte" | "astro" | null. */
292
+ function detectFramework(cwd) {
293
+ if (existsSync(join(cwd, 'svelte.config.js')) || existsSync(join(cwd, 'svelte.config.ts'))) return 'svelte';
294
+ if (existsSync(join(cwd, 'astro.config.mjs')) || existsSync(join(cwd, 'astro.config.mts')) || existsSync(join(cwd, 'astro.config.js'))) return 'astro';
295
+ try {
296
+ const pkg = readFileSync(join(cwd, 'package.json'), 'utf8');
297
+ const json = JSON.parse(pkg);
298
+ const deps = { ...json.dependencies, ...(json.devDependencies || {}) };
299
+ if (deps['@sveltejs/kit'] || deps['svelte']) return 'svelte';
300
+ if (deps['astro']) return 'astro';
301
+ } catch (_) { /* ignore */ }
302
+ return null;
303
+ }
304
+
305
+ /** Default CSS directory and link href for a framework (for add command). */
306
+ function getFrameworkCssPaths(framework) {
307
+ if (framework === 'svelte') return { targetDir: 'static/css', linkHref: '/css/rizzo.min.css' };
308
+ if (framework === 'astro') return { targetDir: 'public/css', linkHref: '/css/rizzo.min.css' };
309
+ return { targetDir: 'css', linkHref: 'css/rizzo.min.css' };
85
310
  }
86
311
 
87
312
  function cmdAdd(argv) {
88
313
  const pathIdx = argv.indexOf('--path');
89
314
  const customPath = pathIdx !== -1 && argv[pathIdx + 1] ? argv[pathIdx + 1] : null;
90
- const targetDir = customPath || 'css';
91
- const targetFile = join(process.cwd(), targetDir, 'rizzo.min.css');
315
+ const frameworkIdx = argv.indexOf('--framework');
316
+ const explicitFramework = frameworkIdx !== -1 && argv[frameworkIdx + 1] ? argv[frameworkIdx + 1].toLowerCase() : null;
317
+ const cwd = process.cwd();
318
+ const framework = explicitFramework && FRAMEWORKS.includes(explicitFramework)
319
+ ? explicitFramework
320
+ : (explicitFramework === null ? detectFramework(cwd) : null);
321
+ const paths = getFrameworkCssPaths(framework);
322
+ const targetDir = customPath || paths.targetDir;
323
+ const targetFile = join(cwd, targetDir, 'rizzo.min.css');
92
324
  const cssSource = getCssPath();
93
325
 
94
326
  if (!existsSync(cssSource)) {
@@ -96,26 +328,27 @@ function cmdAdd(argv) {
96
328
  process.exit(1);
97
329
  }
98
330
 
99
- mkdirSync(join(process.cwd(), targetDir), { recursive: true });
331
+ mkdirSync(join(cwd, targetDir), { recursive: true });
100
332
  copyFileSync(cssSource, targetFile);
101
- const linkPath = targetDir + '/rizzo.min.css';
333
+ const linkHref = customPath ? customPath + '/rizzo.min.css' : paths.linkHref;
102
334
  console.log('\n✓ Rizzo CSS copied to ' + targetFile);
103
- console.log('\nAdd to your HTML or layout:\n');
104
- console.log(' <link rel="stylesheet" href="' + linkPath + '" />');
105
- console.log('\nSet a theme on <html>: data-theme="github-dark-classic" (see: npx rizzo-css theme)\n');
106
- }
107
-
108
- function parseComponentSelection(input, componentList, maxNum) {
109
- const s = (input || '').trim().toLowerCase();
110
- if (s === 'none' || s === 'n') return [];
111
- if (s === 'all' || s === 'a') return componentList.slice();
112
- const parts = s.split(/[\s,]+/).filter(Boolean);
113
- const indices = new Set();
114
- for (const p of parts) {
115
- const n = parseInt(p, 10);
116
- if (n >= 1 && n <= maxNum) indices.add(n - 1);
117
- }
118
- return indices.size === 0 ? [] : Array.from(indices).sort((a, b) => a - b).map((i) => componentList[i]);
335
+ if (framework === 'svelte') {
336
+ console.log('\nDetected Svelte/SvelteKit. Add to your root layout (e.g. src/app.html):\n');
337
+ console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
338
+ console.log('\nSet a theme on <html>: data-theme="github-dark-classic" (see: npx rizzo-css theme)');
339
+ console.log('\nTo add Rizzo Svelte components later: copy from this package\'s scaffold or run npx rizzo-css init and pick Svelte + components in an empty folder, then copy the generated files.\n');
340
+ } else if (framework === 'astro') {
341
+ console.log('\nDetected Astro. Add to your layout (e.g. src/layouts/Layout.astro):\n');
342
+ console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
343
+ console.log('\nSet a theme on <html>: data-theme="github-dark-classic" (see: npx rizzo-css theme)\n');
344
+ } else {
345
+ console.log('\nAdd to your HTML or layout:\n');
346
+ console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
347
+ if (framework === 'vanilla') {
348
+ console.log('\nVanilla JS: same CSS and component classes as Astro/Svelte. Use the same BEM markup from the docs.');
349
+ }
350
+ console.log('\nSet a theme on <html>: data-theme="github-dark-classic" (see: npx rizzo-css theme)\n');
351
+ }
119
352
  }
120
353
 
121
354
  function getScaffoldSvelteDir() {
@@ -126,6 +359,18 @@ function getScaffoldAstroDir() {
126
359
  return join(getPackageRoot(), 'scaffold', 'astro');
127
360
  }
128
361
 
362
+ function getScaffoldVanillaIndex() {
363
+ return join(getPackageRoot(), 'scaffold', 'vanilla', 'index.html');
364
+ }
365
+
366
+ function getScaffoldAstroAppDir() {
367
+ return join(getPackageRoot(), 'scaffold', 'astro-app');
368
+ }
369
+
370
+ function getScaffoldSvelteAppDir() {
371
+ return join(getPackageRoot(), 'scaffold', 'svelte-app');
372
+ }
373
+
129
374
  function copyDirRecursive(src, dest) {
130
375
  mkdirSync(dest, { recursive: true });
131
376
  const entries = readdirSync(src, { withFileTypes: true });
@@ -140,6 +385,31 @@ function copyDirRecursive(src, dest) {
140
385
  }
141
386
  }
142
387
 
388
+ /** Copy directory recursively; in text files (utf-8), replace each key in replacements with its value. */
389
+ function copyDirRecursiveWithReplacements(src, dest, replacements) {
390
+ mkdirSync(dest, { recursive: true });
391
+ const entries = readdirSync(src, { withFileTypes: true });
392
+ const textExtensions = new Set(['.html', '.astro', '.svelte', '.ts', '.js', '.mjs', '.json', '.css', '.md']);
393
+ for (const e of entries) {
394
+ const srcPath = join(src, e.name);
395
+ const destPath = join(dest, e.name);
396
+ if (e.isDirectory()) {
397
+ copyDirRecursiveWithReplacements(srcPath, destPath, replacements);
398
+ } else {
399
+ const ext = srcPath.slice(srcPath.lastIndexOf('.'));
400
+ if (textExtensions.has(ext)) {
401
+ let content = readFileSync(srcPath, 'utf8');
402
+ for (const [key, value] of Object.entries(replacements)) {
403
+ content = content.split(key).join(value);
404
+ }
405
+ writeFileSync(destPath, content, 'utf8');
406
+ } else {
407
+ copyFileSync(srcPath, destPath);
408
+ }
409
+ }
410
+ }
411
+ }
412
+
143
413
  function copySvelteComponents(projectDir, selectedNames) {
144
414
  const scaffoldDir = getScaffoldSvelteDir();
145
415
  if (!existsSync(scaffoldDir)) {
@@ -204,30 +474,149 @@ function copyAstroComponents(projectDir, selectedNames) {
204
474
  }
205
475
  }
206
476
 
477
+ /** Add Rizzo CSS (and optional components) to an existing project in cwd. */
478
+ async function runAddToExisting() {
479
+ const cwd = process.cwd();
480
+ const detected = detectFramework(cwd);
481
+ const frameworkOptions = [
482
+ { value: 'vanilla', label: 'Vanilla JS (HTML + CSS)', color: C.vanilla },
483
+ { value: 'astro', label: 'Astro', color: C.astro },
484
+ { value: 'svelte', label: 'Svelte', color: C.svelte },
485
+ ];
486
+ let frameworkPrompt = '? Framework';
487
+ if (detected) {
488
+ frameworkPrompt += ' (detected: ' + detected + ' — pick to confirm or choose another)';
489
+ }
490
+ const framework = await selectMenu(frameworkOptions, frameworkPrompt);
491
+
492
+ const selectedThemes = await multiSelectMenu(
493
+ THEMES.map((t) => ({ value: t, label: t })),
494
+ '? Themes (Space to toggle, Enter to confirm) — we\'ll suggest the first as default data-theme'
495
+ );
496
+ const themeList = selectedThemes.length > 0 ? selectedThemes : [THEMES[0]];
497
+ const suggestedTheme = THEMES.includes(themeList[0]) ? themeList[0] : THEMES[0];
498
+
499
+ let selectedComponents = [];
500
+ const componentList = framework === 'svelte' ? SVELTE_COMPONENTS : framework === 'astro' ? ASTRO_COMPONENTS : [];
501
+ if (componentList.length > 0) {
502
+ const includeLabel = framework === 'svelte' ? 'Svelte' : 'Astro';
503
+ const includeChoice = await selectMenu(
504
+ [
505
+ { value: 'none', label: 'None' },
506
+ { value: 'pick', label: 'Yes, pick ' + includeLabel + ' components' },
507
+ ],
508
+ '? Include ' + includeLabel + ' components?'
509
+ );
510
+ if (includeChoice === 'pick') {
511
+ selectedComponents = await multiSelectMenu(
512
+ componentList.map((c) => ({ value: c, label: c })),
513
+ '? Components (Space to toggle, Enter to confirm)'
514
+ );
515
+ }
516
+ }
517
+
518
+ const cssSource = getCssPath();
519
+ if (!existsSync(cssSource)) {
520
+ console.error('Error: Rizzo CSS build not found. Run from repo root: pnpm build:css');
521
+ process.exit(1);
522
+ }
523
+
524
+ const paths = getFrameworkCssPaths(framework);
525
+ const targetDir = join(cwd, paths.targetDir);
526
+ const cssTarget = join(targetDir, 'rizzo.min.css');
527
+ mkdirSync(targetDir, { recursive: true });
528
+ copyFileSync(cssSource, cssTarget);
529
+
530
+ if (framework === 'svelte' && selectedComponents.length > 0) {
531
+ copySvelteComponents(cwd, selectedComponents);
532
+ } else if (framework === 'astro' && selectedComponents.length > 0) {
533
+ copyAstroComponents(cwd, selectedComponents);
534
+ }
535
+
536
+ const linkHref = paths.linkHref;
537
+ console.log('\n✓ Rizzo CSS added to your existing project');
538
+ console.log(' - ' + cssTarget);
539
+ if (framework === 'svelte') {
540
+ console.log('\nAdd to your root layout (e.g. src/app.html):');
541
+ console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
542
+ console.log('\nSet a theme on <html>: data-theme="' + suggestedTheme + '" (see: npx rizzo-css theme)');
543
+ if (selectedComponents.length > 0) {
544
+ console.log(' Components are in src/lib/rizzo — import from \'$lib/rizzo\'.');
545
+ }
546
+ } else if (framework === 'astro') {
547
+ console.log('\nAdd to your layout (e.g. src/layouts/Layout.astro):');
548
+ console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
549
+ console.log('\nSet a theme on <html>: data-theme="' + suggestedTheme + '" (see: npx rizzo-css theme)');
550
+ if (selectedComponents.length > 0) {
551
+ console.log(' Components are in src/components/rizzo — import from there.');
552
+ }
553
+ } else {
554
+ console.log('\nAdd to your HTML or layout:');
555
+ console.log(' <link rel="stylesheet" href="' + linkHref + '" />');
556
+ console.log('\nSet a theme on <html>: data-theme="' + suggestedTheme + '" (see: npx rizzo-css theme)');
557
+ }
558
+ console.log('\nDocs: https://rizzo-css.vercel.app\n');
559
+ }
560
+
207
561
  async function cmdInit() {
208
- const name = await question('Project name (folder name, or leave blank for current directory): ');
209
- let framework = await question('Framework: vanilla, astro, svelte (default: vanilla): ') || 'vanilla';
210
- framework = framework.toLowerCase();
211
- if (!FRAMEWORKS.includes(framework)) framework = 'vanilla';
562
+ const initMode = await selectMenu(
563
+ [
564
+ { value: 'existing', label: 'Add to existing project (current directory)' },
565
+ { value: 'new', label: 'Create new project (scaffold)' },
566
+ ],
567
+ '? Are you using an existing project or creating a new one?'
568
+ );
569
+
570
+ if (initMode === 'existing') {
571
+ await runAddToExisting();
572
+ return;
573
+ }
212
574
 
213
- const theme = await question('Theme (default: github-dark-classic, or run "npx rizzo-css theme" for list): ') || 'github-dark-classic';
575
+ const projectChoice = await selectMenu(
576
+ [
577
+ { value: 'cwd', label: 'Current directory' },
578
+ { value: 'name', label: 'Enter project name (new folder)' },
579
+ ],
580
+ '? Project location'
581
+ );
582
+ const name = projectChoice === 'name' ? await question('Project name (folder name): ') : '';
583
+
584
+ const framework = await selectMenu(
585
+ [
586
+ { value: 'vanilla', label: 'Vanilla JS (HTML + CSS + same styles & components)', color: C.vanilla },
587
+ { value: 'astro', label: 'Astro', color: C.astro },
588
+ { value: 'svelte', label: 'Svelte', color: C.svelte },
589
+ ],
590
+ '? Framework (arrows, Enter to select) — all get the same CSS and component styles'
591
+ );
592
+
593
+ const selectedThemes = await multiSelectMenu(
594
+ THEMES.map((t) => ({ value: t, label: t })),
595
+ '? Themes (Space to toggle, Enter to confirm)'
596
+ );
597
+ const themeList = selectedThemes.length > 0 ? selectedThemes : [THEMES[0]];
598
+ const theme = THEMES.includes(themeList[0]) ? themeList[0] : THEMES[0];
214
599
 
215
600
  let selectedComponents = [];
216
601
  const componentList = framework === 'svelte' ? SVELTE_COMPONENTS : framework === 'astro' ? ASTRO_COMPONENTS : [];
217
602
  if (componentList.length > 0) {
218
- const label = framework === 'svelte' ? 'Svelte' : 'Astro';
219
- const include = await question('Include ' + label + ' components? (y/n, default: n): ') || 'n';
220
- if (include.toLowerCase() === 'y' || include.toLowerCase() === 'yes') {
221
- console.log('\nComponents (enter numbers to include, e.g. 1 3 5, or "all" / "none"):');
222
- componentList.forEach((c, i) => console.log(' ' + (i + 1) + '. ' + c));
223
- const choice = await question('\nSelection (e.g. 1 2 3 or all): ');
224
- selectedComponents = parseComponentSelection(choice, componentList, componentList.length);
603
+ const includeLabel = framework === 'svelte' ? 'Svelte' : 'Astro';
604
+ const includeChoice = await selectMenu(
605
+ [
606
+ { value: 'none', label: 'None' },
607
+ { value: 'pick', label: 'Yes, pick ' + includeLabel + ' components' },
608
+ ],
609
+ '? Include ' + includeLabel + ' components?'
610
+ );
611
+ if (includeChoice === 'pick') {
612
+ selectedComponents = await multiSelectMenu(
613
+ componentList.map((c) => ({ value: c, label: c })),
614
+ '? Components (Space to toggle, Enter to confirm)'
615
+ );
225
616
  }
226
617
  }
227
618
 
228
619
  const projectDir = name ? join(process.cwd(), name) : process.cwd();
229
- const cssDir = framework === 'astro' ? join(projectDir, 'public', 'css') : join(projectDir, 'css');
230
- const cssTarget = join(cssDir, 'rizzo.min.css');
231
620
  const cssSource = getCssPath();
232
621
 
233
622
  if (!existsSync(cssSource)) {
@@ -235,12 +624,63 @@ async function cmdInit() {
235
624
  process.exit(1);
236
625
  }
237
626
 
238
- mkdirSync(cssDir, { recursive: true });
239
- copyFileSync(cssSource, cssTarget);
240
-
241
- const linkHref = framework === 'astro' ? '/css/rizzo.min.css' : 'css/rizzo.min.css';
242
- const indexHtml = `<!DOCTYPE html>
243
- <html lang="en" data-theme="${theme}">
627
+ const themeComment = themeList.length > 0
628
+ ? '\n <!-- Selected themes: ' + themeList.join(', ') + ' -->'
629
+ : '';
630
+ const projectNamePkg = name
631
+ ? name.replace(/\s+/g, '-').toLowerCase()
632
+ : (framework === 'astro' ? 'my-astro-app' : framework === 'svelte' ? 'my-svelte-app' : 'my-app');
633
+ const replacements = {
634
+ '{{DATA_THEME}}': theme,
635
+ '{{THEME_LIST_COMMENT}}': themeComment,
636
+ '{{TITLE}}': name || 'App',
637
+ '{{PROJECT_NAME}}': projectNamePkg,
638
+ };
639
+
640
+ const astroAppDir = getScaffoldAstroAppDir();
641
+ const svelteAppDir = getScaffoldSvelteAppDir();
642
+
643
+ let cssTarget;
644
+ let indexPath;
645
+
646
+ if (framework === 'astro' && existsSync(astroAppDir)) {
647
+ mkdirSync(projectDir, { recursive: true });
648
+ copyDirRecursiveWithReplacements(astroAppDir, projectDir, replacements);
649
+ mkdirSync(join(projectDir, 'public', 'css'), { recursive: true });
650
+ cssTarget = join(projectDir, 'public', 'css', 'rizzo.min.css');
651
+ copyFileSync(cssSource, cssTarget);
652
+ if (selectedComponents.length > 0) {
653
+ copyAstroComponents(projectDir, selectedComponents);
654
+ }
655
+ } else if (framework === 'svelte' && existsSync(svelteAppDir)) {
656
+ mkdirSync(projectDir, { recursive: true });
657
+ copyDirRecursiveWithReplacements(svelteAppDir, projectDir, replacements);
658
+ mkdirSync(join(projectDir, 'static', 'css'), { recursive: true });
659
+ cssTarget = join(projectDir, 'static', 'css', 'rizzo.min.css');
660
+ copyFileSync(cssSource, cssTarget);
661
+ if (selectedComponents.length > 0) {
662
+ copySvelteComponents(projectDir, selectedComponents);
663
+ }
664
+ } else {
665
+ const cssDir = framework === 'astro' ? join(projectDir, 'public', 'css') : join(projectDir, 'css');
666
+ cssTarget = join(cssDir, 'rizzo.min.css');
667
+ const linkHref = framework === 'astro' ? '/css/rizzo.min.css' : 'css/rizzo.min.css';
668
+ mkdirSync(cssDir, { recursive: true });
669
+ copyFileSync(cssSource, cssTarget);
670
+
671
+ const vanillaScaffoldPath = getScaffoldVanillaIndex();
672
+ indexPath = join(projectDir, 'index.html');
673
+ if (framework === 'vanilla' && existsSync(vanillaScaffoldPath)) {
674
+ let indexHtml = readFileSync(vanillaScaffoldPath, 'utf8');
675
+ indexHtml = indexHtml
676
+ .replace(/\{\{DATA_THEME\}\}/g, theme)
677
+ .replace(/\{\{THEME_LIST_COMMENT\}\}/g, themeComment)
678
+ .replace(/\{\{TITLE\}\}/g, name || 'App')
679
+ .replace(/\{\{LINK_HREF\}\}/g, linkHref);
680
+ writeFileSync(indexPath, indexHtml, 'utf8');
681
+ } else {
682
+ const indexHtml = `<!DOCTYPE html>
683
+ <html lang="en" data-theme="${theme}">${themeComment}
244
684
  <head>
245
685
  <meta charset="UTF-8" />
246
686
  <meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -253,20 +693,32 @@ async function cmdInit() {
253
693
  </body>
254
694
  </html>
255
695
  `;
256
-
257
- const indexPath = join(projectDir, 'index.html');
258
- writeFileSync(indexPath, indexHtml, 'utf8');
259
-
260
- if (framework === 'svelte' && selectedComponents.length > 0) {
261
- copySvelteComponents(projectDir, selectedComponents);
262
- } else if (framework === 'astro' && selectedComponents.length > 0) {
263
- copyAstroComponents(projectDir, selectedComponents);
696
+ writeFileSync(indexPath, indexHtml, 'utf8');
697
+ }
698
+ if (framework === 'svelte' && selectedComponents.length > 0) {
699
+ copySvelteComponents(projectDir, selectedComponents);
700
+ } else if (framework === 'astro' && selectedComponents.length > 0) {
701
+ copyAstroComponents(projectDir, selectedComponents);
702
+ }
264
703
  }
265
704
 
266
705
  console.log('\n✓ Project ready at ' + projectDir);
267
706
  console.log(' - ' + cssTarget);
268
- console.log(' - ' + indexPath);
269
- console.log('\nRun a local server (e.g. npx serve .) or open index.html. Docs: https://rizzo-css.vercel.app\n');
707
+ if (indexPath) console.log(' - ' + indexPath);
708
+ if (framework === 'vanilla') {
709
+ console.log(' - Vanilla JS: same CSS and component styles; index includes theme switcher and sample components.');
710
+ }
711
+ if (framework === 'astro' && existsSync(astroAppDir)) {
712
+ console.log(' - Default Astro project with Rizzo CSS. Run: pnpm install && pnpm dev');
713
+ }
714
+ if (framework === 'svelte' && existsSync(svelteAppDir)) {
715
+ console.log(' - Default SvelteKit project with Rizzo CSS. Run: pnpm install && pnpm dev');
716
+ }
717
+ if ((framework === 'svelte' || framework === 'astro') && !existsSync(framework === 'astro' ? astroAppDir : svelteAppDir)) {
718
+ const fw = framework === 'svelte' ? 'Svelte' : 'Astro';
719
+ console.log('\nTip: To use the official ' + fw + ' scaffold, create a project with their CLI (e.g. npm create ' + framework + '@latest my-app), then run npx rizzo-css add in that folder.');
720
+ }
721
+ console.log('\nDocs: https://rizzo-css.vercel.app\n');
270
722
  }
271
723
 
272
724
  function main() {
@@ -1,6 +1,4 @@
1
- :root{--primary-color:oklch(45.2% 0.198 250.1deg);--secondary-color:oklch(25.1% 0 0deg);--font-family-sans:system-ui,-apple-system,blinkmacsystemfont,"Segoe UI",roboto,"Helvetica Neue",arial,sans-serif;--font-family-serif:georgia,"Times New Roman",times,serif;--font-family-mono:"SF Mono",monaco,"Cascadia Code","Roboto Mono",consolas,"Courier New",monospace;--font-family:var(--font-family-sans);--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-size-scale:1;--font-size-xs:calc(0.75rem*var(--font-size-scale));--font-size-sm:calc(0.875rem*var(--font-size-scale));--font-size-base:calc(1rem*var(--font-size-scale));--font-size-lg:calc(1.125rem*var(--font-size-scale));--font-size-xl:calc(1.25rem*var(--font-size-scale));--font-size-2xl:calc(1.5rem*var(--font-size-scale));--font-size-3xl:calc(1.875rem*var(--font-size-scale));--font-size-4xl:calc(2.25rem*var(--font-size-scale));--font-size-5xl:calc(3rem*var(--font-size-scale));--font-size-6xl:calc(3.75rem*var(--font-size-scale));--line-height-tight:1.25;--line-height-snug:1.375;--line-height-normal:1.5;--line-height-relaxed:1.625;--line-height-loose:2;--letter-spacing-tighter:-0.05em;--letter-spacing-tight:-0.025em;--letter-spacing-normal:0;--letter-spacing-wide:0.025em;--letter-spacing-wider:0.05em;--letter-spacing-widest:0.1em;--spacing-0:0;--spacing-1:0.25rem;--spacing-2:0.5rem;--spacing-3:0.75rem;--spacing-4:1rem;--spacing-5:1.25rem;--spacing-6:1.5rem;--spacing-8:2rem;--spacing-10:2.5rem;--spacing-12:3rem;--spacing-16:4rem;--spacing-20:5rem;--spacing-24:6rem;--spacing-0-125:0.125rem;--spacing-0-375:0.375rem;--spacing-0-625:0.625rem;--spacing-0-875:0.875rem;--spacing-28:7rem;--spacing-32:8rem;--spacing-36:9rem;--spacing-40:10rem;--spacing-48:12rem;--spacing-56:14rem;--spacing-64:16rem;--spacing-72:18rem;--spacing-80:20rem;--spacing-96:24rem;--spacing-50:12.5rem;--spacing-100:25rem;--spacing-150:37.5rem;--spacing-14:3.5rem;--spacing-15:3.75rem;--spacing-75:18.75rem;--spacing-175:43.75rem;--spacing-70:17.5rem;--spacing-105:26.25rem;--spacing-2500:2500rem;--radius-none:0;--radius-sm:0.125rem;--radius:0.25rem;--radius-md:0.375rem;--radius-lg:0.5rem;--radius-xl:0.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-full:9999px;--radius-circle:50%;--z-base:0;--z-1:1;--z-2:2;--z-3:3;--z-10:10;--z-dropdown:10;--z-dropdown-submenu:11;--z-sticky:50;--z-fixed:100;--z-search-overlay:98;--z-search-panel:99;--z-modal-backdrop:1999;--z-modal:2000;--z-tooltip:3000;--z-toast:4000;--z-settings:10000;--z-navbar:5000;--z-navbar-mobile-menu-open:5100;--z-skip-link:10001;--z-navbar-mobile-menu:101;--z-navbar-search-overlay:98;--z-navbar-search-panel:99;--z-navbar-mobile-search-overlay:101;--z-navbar-mobile-search-panel:102;--transition-fast:150ms;--transition-base:200ms;--transition-slow:300ms;--transition-slower:350ms;--transition-slowest:400ms;--transition-ease-out:300ms ease-out;--transition-ease-in:300ms ease-in;--theme-transition-duration:0.2s;--blur-sm:4px;--blur:8px;--blur-md:12px;--blur-lg:16px;--outline-width:2px;--outline-offset:2px;--container-sm:640px;--container-md:768px;--container-lg:1024px;--container-xl:1280px;--container-2xl:1536px;--container-default:1200px;--vh-70:70vh;--vh-80:80vh;--vh-90:90vh;--touch-target-min:3rem;--max-height-dropdown:600px;--max-height-modal:32rem;--max-height-navbar-submenu:2000px;--ease-in-out-cubic:cubic-bezier(0.4,0,0.2,1);--scale-80:0.8;--scale-95:0.95;--scale-100:1;--scale-110:1.1;--border-width:1px;--border-width-2:2px;--border-width-3:3px;--border-width-4:4px;--border-width-arrow:6px;--border-width-accent:3px;--opacity-0:0;--opacity-50:0.5;--opacity-60:0.6;--opacity-70:0.7;--opacity-80:0.8;--opacity-90:0.9;--opacity-100:1;--background:oklch(100% 0 0deg);--background-alt:oklch(96.9% 0 0deg);--text:oklch(25.1% 0 0deg);--text-dim:oklch(50.2% 0 0deg);--icon:var(--text);--icon-dim:var(--text-dim);--border:oklch(90.2% 0 0deg);--accent:oklch(45.2% 0.198 250.1deg);--accent-hover:oklch(40.2% 0.198 250.1deg);--accent-text:oklch(100% 0 0deg);--accent-text-on-hover:var(--accent-text);--success:oklch(60.2% 0.182 145.1deg);--success-hover:oklch(70% 0.16 145deg);--success-text:oklch(20% 0 0deg);--success-text-on-solid:var(--success-text);--warning:oklch(80.2% 0.152 90.1deg);--warning-hover:oklch(88% 0.12 90deg);--warning-text:oklch(100% 0 0deg);--warning-text-on-solid:oklch(22% 0.02 90deg);--text-on-solid-hover:oklch(22% 0.02 0deg);--error:oklch(55.2% 0.218 25.1deg);--error-hover:oklch(65% 0.18 25deg);--error-text:oklch(100% 0 0deg);--error-text-on-solid:var(--error-text);--info:oklch(60.2% 0.118 210.1deg);--info-hover:oklch(70% 0.1 210deg);--info-text:oklch(20% 0 0deg);--info-text-on-solid:var(--info-text);--selection:oklch(70% 0.15 250deg);--shadow-color:oklch(0% 0 0deg);--shadow-sm:0 1px 2px 0 oklch(from var(--shadow-color) l c h/5%);--shadow:0 1px 3px 0 oklch(from var(--shadow-color) l c h/10%),0 1px 2px -1px oklch(from var(--shadow-color) l c h/10%);--shadow-md:0 4px 6px -1px oklch(from var(--shadow-color) l c h/10%),0 2px 4px -2px oklch(from var(--shadow-color) l c h/6%);--shadow-lg:0 10px 15px -3px oklch(from var(--shadow-color) l c h/10%),0 4px 6px -4px oklch(from var(--shadow-color) l c h/5%);--shadow-xl:0 20px 25px -5px oklch(from var(--shadow-color) l c h/10%),0 8px 10px -6px oklch(from var(--shadow-color) l c h/4%);--overlay:oklch(from var(--shadow-color) l c h/50%);--shadow-inset-sm:inset 0 var(--spacing-0-125) var(--spacing-0-125) oklch(from var(--shadow-color) l c h/10%);--shadow-inset:inset 0 var(--spacing-0-125) var(--spacing-0-125) oklch(from var(--shadow-color) l c h/10%)}*,
2
- *::after,
3
- *::before{box-sizing:border-box}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;margin:0;padding:0} blockquote,body, dd, dl,
1
+ :root{--primary-color:oklch(45.2% 0.198 250.1deg);--secondary-color:oklch(25.1% 0 0deg);--font-family-sans:system-ui,-apple-system,blinkmacsystemfont,"Segoe UI",roboto,"Helvetica Neue",arial,sans-serif;--font-family-serif:georgia,"Times New Roman",times,serif;--font-family-mono:"SF Mono",monaco,"Cascadia Code","Roboto Mono",consolas,"Courier New",monospace;--font-family:var(--font-family-sans);--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-size-scale:1;--font-size-xs:calc(0.75rem*var(--font-size-scale));--font-size-sm:calc(0.875rem*var(--font-size-scale));--font-size-base:calc(1rem*var(--font-size-scale));--font-size-lg:calc(1.125rem*var(--font-size-scale));--font-size-xl:calc(1.25rem*var(--font-size-scale));--font-size-2xl:calc(1.5rem*var(--font-size-scale));--font-size-3xl:calc(1.875rem*var(--font-size-scale));--font-size-4xl:calc(2.25rem*var(--font-size-scale));--font-size-5xl:calc(3rem*var(--font-size-scale));--font-size-6xl:calc(3.75rem*var(--font-size-scale));--line-height-tight:1.25;--line-height-snug:1.375;--line-height-normal:1.5;--line-height-relaxed:1.625;--line-height-loose:2;--letter-spacing-tighter:-0.05em;--letter-spacing-tight:-0.025em;--letter-spacing-normal:0;--letter-spacing-wide:0.025em;--letter-spacing-wider:0.05em;--letter-spacing-widest:0.1em;--spacing-0:0;--spacing-1:0.25rem;--spacing-2:0.5rem;--spacing-3:0.75rem;--spacing-4:1rem;--spacing-5:1.25rem;--spacing-6:1.5rem;--spacing-8:2rem;--spacing-10:2.5rem;--spacing-12:3rem;--spacing-16:4rem;--spacing-20:5rem;--spacing-24:6rem;--spacing-0-125:0.125rem;--spacing-0-375:0.375rem;--spacing-0-625:0.625rem;--spacing-0-875:0.875rem;--spacing-28:7rem;--spacing-32:8rem;--spacing-36:9rem;--spacing-40:10rem;--spacing-48:12rem;--spacing-56:14rem;--spacing-64:16rem;--spacing-72:18rem;--spacing-80:20rem;--spacing-96:24rem;--spacing-50:12.5rem;--spacing-100:25rem;--spacing-150:37.5rem;--spacing-14:3.5rem;--spacing-15:3.75rem;--spacing-75:18.75rem;--spacing-175:43.75rem;--spacing-70:17.5rem;--spacing-105:26.25rem;--spacing-2500:2500rem;--radius-none:0;--radius-sm:0.125rem;--radius:0.25rem;--radius-md:0.375rem;--radius-lg:0.5rem;--radius-xl:0.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-full:9999px;--radius-circle:50%;--z-base:0;--z-1:1;--z-2:2;--z-3:3;--z-10:10;--z-dropdown:10;--z-dropdown-submenu:11;--z-sticky:50;--z-fixed:100;--z-search-overlay:98;--z-search-panel:99;--z-modal-backdrop:1999;--z-modal:2000;--z-tooltip:3000;--z-toast:4000;--z-settings:10000;--z-navbar:5000;--z-navbar-mobile-menu-open:5100;--z-skip-link:10001;--z-navbar-mobile-menu:101;--z-navbar-search-overlay:98;--z-navbar-search-panel:99;--z-navbar-mobile-search-overlay:101;--z-navbar-mobile-search-panel:102;--transition-fast:150ms;--transition-base:200ms;--transition-slow:300ms;--transition-slower:350ms;--transition-slowest:400ms;--transition-ease-out:300ms ease-out;--transition-ease-in:300ms ease-in;--theme-transition-duration:0.2s;--blur-sm:4px;--blur:8px;--blur-md:12px;--blur-lg:16px;--outline-width:2px;--outline-offset:2px;--container-sm:640px;--container-md:768px;--container-lg:1024px;--container-xl:1280px;--container-2xl:1536px;--container-default:1200px;--vh-70:70vh;--vh-80:80vh;--vh-90:90vh;--touch-target-min:3rem;--max-height-dropdown:600px;--max-height-modal:32rem;--max-height-navbar-submenu:2000px;--ease-in-out-cubic:cubic-bezier(0.4,0,0.2,1);--scale-80:0.8;--scale-95:0.95;--scale-100:1;--scale-110:1.1;--border-width:1px;--border-width-2:2px;--border-width-3:3px;--border-width-4:4px;--border-width-arrow:6px;--border-width-accent:3px;--opacity-0:0;--opacity-50:0.5;--opacity-60:0.6;--opacity-70:0.7;--opacity-80:0.8;--opacity-90:0.9;--opacity-100:1;--background:oklch(100% 0 0deg);--background-alt:oklch(96.9% 0 0deg);--text:oklch(25.1% 0 0deg);--text-dim:oklch(50.2% 0 0deg);--icon:var(--text);--icon-dim:var(--text-dim);--border:oklch(90.2% 0 0deg);--accent:oklch(45.2% 0.198 250.1deg);--accent-hover:oklch(40.2% 0.198 250.1deg);--accent-text:oklch(100% 0 0deg);--accent-text-on-hover:var(--accent-text);--success:oklch(60.2% 0.182 145.1deg);--success-hover:oklch(70% 0.16 145deg);--success-text:oklch(20% 0 0deg);--success-text-on-solid:var(--success-text);--warning:oklch(80.2% 0.152 90.1deg);--warning-hover:oklch(88% 0.12 90deg);--warning-text:oklch(100% 0 0deg);--warning-text-on-solid:oklch(22% 0.02 90deg);--text-on-solid-hover:oklch(22% 0.02 0deg);--error:oklch(55.2% 0.218 25.1deg);--error-hover:oklch(65% 0.18 25deg);--error-text:oklch(100% 0 0deg);--error-text-on-solid:var(--error-text);--info:oklch(60.2% 0.118 210.1deg);--info-hover:oklch(70% 0.1 210deg);--info-text:oklch(20% 0 0deg);--info-text-on-solid:var(--info-text);--selection:oklch(70% 0.15 250deg);--shadow-color:oklch(0% 0 0deg);--shadow-sm:0 1px 2px 0 oklch(from var(--shadow-color) l c h/5%);--shadow:0 1px 3px 0 oklch(from var(--shadow-color) l c h/10%),0 1px 2px -1px oklch(from var(--shadow-color) l c h/10%);--shadow-md:0 4px 6px -1px oklch(from var(--shadow-color) l c h/10%),0 2px 4px -2px oklch(from var(--shadow-color) l c h/6%);--shadow-lg:0 10px 15px -3px oklch(from var(--shadow-color) l c h/10%),0 4px 6px -4px oklch(from var(--shadow-color) l c h/5%);--shadow-xl:0 20px 25px -5px oklch(from var(--shadow-color) l c h/10%),0 8px 10px -6px oklch(from var(--shadow-color) l c h/4%);--overlay:oklch(from var(--shadow-color) l c h/50%);--shadow-inset-sm:inset 0 var(--spacing-0-125) var(--spacing-0-125) oklch(from var(--shadow-color) l c h/10%);--shadow-inset:inset 0 var(--spacing-0-125) var(--spacing-0-125) oklch(from var(--shadow-color) l c h/10%)}*, *::after, *::before{box-sizing:border-box;margin:0;padding:0}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;margin:0;padding:0} blockquote,body, dd, dl,
4
2
  figure, h1, h2, h3, h4, h5, h6, p{margin-block-end:0}
5
3
  ol[role="list"],ul[role="list"]{list-style:none}body{line-height:1.5;margin:0;min-height:100vh;padding:0}
6
4
  button,h1, h2, h3, h4, h5, h6, input, label{line-height:1.1}h1, h2, h3, h4, h5, h6{text-wrap:balance}a:not([class]){-webkit-text-decoration-skip:ink;color:currentcolor;text-decoration-skip-ink:auto}img,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rizzo-css",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "scripts": {
5
5
  "prepublishOnly": "cd ../.. && pnpm build:css && node scripts/copy-scaffold.js"
6
6
  },
@@ -0,0 +1,4 @@
1
+ // https://astro.build/config
2
+ import { defineConfig } from 'astro/config';
3
+
4
+ export default defineConfig({});
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "scripts": {
6
+ "dev": "astro dev",
7
+ "build": "astro build",
8
+ "preview": "astro preview"
9
+ },
10
+ "dependencies": {
11
+ "astro": "^5.0.0"
12
+ }
13
+ }
File without changes
@@ -0,0 +1,19 @@
1
+ ---
2
+ interface Props {
3
+ title?: string;
4
+ }
5
+
6
+ const { title = '{{TITLE}}' } = Astro.props;
7
+ ---
8
+ <!doctype html>
9
+ <html lang="en" data-theme="{{DATA_THEME}}">{{THEME_LIST_COMMENT}}
10
+ <head>
11
+ <meta charset="UTF-8" />
12
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
13
+ <link rel="stylesheet" href="/css/rizzo.min.css" />
14
+ <title>{title}</title>
15
+ </head>
16
+ <body>
17
+ <slot />
18
+ </body>
19
+ </html>
@@ -0,0 +1,13 @@
1
+ ---
2
+ import Layout from '../layouts/Layout.astro';
3
+ ---
4
+ <Layout>
5
+ <main style="padding: var(--spacing-6); max-width: 65ch;">
6
+ <h1>Hello, Rizzo CSS</h1>
7
+ <p>Your Astro project is set up with Rizzo CSS. Same styles and components as Vanilla JS and Svelte.</p>
8
+ <p>
9
+ <a href="https://rizzo-css.vercel.app/docs/components">Component docs</a>
10
+ — Set theme via <code>data-theme</code> on <code>&lt;html&gt;</code> (e.g. <code>github-dark-classic</code>). Run <code>npx rizzo-css theme</code> to list themes.
11
+ </p>
12
+ </main>
13
+ </Layout>
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "astro/tsconfigs/strict",
3
+ "include": [".astro/types.d.ts", "**/*"],
4
+ "exclude": ["dist"]
5
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "scripts": {
6
+ "dev": "vite dev",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "devDependencies": {
11
+ "@sveltejs/adapter-auto": "^3.0.0",
12
+ "@sveltejs/kit": "^2.0.0",
13
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
14
+ "svelte": "^5.0.0",
15
+ "vite": "^6.0.0"
16
+ }
17
+ }
@@ -0,0 +1,13 @@
1
+ // See https://svelte.dev/docs/kit/types#app.d.ts
2
+ // for information about these interfaces
3
+ declare global {
4
+ namespace App {
5
+ // interface Error {}
6
+ // interface Locals {}
7
+ // interface PageData {}
8
+ // interface PageState {}
9
+ // interface Platform {}
10
+ }
11
+ }
12
+
13
+ export {};
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="{{DATA_THEME}}">{{THEME_LIST_COMMENT}}
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <link rel="stylesheet" href="/css/rizzo.min.css" />
7
+ %sveltekit.head%
8
+ </head>
9
+ <body data-sveltekit-preload-data="hover">
10
+ <div style="display: contents">%sveltekit.body%</div>
11
+ </body>
12
+ </html>
@@ -0,0 +1,5 @@
1
+ <script>
2
+ // Root layout — Rizzo CSS is loaded via app.html
3
+ </script>
4
+
5
+ <slot />
@@ -0,0 +1,12 @@
1
+ <script>
2
+ // Welcome page
3
+ </script>
4
+
5
+ <main style="padding: var(--spacing-6); max-width: 65ch;">
6
+ <h1>Hello, Rizzo CSS</h1>
7
+ <p>Your SvelteKit project is set up with Rizzo CSS. Same styles and components as Vanilla JS and Astro.</p>
8
+ <p>
9
+ <a href="https://rizzo-css.vercel.app/docs/components">Component docs</a>
10
+ — Set theme via <code>data-theme</code> on <code>&lt;html&gt;</code> (e.g. <code>github-dark-classic</code>). Run <code>npx rizzo-css theme</code> to list themes.
11
+ </p>
12
+ </main>
File without changes
@@ -0,0 +1,11 @@
1
+ // @ts-check
2
+ import adapter from '@sveltejs/adapter-auto';
3
+
4
+ /** @type {import('@sveltejs/kit').Config} */
5
+ const config = {
6
+ kit: {
7
+ adapter: adapter()
8
+ }
9
+ };
10
+
11
+ export default config;
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "./.svelte-kit/tsconfig.json",
3
+ "compilerOptions": {
4
+ "allowJs": true,
5
+ "checkJs": true,
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "resolveJsonModule": true,
9
+ "skipLibCheck": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "bundler"
14
+ }
15
+ }
@@ -0,0 +1,6 @@
1
+ import { sveltekit } from '@sveltejs/kit/vite';
2
+ import { defineConfig } from 'vite';
3
+
4
+ export default defineConfig({
5
+ plugins: [sveltekit()]
6
+ });
@@ -0,0 +1,77 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="{{DATA_THEME}}">{{THEME_LIST_COMMENT}}
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{TITLE}}</title>
7
+ <link rel="stylesheet" href="{{LINK_HREF}}" />
8
+ </head>
9
+ <body>
10
+ <header style="margin-bottom: var(--spacing-6);">
11
+ <h1>Hello, Rizzo CSS</h1>
12
+ <p>Vanilla JS — same styles and components as Astro/Svelte. Use the same BEM classes; see <a href="https://rizzo-css.vercel.app/docs/components">docs</a>.</p>
13
+ <div style="display: flex; align-items: center; gap: var(--spacing-3); flex-wrap: wrap; margin-top: var(--spacing-3);">
14
+ <label for="theme-select">Theme:</label>
15
+ <select id="theme-select" style="width: auto; min-width: 12rem;">
16
+ <option value="github-dark-classic">github-dark-classic</option>
17
+ <option value="github-light">github-light</option>
18
+ <option value="shades-of-purple">shades-of-purple</option>
19
+ <option value="sandstorm-classic">sandstorm-classic</option>
20
+ <option value="rocky-blood-orange">rocky-blood-orange</option>
21
+ <option value="minimal-dark-neon-yellow">minimal-dark-neon-yellow</option>
22
+ <option value="hack-the-box">hack-the-box</option>
23
+ <option value="pink-cat-boo">pink-cat-boo</option>
24
+ <option value="red-velvet-cupcake">red-velvet-cupcake</option>
25
+ <option value="orangy-one-light">orangy-one-light</option>
26
+ <option value="sunflower">sunflower</option>
27
+ <option value="green-breeze-light">green-breeze-light</option>
28
+ <option value="cute-pink">cute-pink</option>
29
+ <option value="semi-light-purple">semi-light-purple</option>
30
+ </select>
31
+ </div>
32
+ </header>
33
+
34
+ <section style="margin-bottom: var(--spacing-6);">
35
+ <h2>Buttons</h2>
36
+ <div style="display: flex; flex-wrap: wrap; gap: var(--spacing-3);">
37
+ <button type="button" class="btn">Default</button>
38
+ <button type="button" class="btn btn-primary">Primary</button>
39
+ <button type="button" class="btn btn-success">Success</button>
40
+ <button type="button" class="btn btn-warning">Warning</button>
41
+ <button type="button" class="btn btn-error">Error</button>
42
+ <button type="button" class="btn btn-outline">Outline</button>
43
+ </div>
44
+ </section>
45
+
46
+ <section style="margin-bottom: var(--spacing-6);">
47
+ <h2>Card &amp; Badge</h2>
48
+ <div class="card" style="max-width: 20rem;">
49
+ <div class="card__body">
50
+ <h3 class="card__title">Card title</h3>
51
+ <p>Same component styles as Astro and Svelte. Use class names from the <a href="https://rizzo-css.vercel.app/docs/components">component docs</a>.</p>
52
+ <span class="badge badge--primary">Badge</span>
53
+ </div>
54
+ </div>
55
+ </section>
56
+
57
+ <p><small>All themes and component styles are included. Run <code>npx rizzo-css theme</code> to list themes.</small></p>
58
+
59
+ <script>
60
+ (function () {
61
+ var select = document.getElementById('theme-select');
62
+ var html = document.documentElement;
63
+ var key = 'rizzo-theme';
64
+ function applyTheme(value) {
65
+ html.setAttribute('data-theme', value);
66
+ try { localStorage.setItem(key, value); } catch (e) {}
67
+ select.value = value;
68
+ }
69
+ var stored = null;
70
+ try { stored = localStorage.getItem(key); } catch (e) {}
71
+ var initial = stored || html.getAttribute('data-theme') || 'github-dark-classic';
72
+ applyTheme(initial);
73
+ select.addEventListener('change', function () { applyTheme(select.value); });
74
+ })();
75
+ </script>
76
+ </body>
77
+ </html>