@birdapi/velinstyle 0.6.1 → 0.8.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.
Files changed (64) hide show
  1. package/README.de.md +337 -316
  2. package/README.md +33 -12
  3. package/cli/blueprint.js +8 -0
  4. package/cli/blueprints/bottom-nav-mobile.html +17 -0
  5. package/cli/blueprints/cookie-consent.html +9 -0
  6. package/cli/blueprints/empty-state.html +5 -0
  7. package/cli/blueprints/filter-bar.html +15 -0
  8. package/cli/blueprints/notification-center.html +13 -0
  9. package/cli/blueprints/onboarding.html +23 -0
  10. package/cli/blueprints/pricing-table.html +20 -0
  11. package/cli/blueprints/settings-panel.html +20 -0
  12. package/cli/index.js +119 -3
  13. package/cli/layout-audit.js +325 -0
  14. package/cli/scaffold-recipes.json +70 -0
  15. package/cli/scaffold.js +155 -0
  16. package/cli/scanner.js +114 -0
  17. package/components/focus-manager.js +106 -80
  18. package/components/index.js +20 -1
  19. package/components/sanitize.js +29 -3
  20. package/components/shadow-a11y-styles.js +18 -0
  21. package/components/velin-accordion.js +112 -98
  22. package/components/velin-announcer.js +35 -0
  23. package/components/velin-bottom-nav.js +89 -0
  24. package/components/velin-carousel.js +40 -5
  25. package/components/velin-collapse.js +95 -65
  26. package/components/velin-combobox.js +149 -0
  27. package/components/velin-command.js +127 -0
  28. package/components/velin-counter.js +152 -0
  29. package/components/velin-drawer.js +6 -3
  30. package/components/velin-dropdown.js +33 -2
  31. package/components/velin-flip.js +220 -0
  32. package/components/velin-icon.js +43 -9
  33. package/components/velin-live-dot.js +85 -0
  34. package/components/velin-menubar.js +83 -0
  35. package/components/velin-modal.js +3 -1
  36. package/components/velin-popover.js +61 -21
  37. package/components/velin-rating.js +91 -0
  38. package/components/velin-reveal.js +80 -0
  39. package/components/velin-segmented-control.js +108 -0
  40. package/components/velin-sheet.js +107 -0
  41. package/components/velin-sparkline.js +207 -0
  42. package/components/velin-theme-toggle.js +277 -60
  43. package/components/velin-tooltip-wc.js +26 -2
  44. package/dist/velinstyle-components.iife.js +1849 -120
  45. package/dist/velinstyle-components.js +1871 -120
  46. package/dist/velinstyle-components.min.js +434 -91
  47. package/dist/velinstyle.css +640 -44
  48. package/dist/velinstyle.min.css +1 -1
  49. package/package.json +7 -3
  50. package/src/a11y/focus-not-obscured.css +21 -0
  51. package/src/a11y/forced-colors.css +86 -38
  52. package/src/a11y/high-contrast-aaa.css +37 -0
  53. package/src/a11y/preferences.css +106 -85
  54. package/src/a11y/security.css +119 -104
  55. package/src/a11y/target-size.css +32 -0
  56. package/src/base/reset.css +12 -1
  57. package/src/base/root.css +4 -4
  58. package/src/components/nav.css +152 -151
  59. package/src/tokens/motion.css +7 -0
  60. package/src/utilities/animation.css +97 -1
  61. package/src/utilities/chart-animation.css +101 -0
  62. package/src/utilities/filter-effects.css +103 -0
  63. package/src/utilities/safe-area.css +39 -0
  64. package/src/velinstyle.css +9 -1
package/README.md CHANGED
@@ -8,15 +8,14 @@
8
8
  <br>
9
9
 
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-2563eb?style=flat-square)](LICENSE)
11
- [![Release](https://img.shields.io/badge/Release-v0.6.1-2563eb?style=flat-square)](CHANGELOG.md)
11
+ [![Release](https://img.shields.io/badge/Release-v0.8.0-2563eb?style=flat-square)](CHANGELOG.md)
12
12
  [![WCAG AA](https://img.shields.io/badge/WCAG-AA-16a34a?style=flat-square)](docs/a11y.html)
13
13
  [![CSS min](https://img.shields.io/badge/CSS_min-~46KB-15803d?style=flat-square)]()
14
14
  [![JS min](https://img.shields.io/badge/JS_min-~66KB-15803d?style=flat-square)]()
15
- [![npm](https://img.shields.io/badge/npm-publish_pending-64748b?style=flat-square)](https://www.npmjs.com/package/@birdapi/velinstyle)
15
+ [![npm version](https://img.shields.io/npm/v/@birdapi/velinstyle?style=flat-square)](https://www.npmjs.com/package/@birdapi/velinstyle)
16
+ [![GitHub Pages](https://img.shields.io/badge/docs-GitHub_Pages-2563eb?style=flat-square)](https://skylitedesign.github.io/velinstyle/)
16
17
 
17
- <!-- After npm publish: https://img.shields.io/npm/v/velinstyle?style=flat-square -->
18
-
19
- [Website](https://velinstyle.info) · [Docs](docs/index.html) · [Samples](samples/) · [Playground](tools/playground/index.html) · [Theme Builder](tools/theme-builder/index.html) · [Issues](https://github.com/SkyliteDesign/velinstyle/issues)
18
+ [Documentation](https://skylitedesign.github.io/velinstyle/) · [npm](https://www.npmjs.com/package/@birdapi/velinstyle) · [Samples](samples/) · [Playground](tools/playground/index.html) · [Theme Builder](tools/theme-builder/index.html) · [Issues](https://github.com/SkyliteDesign/velinstyle/issues)
20
19
 
21
20
  **English** · **[Deutsch](README.de.md)**
22
21
 
@@ -74,8 +73,8 @@ Teams ship UI under pressure. **Accessibility** and **consistent theming** are o
74
73
  <tr><td align="center">🎨</td><td><strong>OKLCH + 13 theme presets</strong></td><td>Perceptually uniform colors; dark mode via token swap</td></tr>
75
74
  <tr><td align="center">📦</td><td><strong>~46 KB CSS + ~66 KB JS (min)</strong></td><td>Lean defaults vs. heavier all-in-one stacks</td></tr>
76
75
  <tr><td align="center">📐</td><td><strong>Container Queries + utilities</strong></td><td>Components adapt to <em>their</em> container, not only the viewport</td></tr>
77
- <tr><td align="center">🧩</td><td><strong>25 CSS modules · 21 Web Components</strong></td><td>Modals, tabs, drawers, toasts, icons—documented APIs in <a href="docs/css-components.html">CSS</a> &amp; <a href="docs/components.html">WC docs</a></td></tr>
78
- <tr><td align="center">🛠️</td><td><strong>Optional CLI</strong></td><td><code>init</code>, <code>build</code>, <code>icons</code>, <code>scan</code>, <code>prefix</code>, <code>blueprint</code>, <code>tokens build</code></td></tr>
76
+ <tr><td align="center">🧩</td><td><strong>25 CSS modules · 29 Web Components</strong></td><td>Modals, tabs, drawers, toasts, icons—documented APIs in <a href="docs/css-components.html">CSS</a> &amp; <a href="docs/components.html">WC docs</a></td></tr>
77
+ <tr><td align="center">🛠️</td><td><strong>Optional CLI</strong></td><td><code>init</code>, <code>build</code>, <code>icons</code>, <code>scan</code>, <code>prefix</code>, <code>blueprint</code>, <code>scaffold</code>, <code>layout</code>, <code>tokens build</code></td></tr>
79
78
  <tr><td align="center">🌍</td><td><strong>RTL-ready</strong></td><td>Logical properties and layout-minded defaults · <a href="samples/rtl.html">RTL sample</a></td></tr>
80
79
  </tbody>
81
80
  </table>
@@ -114,16 +113,16 @@ npm install @birdapi/velinstyle
114
113
  ### <span>②</span> Link CSS + components (ES modules)
115
114
 
116
115
  ```html
117
- <link rel="stylesheet" href="node_modules/velinstyle/dist/velinstyle.min.css">
118
- <script type="module" src="node_modules/velinstyle/dist/velinstyle-components.min.js"></script>
116
+ <link rel="stylesheet" href="node_modules/@birdapi/velinstyle/dist/velinstyle.min.css">
117
+ <script type="module" src="node_modules/@birdapi/velinstyle/dist/velinstyle-components.min.js"></script>
119
118
  ```
120
119
 
121
120
  <details>
122
121
  <summary><strong>CDN alternative</strong> (skip npm install)</summary>
123
122
 
124
123
  ```html
125
- <link rel="stylesheet" href="https://unpkg.com/velinstyle@latest/dist/velinstyle.min.css">
126
- <script type="module" src="https://unpkg.com/velinstyle@latest/dist/velinstyle-components.min.js"></script>
124
+ <link rel="stylesheet" href="https://unpkg.com/@birdapi/velinstyle@latest/dist/velinstyle.min.css">
125
+ <script type="module" src="https://unpkg.com/@birdapi/velinstyle@latest/dist/velinstyle-components.min.js"></script>
127
126
  ```
128
127
 
129
128
  </details>
@@ -226,7 +225,29 @@ All commands: `npx velinstyle <command>` · `npx velinstyle --help`
226
225
  </details>
227
226
 
228
227
  <details>
229
- <summary><strong>blueprint</strong> — 14 HTML snippets</summary>
228
+ <summary><strong>scaffold</strong> — prompt HTML (0.8.0)</summary>
229
+
230
+ - **`npx velinstyle scaffold list-intents`**
231
+ - **`npx velinstyle scaffold "Navbar with search" -o nav.html`**
232
+ - **`npx velinstyle scaffold "…" --json`** — for agents/CI
233
+
234
+ See [docs/guides/prompt-scaffolding.html](docs/guides/prompt-scaffolding.html).
235
+
236
+ </details>
237
+
238
+ <details>
239
+ <summary><strong>layout</strong> — responsive audit (0.8.0)</summary>
240
+
241
+ - **`npx velinstyle layout audit [path]`**
242
+ - **`npx velinstyle layout suggest [path]`**
243
+ - **`npx velinstyle layout fix [path] --write`**
244
+
245
+ See [docs/guides/responsive-layout.html](docs/guides/responsive-layout.html).
246
+
247
+ </details>
248
+
249
+ <details>
250
+ <summary><strong>blueprint</strong> — 22 HTML snippets</summary>
230
251
 
231
252
  - **`npx velinstyle blueprint list`**
232
253
  - **`npx velinstyle blueprint &lt;name&gt; -o snippet.html`**
package/cli/blueprint.js CHANGED
@@ -18,6 +18,14 @@ const BLUEPRINTS = {
18
18
  'search-field': 'search-field.html',
19
19
  'sidebar-layout': 'sidebar-layout.html',
20
20
  'table-responsive': 'table-responsive.html',
21
+ 'bottom-nav-mobile': 'bottom-nav-mobile.html',
22
+ 'empty-state': 'empty-state.html',
23
+ 'cookie-consent': 'cookie-consent.html',
24
+ 'filter-bar': 'filter-bar.html',
25
+ 'notification-center': 'notification-center.html',
26
+ 'settings-panel': 'settings-panel.html',
27
+ onboarding: 'onboarding.html',
28
+ 'pricing-table': 'pricing-table.html',
21
29
  };
22
30
 
23
31
  export function listBlueprints() {
@@ -0,0 +1,17 @@
1
+ <nav class="velin-pb-safe" aria-label="Primary">
2
+ <velin-bottom-nav current="home">
3
+ <a href="#home" data-nav="home" current>Home</a>
4
+ <a href="#search" data-nav="search">Search</a>
5
+ <a href="#profile" data-nav="profile">Profile</a>
6
+ </velin-bottom-nav>
7
+ </nav>
8
+ <script type="module">
9
+ import '../../dist/velinstyle-components.min.js';
10
+ const nav = document.querySelector('velin-bottom-nav');
11
+ nav?.querySelectorAll('a').forEach((link) => {
12
+ link.addEventListener('click', (e) => {
13
+ e.preventDefault();
14
+ nav.setAttribute('current', link.dataset.nav || '');
15
+ });
16
+ });
17
+ </script>
@@ -0,0 +1,9 @@
1
+ <aside class="velin-p-4 velin-border velin-rounded-lg velin-pb-safe" role="region" aria-label="Cookie consent">
2
+ <div class="velin-user-content">
3
+ <p class="velin-mbe-3">We use essential cookies only. No third-party trackers in this demo.</p>
4
+ </div>
5
+ <div class="velin-flex velin-gap-3">
6
+ <button type="button" class="velin-btn velin-btn--primary">Accept</button>
7
+ <button type="button" class="velin-btn velin-btn--outline">Decline</button>
8
+ </div>
9
+ </aside>
@@ -0,0 +1,5 @@
1
+ <section class="velin-p-8 velin-text-center" aria-labelledby="empty-title">
2
+ <h2 id="empty-title" class="velin-text-xl velin-font-bold velin-mbe-2">No items yet</h2>
3
+ <p class="velin-text-muted velin-mbe-4">Create your first entry to see it listed here.</p>
4
+ <button type="button" class="velin-btn velin-btn--primary">Create item</button>
5
+ </section>
@@ -0,0 +1,15 @@
1
+ <form class="velin-flex velin-flex-wrap velin-gap-3 velin-p-4 velin-border-b" role="search" aria-label="Filter list">
2
+ <label class="velin-flex velin-flex-col velin-gap-1">
3
+ <span class="velin-text-sm">Query</span>
4
+ <input type="search" class="velin-input" name="q" placeholder="Search…" />
5
+ </label>
6
+ <label class="velin-flex velin-flex-col velin-gap-1">
7
+ <span class="velin-text-sm">Status</span>
8
+ <select class="velin-select" name="status">
9
+ <option value="">All</option>
10
+ <option value="open">Open</option>
11
+ <option value="done">Done</option>
12
+ </select>
13
+ </label>
14
+ <button type="submit" class="velin-btn velin-btn--primary velin-self-end">Apply</button>
15
+ </form>
@@ -0,0 +1,13 @@
1
+ <section class="velin-p-4" aria-labelledby="notif-title">
2
+ <h2 id="notif-title" class="velin-text-lg velin-font-bold velin-mbe-4">Notifications</h2>
3
+ <ul class="velin-flex velin-flex-col velin-gap-3" role="list">
4
+ <li class="velin-p-3 velin-border velin-rounded-lg">
5
+ <p class="velin-font-medium">Backup complete</p>
6
+ <p class="velin-text-sm velin-text-muted">2 minutes ago</p>
7
+ </li>
8
+ <li class="velin-p-3 velin-border velin-rounded-lg">
9
+ <p class="velin-font-medium">New comment</p>
10
+ <p class="velin-text-sm velin-text-muted">1 hour ago</p>
11
+ </li>
12
+ </ul>
13
+ </section>
@@ -0,0 +1,23 @@
1
+ <velin-stepper-wc id="onboard" labels="Welcome,Profile,Done" active="0">
2
+ <section class="velin-p-6">
3
+ <h2 class="velin-text-xl velin-mbe-2">Welcome</h2>
4
+ <p class="velin-mbe-4">Set up your workspace in a few steps.</p>
5
+ <button type="button" class="velin-btn velin-btn--primary" data-next>Continue</button>
6
+ </section>
7
+ <section class="velin-p-6">
8
+ <h2 class="velin-text-xl velin-mbe-2">Profile</h2>
9
+ <input type="text" class="velin-input velin-mbe-4" placeholder="Your name" aria-label="Name" />
10
+ <button type="button" class="velin-btn velin-btn--primary" data-next>Continue</button>
11
+ </section>
12
+ <section class="velin-p-6">
13
+ <h2 class="velin-text-xl velin-mbe-2">Done</h2>
14
+ <p>You are ready to go.</p>
15
+ </section>
16
+ </velin-stepper-wc>
17
+ <script type="module">
18
+ import '../../dist/velinstyle-components.min.js';
19
+ const stepper = document.getElementById('onboard');
20
+ document.querySelectorAll('[data-next]').forEach((btn) => {
21
+ btn.addEventListener('click', () => stepper?.next());
22
+ });
23
+ </script>
@@ -0,0 +1,20 @@
1
+ <div class="velin-grid velin-gap-4" style="grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));">
2
+ <article class="velin-p-6 velin-border velin-rounded-lg velin-text-center">
3
+ <h3 class="velin-text-lg velin-font-bold">Starter</h3>
4
+ <p class="velin-text-3xl velin-font-bold velin-mbs-2 velin-mbe-4">$0</p>
5
+ <ul class="velin-text-sm velin-mbe-4" role="list">
6
+ <li>1 project</li>
7
+ <li>Community support</li>
8
+ </ul>
9
+ <button type="button" class="velin-btn velin-btn--outline velin-w-full">Choose</button>
10
+ </article>
11
+ <article class="velin-p-6 velin-border velin-rounded-lg velin-text-center velin-border-primary">
12
+ <h3 class="velin-text-lg velin-font-bold">Pro</h3>
13
+ <p class="velin-text-3xl velin-font-bold velin-mbs-2 velin-mbe-4">$12</p>
14
+ <ul class="velin-text-sm velin-mbe-4" role="list">
15
+ <li>Unlimited projects</li>
16
+ <li>Priority support</li>
17
+ </ul>
18
+ <button type="button" class="velin-btn velin-btn--primary velin-w-full">Choose</button>
19
+ </article>
20
+ </div>
@@ -0,0 +1,20 @@
1
+ <velin-sheet id="settings-sheet" title="Settings">
2
+ <form class="velin-flex velin-flex-col velin-gap-4 velin-p-4">
3
+ <label class="velin-flex velin-flex-col velin-gap-1">
4
+ <span>Display name</span>
5
+ <input type="text" class="velin-input" name="name" autocomplete="name" />
6
+ </label>
7
+ <label class="velin-flex velin-items-center velin-gap-2">
8
+ <input type="checkbox" class="velin-checkbox" name="notify" />
9
+ <span>Email notifications</span>
10
+ </label>
11
+ <button type="submit" class="velin-btn velin-btn--primary">Save</button>
12
+ </form>
13
+ </velin-sheet>
14
+ <button type="button" class="velin-btn velin-btn--outline" id="open-settings">Open settings</button>
15
+ <script type="module">
16
+ import '../../dist/velinstyle-components.min.js';
17
+ document.getElementById('open-settings')?.addEventListener('click', () => {
18
+ document.getElementById('settings-sheet')?.open?.();
19
+ });
20
+ </script>
package/cli/index.js CHANGED
@@ -11,7 +11,7 @@ const PKG_ROOT = join(__dirname, '..');
11
11
  const args = process.argv.slice(2);
12
12
  const command = args[0];
13
13
 
14
- const LAYERS = ['tokens', 'reset', 'base', 'a11y', 'layout', 'components', 'utilities', 'helpers'];
14
+ const LAYERS = ['tokens', 'reset', 'base', 'a11y', 'layout', 'components', 'utilities', 'security', 'helpers'];
15
15
 
16
16
  const LAYER_FILES = {
17
17
  tokens: [
@@ -24,8 +24,9 @@ const LAYER_FILES = {
24
24
  a11y: [
25
25
  'a11y/sr-only.css', 'a11y/skip-link.css', 'a11y/reduced-motion.css',
26
26
  'a11y/forced-colors.css', 'a11y/skeleton.css', 'a11y/preferences.css',
27
- 'a11y/security.css',
27
+ 'a11y/focus-not-obscured.css', 'a11y/target-size.css', 'a11y/high-contrast-aaa.css',
28
28
  ],
29
+ security: ['a11y/security.css'],
29
30
  layout: [
30
31
  'layout/breakpoints.css', 'layout/container.css', 'layout/grid.css',
31
32
  'layout/flex.css', 'layout/patterns.css',
@@ -48,6 +49,7 @@ const LAYER_FILES = {
48
49
  'utilities/scroll.css', 'utilities/color-mix.css', 'utilities/scroll-animation.css',
49
50
  'utilities/view-transition.css', 'utilities/scope.css', 'utilities/anchor.css',
50
51
  'utilities/filter.css', 'utilities/container-style.css', 'utilities/state.css',
52
+ 'utilities/safe-area.css',
51
53
  ],
52
54
  helpers: ['helpers/helpers.css'],
53
55
  };
@@ -78,7 +80,7 @@ function hasFlag(flag, alias) {
78
80
 
79
81
  function help() {
80
82
  console.log(`
81
- ${C.bold('VelinStyle CLI')} v0.6.1
83
+ ${C.bold('VelinStyle CLI')} v0.8.0
82
84
 
83
85
  ${C.bold('Usage:')}
84
86
  velinstyle init Create velinstyle.config.js
@@ -89,6 +91,8 @@ function help() {
89
91
  velinstyle scan [path] Security & accessibility scanner
90
92
  velinstyle prefix [path] Add missing velin- prefix (dry-run; use --write)
91
93
  velinstyle blueprint [name] Print HTML blueprint (run: velinstyle blueprint list)
94
+ velinstyle scaffold "<prompt>" Generate layout HTML from a text description
95
+ velinstyle layout <sub> Responsive layout audit and fixes
92
96
  velinstyle tokens build Generate CSS variables from tokens.json
93
97
 
94
98
  ${C.bold('Icons subcommands:')}
@@ -106,6 +110,16 @@ function help() {
106
110
  velinstyle blueprint list Print all blueprint ids
107
111
  velinstyle blueprint <name> [--output, -o <file>]
108
112
 
113
+ ${C.bold('Scaffold (0.8.0):')}
114
+ velinstyle scaffold "<prompt>" Compose blueprints from natural language
115
+ velinstyle scaffold list-intents Show supported intent keywords
116
+ velinstyle scaffold "<prompt>" -o out.html [--json]
117
+
118
+ ${C.bold('Layout (0.8.0):')}
119
+ velinstyle layout audit [path] Report flex/grid/responsive issues
120
+ velinstyle layout suggest [path] Audit with fix suggestions
121
+ velinstyle layout fix [path] Apply safe fixes (--dry-run default, --write)
122
+
109
123
  ${C.bold('Tokens:')}
110
124
  velinstyle tokens build [--input <path>] [--output, -o <file>]
111
125
 
@@ -496,6 +510,104 @@ async function prefixCmd() {
496
510
  }
497
511
  }
498
512
 
513
+ async function scaffoldCmd() {
514
+ const sub = args[1];
515
+ const { scaffoldFromPrompt, listIntents } = await import('./scaffold.js');
516
+ if (sub === 'list-intents') {
517
+ console.log(`\n ${C.bold('Scaffold intents:')}\n`);
518
+ for (const i of listIntents()) {
519
+ console.log(` ${C.cyan(i.id)} — ${i.blueprints.join(' + ')}`);
520
+ console.log(C.dim(` keywords: ${i.keywords.slice(0, 5).join(', ')}…`));
521
+ }
522
+ console.log('');
523
+ return;
524
+ }
525
+ const promptParts = [];
526
+ let i = 1;
527
+ while (i < args.length) {
528
+ const a = args[i];
529
+ if (a.startsWith('-')) break;
530
+ if (a !== 'scaffold') promptParts.push(a);
531
+ i += 1;
532
+ }
533
+ const prompt = promptParts.join(' ').trim() || sub;
534
+ if (!prompt || prompt === 'list-intents') {
535
+ console.log('Usage: velinstyle scaffold "<description>" [-o file.html] [--json]');
536
+ return;
537
+ }
538
+ const r = scaffoldFromPrompt(prompt);
539
+ if (!r.ok) {
540
+ console.log(C.red(r.error));
541
+ process.exit(1);
542
+ }
543
+ const out = getArg('--output', '-o');
544
+ const asJson = hasFlag('--json');
545
+ if (asJson) {
546
+ console.log(JSON.stringify(r, null, 2));
547
+ } else if (out) {
548
+ writeFileSync(resolve(out), r.html, 'utf-8');
549
+ console.log(C.green(`Wrote ${resolve(out)} (intent: ${r.intent}, ${r.confidence})`));
550
+ if (r.responsiveHints?.length) {
551
+ console.log(C.yellow(` ${r.responsiveHints.length} layout hint(s) — run: velinstyle layout suggest ${out}`));
552
+ }
553
+ } else {
554
+ console.log(r.html);
555
+ }
556
+ }
557
+
558
+ async function layoutCmd() {
559
+ const sub = args[1] || 'audit';
560
+ const rawTarget = args[2] && !args[2].startsWith('-') ? args[2] : '.';
561
+ const targetPath = resolve(rawTarget);
562
+ const asJson = hasFlag('--json');
563
+ const write = hasFlag('--write');
564
+ const dryRun = hasFlag('--dry-run') || !write;
565
+
566
+ const { auditPath, suggestFromIssues, formatTextReport, fixPath } = await import('./layout-audit.js');
567
+
568
+ if (sub === 'fix') {
569
+ const result = fixPath(targetPath, { write, dryRun });
570
+ if (!result.ok) {
571
+ console.log(C.red(result.error || 'Fix failed'));
572
+ process.exit(1);
573
+ }
574
+ if (asJson) {
575
+ console.log(JSON.stringify(result, null, 2));
576
+ return;
577
+ }
578
+ if (result.changes.length === 0) {
579
+ console.log(C.green('No safe fixes to apply.'));
580
+ return;
581
+ }
582
+ for (const c of result.changes) {
583
+ console.log(`${dryRun ? '[dry-run] ' : ''}${c.file}`);
584
+ c.changes.forEach((ch) => console.log(C.dim(` - ${ch}`)));
585
+ }
586
+ return;
587
+ }
588
+
589
+ const { issues, files } = auditPath(targetPath);
590
+ const outIssues = sub === 'suggest' ? suggestFromIssues(issues) : issues;
591
+
592
+ if (asJson) {
593
+ console.log(JSON.stringify({ files, issues: outIssues }, null, 2));
594
+ return;
595
+ }
596
+
597
+ console.log(formatTextReport(outIssues, files));
598
+ if (sub === 'suggest' && issues.length) {
599
+ console.log(C.bold('Suggested fixes:'));
600
+ for (const i of outIssues) {
601
+ console.log(` [${i.id}] ${i.fix}`);
602
+ if (i.responsive?.mobile) {
603
+ console.log(C.dim(` mobile: ${i.responsive.mobile}`));
604
+ }
605
+ }
606
+ console.log('');
607
+ }
608
+ process.exit(issues.some((x) => x.severity === 'error') ? 1 : 0);
609
+ }
610
+
499
611
  async function scanCmd() {
500
612
  const targetPath = args[1] && !args[1].startsWith('-') ? resolve(args[1]) : resolve('.');
501
613
  const format = getArg('--format') || 'text';
@@ -507,6 +619,7 @@ async function scanCmd() {
507
619
  fixLang = 'de';
508
620
  }
509
621
  const severity = getArg('--severity') || 'warning';
622
+ const only = getArg('--only');
510
623
 
511
624
  const { scan } = await import('./scanner.js');
512
625
  const exitCode = scan(targetPath, {
@@ -515,6 +628,7 @@ async function scanCmd() {
515
628
  fixDryRun,
516
629
  fixLang: fixLang || undefined,
517
630
  format,
631
+ only,
518
632
  });
519
633
  process.exit(exitCode);
520
634
  }
@@ -531,6 +645,8 @@ switch (command) {
531
645
  case 'tokens': await tokensCmd(); break;
532
646
  case 'scan': scanCmd(); break;
533
647
  case 'prefix': await prefixCmd(); break;
648
+ case 'scaffold': await scaffoldCmd(); break;
649
+ case 'layout': await layoutCmd(); break;
534
650
  case '--help': case '-h': help(); break;
535
651
  default: help(); break;
536
652
  }