@birdapi/velinstyle 0.7.0 → 0.9.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 (336) hide show
  1. package/README.de.md +135 -224
  2. package/README.md +134 -223
  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/cli-manifest.json +97 -0
  13. package/cli/docgen/extract-a11y.js +89 -0
  14. package/cli/docgen/extract-attributes.js +95 -0
  15. package/cli/docgen/extract-cli.js +50 -0
  16. package/cli/docgen/extract-components.js +175 -0
  17. package/cli/docgen/extract-meta.js +32 -0
  18. package/cli/docgen/extract-rules.js +76 -0
  19. package/cli/docgen/extract-tokens.js +79 -0
  20. package/cli/docgen/extract-utilities.js +75 -0
  21. package/cli/docgen/markdown.js +43 -0
  22. package/cli/docs-generate.js +196 -0
  23. package/cli/index.js +236 -5
  24. package/cli/layout-audit.js +325 -0
  25. package/cli/meta.js +97 -0
  26. package/cli/perf-audit.js +174 -0
  27. package/cli/pii-scanner.js +134 -0
  28. package/cli/scaffold-recipes.json +70 -0
  29. package/cli/scaffold.js +155 -0
  30. package/cli/scanner-rules-data.js +62 -0
  31. package/cli/scanner.js +136 -1
  32. package/cli/search-index.js +163 -0
  33. package/cli/tokens-build.js +21 -1
  34. package/cli/tokens-validate.js +69 -0
  35. package/components/a11y-entry.js +42 -0
  36. package/components/a11y-utils.js +71 -0
  37. package/components/focus-manager.js +45 -8
  38. package/components/index.js +40 -11
  39. package/components/runtime/component-loaders.js +43 -0
  40. package/components/runtime/index.js +77 -0
  41. package/components/runtime-entry.js +10 -0
  42. package/components/sanitize.js +135 -28
  43. package/components/shadow-a11y-styles.js +18 -0
  44. package/components/velin-accordion.js +12 -0
  45. package/components/velin-announcer.js +35 -0
  46. package/components/velin-bottom-nav.js +108 -0
  47. package/components/velin-carousel.js +2 -2
  48. package/components/velin-code-block.js +142 -0
  49. package/components/velin-combobox.js +149 -0
  50. package/components/velin-command.js +142 -0
  51. package/components/velin-copy.js +18 -5
  52. package/components/velin-counter.js +155 -0
  53. package/components/velin-dialog.js +9 -1
  54. package/components/velin-email.js +94 -0
  55. package/components/velin-flip.js +220 -0
  56. package/components/velin-icon.js +99 -22
  57. package/components/velin-lightbox.js +8 -3
  58. package/components/velin-live-dot.js +90 -0
  59. package/components/velin-menubar.js +83 -0
  60. package/components/velin-modal.js +6 -3
  61. package/components/velin-popover.js +34 -2
  62. package/components/velin-rating.js +91 -0
  63. package/components/velin-reveal.js +22 -0
  64. package/components/velin-scroll-top.js +1 -1
  65. package/components/velin-search.js +267 -0
  66. package/components/velin-secure-field.js +99 -0
  67. package/components/velin-segmented-control.js +108 -0
  68. package/components/velin-sheet.js +107 -0
  69. package/components/velin-sparkline.js +227 -0
  70. package/components/{velin-stepper-wc.js → velin-stepper.js} +37 -8
  71. package/components/velin-theme-toggle.js +284 -60
  72. package/components/velin-toast.js +4 -0
  73. package/components/{velin-tooltip-wc.js → velin-tooltip.js} +22 -3
  74. package/core/a11y/component-contracts.json +221 -0
  75. package/core/attributes/index.js +6 -0
  76. package/core/attributes/registry.js +268 -0
  77. package/core/highlight/highlight.js +114 -0
  78. package/core/highlight/index.js +29 -0
  79. package/core/highlight/languages/_utils.js +52 -0
  80. package/core/highlight/languages/blade.js +16 -0
  81. package/core/highlight/languages/css.js +26 -0
  82. package/core/highlight/languages/html.js +17 -0
  83. package/core/highlight/languages/js.js +58 -0
  84. package/core/highlight/languages/json.js +14 -0
  85. package/core/highlight/languages/markdown.js +16 -0
  86. package/core/highlight/languages/php.js +39 -0
  87. package/core/highlight/languages/plain.js +7 -0
  88. package/core/highlight/languages/shell.js +33 -0
  89. package/core/highlight/languages/sql.js +20 -0
  90. package/core/highlight/languages/typescript.js +28 -0
  91. package/core/highlight/observe.js +53 -0
  92. package/core/highlight/registry.js +107 -0
  93. package/core/highlight/render.js +35 -0
  94. package/core/highlight/types.js +28 -0
  95. package/core/meta/build.js +293 -0
  96. package/core/meta/index.js +17 -0
  97. package/core/meta/schema.js +49 -0
  98. package/core/motion/effects.js +54 -0
  99. package/core/motion/index.js +76 -0
  100. package/core/motion/scheduler.js +72 -0
  101. package/core/motion/scroll.js +32 -0
  102. package/core/motion/stagger.js +24 -0
  103. package/core/search/docs-url.js +96 -0
  104. package/core/search/engine.js +139 -0
  105. package/core/search/highlight.js +48 -0
  106. package/core/search/index.js +126 -0
  107. package/core/search/providers.js +36 -0
  108. package/core/search/types.js +61 -0
  109. package/core/search/worker-client.js +56 -0
  110. package/core/search/worker.js +20 -0
  111. package/dist/chunks/a11y-utils-IRC7LOPF.js +16 -0
  112. package/dist/chunks/attributes-3BZLI7XF.js +364 -0
  113. package/dist/chunks/attributes-4N5JP4CG.js +364 -0
  114. package/dist/chunks/attributes-5Q2WXGRC.js +374 -0
  115. package/dist/chunks/attributes-AIR7SVLK.js +364 -0
  116. package/dist/chunks/attributes-BGRN5XZO.js +364 -0
  117. package/dist/chunks/attributes-BVSQAYLR.js +364 -0
  118. package/dist/chunks/attributes-CDYFUY7Y.js +364 -0
  119. package/dist/chunks/attributes-JMYJZBPE.js +364 -0
  120. package/dist/chunks/attributes-R5JY3X4O.js +374 -0
  121. package/dist/chunks/attributes-STYCS3O5.js +391 -0
  122. package/dist/chunks/attributes-TNSPQTHX.js +374 -0
  123. package/dist/chunks/attributes-TPANLMLF.js +391 -0
  124. package/dist/chunks/blade-LIQLVKZQ.js +19 -0
  125. package/dist/chunks/browser-MRKYZPEH.js +1149 -0
  126. package/dist/chunks/chunk-2354NEBP.js +109 -0
  127. package/dist/chunks/chunk-2VNA7G7S.js +109 -0
  128. package/dist/chunks/chunk-42VVPW6N.js +132 -0
  129. package/dist/chunks/chunk-7FFAMRFV.js +44 -0
  130. package/dist/chunks/chunk-7MCGPHVC.js +69 -0
  131. package/dist/chunks/chunk-AQLRJSGG.js +109 -0
  132. package/dist/chunks/chunk-EWPSDMQT.js +109 -0
  133. package/dist/chunks/chunk-F7OLHTGX.js +22 -0
  134. package/dist/chunks/chunk-J472ERN2.js +242 -0
  135. package/dist/chunks/chunk-LA7264K4.js +85 -0
  136. package/dist/chunks/chunk-M5CHQVP6.js +61 -0
  137. package/dist/chunks/chunk-MKZP2TBH.js +89 -0
  138. package/dist/chunks/chunk-QTJJ3DPO.js +109 -0
  139. package/dist/chunks/chunk-RDE3QE7Z.js +89 -0
  140. package/dist/chunks/chunk-RSMRR27R.js +109 -0
  141. package/dist/chunks/chunk-RZQVWFHW.js +109 -0
  142. package/dist/chunks/chunk-SRL6EWHP.js +109 -0
  143. package/dist/chunks/chunk-U4XHMZ7D.js +109 -0
  144. package/dist/chunks/chunk-V5YIUA6G.js +109 -0
  145. package/dist/chunks/chunk-XIET2VSZ.js +109 -0
  146. package/dist/chunks/chunk-XYLJ2GCB.js +205 -0
  147. package/dist/chunks/chunk-Z4JYQ2XX.js +51 -0
  148. package/dist/chunks/chunk-ZPSPKUYD.js +109 -0
  149. package/dist/chunks/css-MQ5YU77P.js +28 -0
  150. package/dist/chunks/highlight-GJHHV4KM.js +37 -0
  151. package/dist/chunks/highlight-XTHRKBA3.js +37 -0
  152. package/dist/chunks/html-ZODC4DLQ.js +20 -0
  153. package/dist/chunks/js-MNSNYTJU.js +7 -0
  154. package/dist/chunks/json-VEM2HFH3.js +17 -0
  155. package/dist/chunks/markdown-E7OPGS3S.js +19 -0
  156. package/dist/chunks/php-MLL7T3LT.js +41 -0
  157. package/dist/chunks/plain-LJJDC5TN.js +8 -0
  158. package/dist/chunks/runtime-entry.js +14 -0
  159. package/dist/chunks/shell-QTRK4U4E.js +35 -0
  160. package/dist/chunks/sql-PPWLHGJ2.js +23 -0
  161. package/dist/chunks/typescript-JT3CCYWL.js +30 -0
  162. package/dist/chunks/velin-accordion-FBIPAEXG.js +105 -0
  163. package/dist/chunks/velin-accordion-QAQO7BI7.js +117 -0
  164. package/dist/chunks/velin-announcer-PVTXXCWR.js +33 -0
  165. package/dist/chunks/velin-bottom-nav-2N2TYWOF.js +86 -0
  166. package/dist/chunks/velin-bottom-nav-7GEMPIM6.js +86 -0
  167. package/dist/chunks/velin-bottom-nav-CTU6FFZV.js +101 -0
  168. package/dist/chunks/velin-bottom-nav-N3DFQR66.js +86 -0
  169. package/dist/chunks/velin-carousel-Y5VCOABZ.js +213 -0
  170. package/dist/chunks/velin-carousel-ZTBANFQQ.js +213 -0
  171. package/dist/chunks/velin-code-block-ASUBTAQV.js +119 -0
  172. package/dist/chunks/velin-code-block-CIAURDRJ.js +132 -0
  173. package/dist/chunks/velin-code-block-TSUKPY4C.js +123 -0
  174. package/dist/chunks/velin-code-block-Y6CM726Q.js +119 -0
  175. package/dist/chunks/velin-collapse-5XYBUIID.js +80 -0
  176. package/dist/chunks/velin-combobox-2KDTCQ7N.js +155 -0
  177. package/dist/chunks/velin-combobox-NXS4QB4R.js +155 -0
  178. package/dist/chunks/velin-combobox-UGA56HP3.js +155 -0
  179. package/dist/chunks/velin-command-CKDNO4BW.js +148 -0
  180. package/dist/chunks/velin-command-M7RNHUPR.js +136 -0
  181. package/dist/chunks/velin-command-ONKO2A3J.js +136 -0
  182. package/dist/chunks/velin-command-TMLSRGIU.js +136 -0
  183. package/dist/chunks/velin-copy-G2TZ5YBB.js +81 -0
  184. package/dist/chunks/velin-copy-NYDBOKXT.js +81 -0
  185. package/dist/chunks/velin-copy-T5KDTDXP.js +81 -0
  186. package/dist/chunks/velin-countdown-4ZVDGK4R.js +101 -0
  187. package/dist/chunks/velin-countdown-5XSTN3BH.js +101 -0
  188. package/dist/chunks/velin-countdown-7AIY66CU.js +101 -0
  189. package/dist/chunks/velin-counter-ISCTEAHI.js +118 -0
  190. package/dist/chunks/velin-counter-L5FYJF7A.js +121 -0
  191. package/dist/chunks/velin-dialog-D2GUHSCD.js +144 -0
  192. package/dist/chunks/velin-dialog-ELFOZIXZ.js +152 -0
  193. package/dist/chunks/velin-dialog-GTTZI564.js +147 -0
  194. package/dist/chunks/velin-dialog-IJLFDXAJ.js +144 -0
  195. package/dist/chunks/velin-dialog-TNS3WXMB.js +144 -0
  196. package/dist/chunks/velin-drawer-BE6TYN3H.js +130 -0
  197. package/dist/chunks/velin-drawer-DLL2GLT6.js +130 -0
  198. package/dist/chunks/velin-drawer-MJS6WCF7.js +130 -0
  199. package/dist/chunks/velin-dropdown-PLUIFA5M.js +172 -0
  200. package/dist/chunks/velin-email-N5B5ZYTE.js +90 -0
  201. package/dist/chunks/velin-email-NTM6DG3V.js +90 -0
  202. package/dist/chunks/velin-email-RBX6ZR4S.js +90 -0
  203. package/dist/chunks/velin-haptic-4QQTM3RK.js +50 -0
  204. package/dist/chunks/velin-icon-7M76DOPB.js +164 -0
  205. package/dist/chunks/velin-icon-KZO3IEHG.js +164 -0
  206. package/dist/chunks/velin-icon-WTFPG2DD.js +191 -0
  207. package/dist/chunks/velin-icon-ZTSKUOVN.js +193 -0
  208. package/dist/chunks/velin-lightbox-6E7CAATP.js +149 -0
  209. package/dist/chunks/velin-lightbox-I75U6JF4.js +140 -0
  210. package/dist/chunks/velin-lightbox-QOTB4PRL.js +140 -0
  211. package/dist/chunks/velin-lightbox-UCD6N7HF.js +140 -0
  212. package/dist/chunks/velin-live-dot-RJHX2DGY.js +74 -0
  213. package/dist/chunks/velin-live-dot-XHUZCIDZ.js +67 -0
  214. package/dist/chunks/velin-menubar-I426AED2.js +86 -0
  215. package/dist/chunks/velin-menubar-J66JXRWW.js +86 -0
  216. package/dist/chunks/velin-menubar-SFEXJE56.js +86 -0
  217. package/dist/chunks/velin-modal-2HOBXE6F.js +176 -0
  218. package/dist/chunks/velin-modal-5HYNNNUI.js +176 -0
  219. package/dist/chunks/velin-modal-AEOTZ67O.js +177 -0
  220. package/dist/chunks/velin-modal-GLI2XZZB.js +176 -0
  221. package/dist/chunks/velin-persist-DFPL3QP4.js +110 -0
  222. package/dist/chunks/velin-popover-EXINRIGJ.js +145 -0
  223. package/dist/chunks/velin-popover-HQW6Q2Y6.js +172 -0
  224. package/dist/chunks/velin-popover-OXX7MRZ6.js +145 -0
  225. package/dist/chunks/velin-popover-T72HK3DF.js +151 -0
  226. package/dist/chunks/velin-popover-YKCCROJG.js +145 -0
  227. package/dist/chunks/velin-progress-ring-E4YRLZHH.js +66 -0
  228. package/dist/chunks/velin-rating-6CDBI6DG.js +91 -0
  229. package/dist/chunks/velin-rating-N4DI2XNL.js +91 -0
  230. package/dist/chunks/velin-rating-XRQJV4LO.js +91 -0
  231. package/dist/chunks/velin-scroll-top-F2FCZNYD.js +58 -0
  232. package/dist/chunks/velin-scroll-top-OHYVVHHY.js +58 -0
  233. package/dist/chunks/velin-scrollspy-OKCT4FDV.js +50 -0
  234. package/dist/chunks/velin-search-2XRQFBCS.js +556 -0
  235. package/dist/chunks/velin-search-4YHJWYSS.js +556 -0
  236. package/dist/chunks/velin-search-7B4KJ6TX.js +557 -0
  237. package/dist/chunks/velin-search-LWHY23Q5.js +474 -0
  238. package/dist/chunks/velin-secure-field-6PV7RZDJ.js +95 -0
  239. package/dist/chunks/velin-secure-field-H7FSBUJW.js +95 -0
  240. package/dist/chunks/velin-secure-field-OFNAXCDP.js +95 -0
  241. package/dist/chunks/velin-segmented-control-EKL4E3BL.js +109 -0
  242. package/dist/chunks/velin-segmented-control-GYD5BIXQ.js +109 -0
  243. package/dist/chunks/velin-segmented-control-PQX6LKOJ.js +109 -0
  244. package/dist/chunks/velin-sheet-3OGI4HD7.js +119 -0
  245. package/dist/chunks/velin-sheet-OUK6572T.js +119 -0
  246. package/dist/chunks/velin-sheet-R2Y67X6H.js +119 -0
  247. package/dist/chunks/velin-sparkline-GVEANJVW.js +184 -0
  248. package/dist/chunks/velin-sparkline-KDA3GQAN.js +162 -0
  249. package/dist/chunks/velin-sparkline-XVTFC6GE.js +181 -0
  250. package/dist/chunks/velin-stepper-2ZIHLKGM.js +141 -0
  251. package/dist/chunks/velin-stepper-7ECRUYHD.js +148 -0
  252. package/dist/chunks/velin-stepper-FYEUEWE6.js +141 -0
  253. package/dist/chunks/velin-stepper-X54WGHDG.js +141 -0
  254. package/dist/chunks/velin-stepper-wc-QU3KRVQR.js +133 -0
  255. package/dist/chunks/velin-tabs-5UQ5ZVMV.js +138 -0
  256. package/dist/chunks/velin-theme-toggle-32J2CBNC.js +291 -0
  257. package/dist/chunks/velin-theme-toggle-BNNS7WLV.js +285 -0
  258. package/dist/chunks/velin-theme-toggle-NO7V4TWI.js +285 -0
  259. package/dist/chunks/velin-theme-toggle-QELCJM2Q.js +291 -0
  260. package/dist/chunks/velin-toast-OMMOA2DK.js +109 -0
  261. package/dist/chunks/velin-toast-RCZ4CSUC.js +109 -0
  262. package/dist/chunks/velin-toast-RSF2IHT3.js +113 -0
  263. package/dist/chunks/velin-toast-WEUBTJG6.js +109 -0
  264. package/dist/chunks/velin-tooltip-664IOHZA.js +112 -0
  265. package/dist/chunks/velin-tooltip-BDHGRTQB.js +112 -0
  266. package/dist/chunks/velin-tooltip-HVUOKNVX.js +112 -0
  267. package/dist/chunks/velin-tooltip-wc-LXDVT6DE.js +103 -0
  268. package/dist/chunks/worker-client-R6DBYPFT.js +46 -0
  269. package/dist/llms.test.txt +40 -0
  270. package/dist/llms.txt +40 -0
  271. package/dist/search-index.json +1773 -0
  272. package/dist/search-index.test.json +1773 -0
  273. package/dist/themes/brutalist.min.css +1 -1
  274. package/dist/themes/midnight.min.css +1 -1
  275. package/dist/themes/pastel.min.css +1 -1
  276. package/dist/themes/retro.min.css +1 -1
  277. package/dist/themes/sunset.min.css +1 -1
  278. package/dist/velin-agent.json +684 -0
  279. package/dist/velin-agent.test.json +684 -0
  280. package/dist/velinstyle-components.iife.js +7959 -2199
  281. package/dist/velinstyle-components.js +7982 -2174
  282. package/dist/velinstyle-components.min.js +521 -90
  283. package/dist/velinstyle.css +1290 -282
  284. package/dist/velinstyle.d.ts +54 -0
  285. package/dist/velinstyle.min.css +1 -1
  286. package/examples/search-index.schema.json +33 -0
  287. package/package.json +57 -10
  288. package/src/a11y/authentication.css +37 -0
  289. package/src/a11y/consistent-help.css +22 -0
  290. package/src/a11y/dragging-alternatives.css +30 -0
  291. package/src/a11y/focus-appearance.css +20 -0
  292. package/src/a11y/high-contrast-aaa.css +16 -26
  293. package/src/a11y/security.css +18 -0
  294. package/src/a11y/skip-link.css +3 -1
  295. package/src/a11y/sr-only.css +1 -1
  296. package/src/base/reset.css +0 -25
  297. package/src/base/wc-placeholder.css +102 -0
  298. package/src/components/attributes.css +42 -0
  299. package/src/components/highlight.css +182 -0
  300. package/src/components/input-group.css +1 -2
  301. package/src/components/nav.css +151 -151
  302. package/src/components/search.css +95 -0
  303. package/src/components/stepper.css +2 -4
  304. package/src/components/timeline.css +1 -2
  305. package/src/components/tooltip.css +2 -4
  306. package/src/layout/container.css +5 -5
  307. package/src/layout/grid.css +8 -8
  308. package/src/layout/patterns.css +5 -5
  309. package/src/themes/brutalist.css +2 -11
  310. package/src/themes/corporate.css +0 -9
  311. package/src/themes/earth.css +0 -9
  312. package/src/themes/forest.css +0 -9
  313. package/src/themes/midnight.css +1 -10
  314. package/src/themes/neon.css +0 -9
  315. package/src/themes/nordic.css +0 -9
  316. package/src/themes/ocean.css +0 -9
  317. package/src/themes/pastel.css +1 -10
  318. package/src/themes/retro.css +2 -11
  319. package/src/themes/sharp.css +0 -7
  320. package/src/themes/soft.css +0 -7
  321. package/src/themes/sunset.css +1 -10
  322. package/src/tokens/color.css +45 -46
  323. package/src/tokens/fonts.css +10 -0
  324. package/src/tokens/motion.css +7 -0
  325. package/src/tokens/typography.css +4 -5
  326. package/src/utilities/animation.css +99 -3
  327. package/src/utilities/chart-animation.css +101 -0
  328. package/src/utilities/display.css +3 -3
  329. package/src/utilities/divide.css +2 -6
  330. package/src/utilities/filter-effects.css +103 -0
  331. package/src/utilities/responsive.css +4 -4
  332. package/src/utilities/safe-area.css +39 -0
  333. package/src/utilities/scroll-animation.css +34 -0
  334. package/src/utilities/spacing.css +17 -8
  335. package/src/utilities/text.css +4 -1
  336. package/src/velinstyle.css +12 -0
package/cli/meta.js ADDED
@@ -0,0 +1,97 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join, dirname, resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { buildAgentBundle, buildLlmsTxt, buildPageMeta, serializePageMetaScript } from '../core/meta/index.js';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const PKG_ROOT = join(__dirname, '..');
8
+
9
+ /**
10
+ * @param {object} [options]
11
+ * @param {string} [options.outFile]
12
+ * @param {string} [options.llmsFile]
13
+ * @param {string} [options.baseUrl]
14
+ * @param {boolean} [options.llms]
15
+ */
16
+ export async function buildMeta(options = {}) {
17
+ const outFile = options.outFile || join(PKG_ROOT, 'dist', 'velin-agent.json');
18
+ const llmsFile = options.llmsFile || join(PKG_ROOT, 'dist', 'llms.txt');
19
+ const bundle = await buildAgentBundle({ pkgRoot: PKG_ROOT });
20
+
21
+ mkdirSync(dirname(outFile), { recursive: true });
22
+ writeFileSync(outFile, JSON.stringify(bundle, null, 2), 'utf-8');
23
+
24
+ const written = [outFile];
25
+
26
+ if (options.llms !== false) {
27
+ const llms = buildLlmsTxt(bundle, options.baseUrl || 'https://velinstyle.info');
28
+ writeFileSync(llmsFile, llms, 'utf-8');
29
+ written.push(llmsFile);
30
+ }
31
+
32
+ return { ok: true, bundle, written, count: written.length };
33
+ }
34
+
35
+ /**
36
+ * @param {string} htmlPath
37
+ * @param {object} [options]
38
+ */
39
+ export function metaPage(htmlPath, options = {}) {
40
+ const abs = resolve(htmlPath);
41
+ if (!existsSync(abs)) {
42
+ return { ok: false, error: `File not found: ${abs}` };
43
+ }
44
+ const html = readFileSync(abs, 'utf-8');
45
+ const rel = htmlPath.replace(/\\/g, '/');
46
+ const meta = buildPageMeta(html, rel, PKG_ROOT);
47
+ const script = serializePageMetaScript(meta);
48
+
49
+ if (options.write) {
50
+ if (html.includes('id="velin-meta"')) {
51
+ const updated = html.replace(
52
+ /<script[^>]*id=["']velin-meta["'][^>]*>[\s\S]*?<\/script>/i,
53
+ script,
54
+ );
55
+ writeFileSync(abs, updated, 'utf-8');
56
+ } else if (/<\/head>/i.test(html)) {
57
+ writeFileSync(abs, html.replace(/<\/head>/i, ` ${script}\n</head>`), 'utf-8');
58
+ } else {
59
+ return { ok: false, error: 'No </head> or existing velin-meta block found' };
60
+ }
61
+ }
62
+
63
+ return { ok: true, meta, script };
64
+ }
65
+
66
+ export async function metaMain(argv = process.argv.slice(2)) {
67
+ const pageIdx = argv.indexOf('page');
68
+ if (pageIdx !== -1 && argv[pageIdx + 1]) {
69
+ const write = argv.includes('--write');
70
+ const result = metaPage(argv[pageIdx + 1], { write });
71
+ if (!result.ok) {
72
+ console.error(result.error);
73
+ process.exit(1);
74
+ }
75
+ if (write) {
76
+ console.log(`Updated velin-meta in ${argv[pageIdx + 1]}`);
77
+ } else {
78
+ console.log(result.script);
79
+ }
80
+ return;
81
+ }
82
+
83
+ const outIdx = argv.indexOf('--out');
84
+ const llmsIdx = argv.indexOf('--llms-out');
85
+ const baseIdx = argv.indexOf('--base-url');
86
+ const noLlms = argv.includes('--no-llms');
87
+
88
+ const result = await buildMeta({
89
+ outFile: outIdx !== -1 && argv[outIdx + 1] ? resolve(argv[outIdx + 1]) : undefined,
90
+ llmsFile: llmsIdx !== -1 && argv[llmsIdx + 1] ? resolve(argv[llmsIdx + 1]) : undefined,
91
+ baseUrl: baseIdx !== -1 && argv[baseIdx + 1] ? argv[baseIdx + 1] : undefined,
92
+ llms: !noLlms,
93
+ });
94
+
95
+ console.log(`Velin-Meta: wrote ${result.count} file(s)`);
96
+ for (const p of result.written) console.log(` ${p}`);
97
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Performance audit for static HTML — images, scripts, inline styles.
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
5
+ import { join, extname, relative } from 'path';
6
+
7
+ const DEFAULT_IGNORE = ['node_modules', 'dist', '.git', '.next', 'build'];
8
+
9
+ export function walkHtmlFiles(dir, ignore = DEFAULT_IGNORE) {
10
+ const results = [];
11
+ if (!existsSync(dir)) return results;
12
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
13
+ if (ignore.includes(entry.name)) continue;
14
+ const full = join(dir, entry.name);
15
+ if (entry.isDirectory()) results.push(...walkHtmlFiles(full, ignore));
16
+ else if (extname(entry.name).toLowerCase() === '.html') results.push(full);
17
+ }
18
+ return results;
19
+ }
20
+
21
+ function lineOf(content, index) {
22
+ return content.slice(0, index).split('\n').length;
23
+ }
24
+
25
+ /**
26
+ * @param {string} html
27
+ * @param {{ file?: string }} meta
28
+ */
29
+ export function auditHtml(html, meta = {}) {
30
+ const issues = [];
31
+ const file = meta.file || '';
32
+ const lines = html.split('\n');
33
+
34
+ lines.forEach((line, idx) => {
35
+ const ln = idx + 1;
36
+
37
+ const imgTags = [...line.matchAll(/<img\b[^>]*>/gi)];
38
+ for (const m of imgTags) {
39
+ const tag = m[0];
40
+ const hasW = /\bwidth\s*=/i.test(tag);
41
+ const hasH = /\bheight\s*=/i.test(tag);
42
+ const hasStyleDim = /style\s*=\s*["'][^"']*(?:width|height)\s*:/i.test(tag);
43
+ if (!hasW && !hasH && !hasStyleDim) {
44
+ issues.push({
45
+ id: 'img-missing-dimensions',
46
+ severity: 'warning',
47
+ message: '<img> without width/height causes layout shift (CLS).',
48
+ line: ln,
49
+ file,
50
+ fixable: true,
51
+ fix: (l) => l.replace(/<img\b/i, '<img width="800" height="450"'),
52
+ });
53
+ }
54
+ if (!/\bloading\s*=/i.test(tag) && !/\bfetchpriority\s*=\s*["']high["']/i.test(tag)) {
55
+ issues.push({
56
+ id: 'img-no-lazy',
57
+ severity: 'info',
58
+ message: 'Consider loading="lazy" for below-fold images.',
59
+ line: ln,
60
+ file,
61
+ fixable: true,
62
+ fix: (l) => l.replace(/<img\b/i, '<img loading="lazy"'),
63
+ });
64
+ }
65
+ }
66
+
67
+ const scriptTags = [...line.matchAll(/<script\b[^>]*>/gi)];
68
+ for (const m of scriptTags) {
69
+ const tag = m[0];
70
+ if (/\bsrc\s*=/i.test(tag) && !/\bdefer\b/i.test(tag) && !/\basync\b/i.test(tag) && !/\btype\s*=\s*["']module["']/i.test(tag)) {
71
+ issues.push({
72
+ id: 'script-no-defer',
73
+ severity: 'warning',
74
+ message: 'External <script> without defer/async blocks parsing.',
75
+ line: ln,
76
+ file,
77
+ fixable: true,
78
+ fix: (l) => l.replace(/<script\b/i, '<script defer'),
79
+ });
80
+ }
81
+ }
82
+
83
+ if (/\sstyle\s*=\s*["'][^"']{120,}["']/i.test(line)) {
84
+ issues.push({
85
+ id: 'large-inline-style',
86
+ severity: 'info',
87
+ message: 'Large inline style block. Prefer external CSS.',
88
+ line: ln,
89
+ file,
90
+ fixable: false,
91
+ });
92
+ }
93
+ });
94
+
95
+ if (/@font-face/i.test(html) && !/font-display\s*:\s*swap/i.test(html)) {
96
+ issues.push({
97
+ id: 'font-display-swap',
98
+ severity: 'info',
99
+ message: 'Add font-display: swap to @font-face rules.',
100
+ file,
101
+ line: 1,
102
+ fixable: false,
103
+ });
104
+ }
105
+
106
+ if (/velinstyle-components\.min\.js/i.test(html) && /velin-(?:modal|toast|tabs)/i.test(html) === false) {
107
+ issues.push({
108
+ id: 'unused-velin-import',
109
+ severity: 'info',
110
+ message: 'Full component bundle loaded. Consider @birdapi/velinstyle/runtime for tree-shaking.',
111
+ file,
112
+ line: 1,
113
+ fixable: false,
114
+ });
115
+ }
116
+
117
+ return issues;
118
+ }
119
+
120
+ export function auditPath(targetPath) {
121
+ const files = walkHtmlFiles(targetPath);
122
+ const issues = [];
123
+ for (const file of files) {
124
+ const html = readFileSync(file, 'utf-8');
125
+ issues.push(...auditHtml(html, { file }));
126
+ }
127
+ return { issues, files };
128
+ }
129
+
130
+ export function formatTextReport(issues, files) {
131
+ const lines = [`Performance audit — ${files.length} HTML file(s)\n`];
132
+ if (issues.length === 0) {
133
+ lines.push('No performance issues found.');
134
+ return lines.join('\n');
135
+ }
136
+ for (const i of issues) {
137
+ const loc = i.line ? `${relative(process.cwd(), i.file)}:${i.line}` : i.file;
138
+ lines.push(`[${i.severity}] ${i.id} — ${loc}`);
139
+ lines.push(` ${i.message}`);
140
+ }
141
+ return lines.join('\n');
142
+ }
143
+
144
+ export function fixPath(targetPath, { write = false, dryRun = true } = {}) {
145
+ const { issues } = auditPath(targetPath);
146
+ const fixable = issues.filter((i) => i.fixable && typeof i.fix === 'function');
147
+ const byFile = {};
148
+ for (const issue of fixable) {
149
+ if (!byFile[issue.file]) byFile[issue.file] = [];
150
+ byFile[issue.file].push(issue);
151
+ }
152
+ const changes = [];
153
+ for (const [file, fileIssues] of Object.entries(byFile)) {
154
+ const original = readFileSync(file, 'utf-8');
155
+ const lines = original.split('\n');
156
+ fileIssues.sort((a, b) => b.line - a.line);
157
+ const applied = [];
158
+ for (const issue of fileIssues) {
159
+ const idx = issue.line - 1;
160
+ if (idx < 0 || idx >= lines.length) continue;
161
+ const next = issue.fix(lines[idx]);
162
+ if (next !== lines[idx]) {
163
+ lines[idx] = next;
164
+ applied.push(issue.id);
165
+ }
166
+ }
167
+ const content = lines.join('\n');
168
+ if (content !== original) {
169
+ if (write && !dryRun) writeFileSync(file, content, 'utf-8');
170
+ changes.push({ file, changes: applied });
171
+ }
172
+ }
173
+ return { ok: true, changes, dryRun: dryRun && !write };
174
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * PII / secrets detection for velinstyle scan (--only pii).
3
+ */
4
+
5
+ const EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
6
+ const PLACEHOLDER_EMAILS = new Set([
7
+ 'user@example.com',
8
+ 'test@example.com',
9
+ 'admin@example.com',
10
+ 'email@example.com',
11
+ 'you@example.com',
12
+ 'name@example.com',
13
+ '{{email}}',
14
+ '{{EMAIL}}',
15
+ ]);
16
+
17
+ const SECRET_PATTERNS = [
18
+ { re: /\b(?:api[_-]?key|apikey)\s*[:=]\s*['"][^'"]{8,}['"]/i, rule: 'pii/hardcoded-secret', msg: 'Possible API key in source.' },
19
+ { re: /\b(?:secret|password|passwd|pwd)\s*[:=]\s*['"][^'"]{4,}['"]/i, rule: 'pii/hardcoded-secret', msg: 'Possible password/secret in source.' },
20
+ { re: /\bAKIA[0-9A-Z]{16}\b/, rule: 'pii/hardcoded-secret', msg: 'AWS access key pattern detected.' },
21
+ { re: /\bsk_(?:live|test)_[0-9a-zA-Z]{16,}\b/, rule: 'pii/hardcoded-secret', msg: 'Stripe secret key pattern detected.' },
22
+ { re: /\bghp_[0-9a-zA-Z]{36,}\b/, rule: 'pii/hardcoded-secret', msg: 'GitHub token pattern detected.' },
23
+ ];
24
+
25
+ export function isPlaceholderEmail(email) {
26
+ const lower = email.toLowerCase();
27
+ if (PLACEHOLDER_EMAILS.has(lower)) return true;
28
+ if (/@example\.(com|org|net)$/i.test(email)) return true;
29
+ if (/@test\.(com|local)$/i.test(email)) return true;
30
+ if (/@localhost$/i.test(email)) return true;
31
+ return false;
32
+ }
33
+
34
+ export function maskEmail(email, placeholder = 'user@example.com') {
35
+ return placeholder;
36
+ }
37
+
38
+ export function fixHardcodedEmailLine(line, placeholder = 'user@example.com') {
39
+ return line.replace(EMAIL_RE, (match) => {
40
+ if (isPlaceholderEmail(match)) return match;
41
+ return placeholder;
42
+ });
43
+ }
44
+
45
+ export function scanPIIHTML(content, file, options = {}) {
46
+ const issues = [];
47
+ const lines = content.split('\n');
48
+ const placeholder = options.fixEmailPlaceholder || 'user@example.com';
49
+
50
+ lines.forEach((line, idx) => {
51
+ const ln = idx + 1;
52
+ if (/\/\/|\/\*|\*\/|<!--/.test(line) && !/@/.test(line.replace(/<!--.*-->/, ''))) {
53
+ /* still scan — emails in comments are PII too */
54
+ }
55
+
56
+ const emails = [...line.matchAll(EMAIL_RE)].map((m) => m[0]);
57
+ for (const email of emails) {
58
+ if (isPlaceholderEmail(email)) continue;
59
+ issues.push({
60
+ file,
61
+ line: ln,
62
+ severity: 1,
63
+ rule: 'pii/hardcoded-email',
64
+ message: `Hardcoded email "${email}" found. Use env/config or <velin-email>.`,
65
+ fixable: true,
66
+ fix: (currentLine) => fixHardcodedEmailLine(currentLine, placeholder),
67
+ });
68
+ }
69
+
70
+ const hasMailto = /mailto:/i.test(line);
71
+ const emailRe = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/;
72
+ if (hasMailto && emailRe.test(line)) {
73
+ issues.push({
74
+ file,
75
+ line: ln,
76
+ severity: 2,
77
+ rule: 'pii/mailto-in-source',
78
+ message: 'mailto: link exposes email in HTML. Consider <velin-email> for obfuscation.',
79
+ fixable: false,
80
+ });
81
+ }
82
+ });
83
+
84
+ return issues;
85
+ }
86
+
87
+ export function scanPIIJS(content, file) {
88
+ const issues = [];
89
+ const lines = content.split('\n');
90
+
91
+ lines.forEach((line, idx) => {
92
+ const ln = idx + 1;
93
+
94
+ for (const { re, rule, msg } of SECRET_PATTERNS) {
95
+ if (re.test(line)) {
96
+ issues.push({
97
+ file,
98
+ line: ln,
99
+ severity: 0,
100
+ rule,
101
+ message: msg,
102
+ fixable: false,
103
+ });
104
+ }
105
+ }
106
+
107
+ const emails = [...line.matchAll(EMAIL_RE)].map((m) => m[0]);
108
+ for (const email of emails) {
109
+ if (isPlaceholderEmail(email)) continue;
110
+ issues.push({
111
+ file,
112
+ line: ln,
113
+ severity: 1,
114
+ rule: 'pii/hardcoded-email',
115
+ message: `Hardcoded email "${email}" in JavaScript.`,
116
+ fixable: true,
117
+ fix: (currentLine) => fixHardcodedEmailLine(currentLine),
118
+ });
119
+ }
120
+
121
+ if (/localStorage\.setItem\s*\([^)]*@/.test(line) || /sessionStorage\.setItem\s*\([^)]*@/.test(line)) {
122
+ issues.push({
123
+ file,
124
+ line: ln,
125
+ severity: 1,
126
+ rule: 'pii/localstorage-pii',
127
+ message: 'Storing email-like data in Web Storage may violate privacy policies.',
128
+ fixable: false,
129
+ });
130
+ }
131
+ });
132
+
133
+ return issues;
134
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "intents": {
3
+ "navbar": {
4
+ "keywords": ["navbar", "navigation", "nav bar", "nav-bar", "header", "menü", "menu bar", "top bar"],
5
+ "blueprints": ["navbar-header"],
6
+ "optional": ["search-field"],
7
+ "optionalIf": { "search": ["search-field"], "suche": ["search-field"] },
8
+ "confidence": "high"
9
+ },
10
+ "modal": {
11
+ "keywords": ["modal", "dialog", "popup", "bestätigung", "confirm", "lightbox"],
12
+ "blueprints": ["modal"],
13
+ "confidence": "high"
14
+ },
15
+ "card": {
16
+ "keywords": ["card", "cards", "karte", "karten", "grid", "kacheln"],
17
+ "blueprints": ["card-grid"],
18
+ "confidence": "high"
19
+ },
20
+ "dashboard": {
21
+ "keywords": ["dashboard", "admin", "shell", "sidebar layout", "app shell"],
22
+ "blueprints": ["navbar-header", "layout-dashboard"],
23
+ "confidence": "high"
24
+ },
25
+ "login": {
26
+ "keywords": ["login", "sign in", "signin", "anmelden", "auth"],
27
+ "blueprints": ["form-login"],
28
+ "confidence": "high"
29
+ },
30
+ "footer": {
31
+ "keywords": ["footer", "fußzeile", "fusszeile", "site footer"],
32
+ "blueprints": ["footer-simple"],
33
+ "confidence": "high"
34
+ },
35
+ "pricing": {
36
+ "keywords": ["pricing", "preise", "plans", "tarife", "subscription"],
37
+ "blueprints": ["pricing-table"],
38
+ "confidence": "high"
39
+ },
40
+ "empty": {
41
+ "keywords": ["empty state", "empty-state", "keine daten", "no results", "leer"],
42
+ "blueprints": ["empty-state"],
43
+ "confidence": "high"
44
+ },
45
+ "hero": {
46
+ "keywords": ["hero", "landing", "above the fold", "cta section"],
47
+ "blueprints": ["hero-section"],
48
+ "confidence": "medium"
49
+ },
50
+ "table": {
51
+ "keywords": ["table", "datatable", "data table", "tabelle"],
52
+ "blueprints": ["table-responsive"],
53
+ "confidence": "high"
54
+ },
55
+ "onboarding": {
56
+ "keywords": ["onboarding", "wizard", "steps", "tutorial flow"],
57
+ "blueprints": ["onboarding"],
58
+ "confidence": "high"
59
+ }
60
+ },
61
+ "fallback": {
62
+ "intent": "hero",
63
+ "confidence": "low"
64
+ },
65
+ "slots": {
66
+ "brand": ["brand", "logo", "marke"],
67
+ "title": ["title", "titel", "heading", "überschrift"],
68
+ "cta": ["cta", "button", "get started", "loslegen", "start"]
69
+ }
70
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Prompt-based scaffolding: map natural language to VelinStyle blueprint composition.
3
+ */
4
+ import { readFileSync } from 'fs';
5
+ import { dirname, join } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { emitBlueprint } from './blueprint.js';
8
+ import { auditHtml, suggestFromIssues } from './layout-audit.js';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const RECIPES_PATH = join(__dirname, 'scaffold-recipes.json');
12
+
13
+ function loadRecipes() {
14
+ return JSON.parse(readFileSync(RECIPES_PATH, 'utf-8'));
15
+ }
16
+
17
+ /**
18
+ * @param {string} prompt
19
+ */
20
+ export function parseIntent(prompt) {
21
+ const recipes = loadRecipes();
22
+ const lower = prompt.toLowerCase();
23
+ let best = { id: recipes.fallback.intent, confidence: recipes.fallback.confidence, score: 0 };
24
+
25
+ for (const [id, def] of Object.entries(recipes.intents)) {
26
+ let score = 0;
27
+ for (const kw of def.keywords) {
28
+ if (lower.includes(kw.toLowerCase())) score += kw.length;
29
+ }
30
+ if (score > best.score) {
31
+ best = { id, confidence: def.confidence || 'medium', score };
32
+ }
33
+ }
34
+
35
+ return { id: best.id, confidence: best.score > 0 ? best.confidence : 'low' };
36
+ }
37
+
38
+ /**
39
+ * @param {string} prompt
40
+ */
41
+ export function extractSlots(prompt) {
42
+ const recipes = loadRecipes();
43
+ const lower = prompt.toLowerCase();
44
+ const slots = {
45
+ brand: 'Brand',
46
+ title: 'Welcome',
47
+ cta: 'Get started',
48
+ columns: 3,
49
+ };
50
+
51
+ const brandMatch = prompt.match(/(?:brand|logo|marke)\s*[:\-]?\s*["']?([^"'\n,]+)/i);
52
+ if (brandMatch) slots.brand = brandMatch[1].trim();
53
+
54
+ const titleMatch = prompt.match(/(?:title|titel|heading)\s*[:\-]?\s*["']?([^"'\n]+)/i);
55
+ if (titleMatch) slots.title = titleMatch[1].trim();
56
+
57
+ const colMatch = lower.match(/(\d+)\s*(?:spalten|columns|cols|karten|cards)/);
58
+ if (colMatch) slots.columns = Math.min(12, Math.max(1, parseInt(colMatch[1], 10)));
59
+
60
+ for (const [slot, keys] of Object.entries(recipes.slots || {})) {
61
+ if (keys.some((k) => lower.includes(k)) && slot === 'cta') {
62
+ slots.cta = 'Get started';
63
+ }
64
+ }
65
+
66
+ if (/suche|search/i.test(prompt)) slots.includeSearch = true;
67
+
68
+ return slots;
69
+ }
70
+
71
+ /**
72
+ * @param {string} intentId
73
+ * @param {{ prompt?: string, slots?: object }} options
74
+ */
75
+ export function resolveBlueprints(intentId, options = {}) {
76
+ const recipes = loadRecipes();
77
+ const def = recipes.intents[intentId] || recipes.intents[recipes.fallback.intent];
78
+ const ids = [...def.blueprints];
79
+
80
+ const lower = (options.prompt || '').toLowerCase();
81
+ if (def.optionalIf) {
82
+ for (const [key, bps] of Object.entries(def.optionalIf)) {
83
+ if (lower.includes(key)) ids.push(...bps);
84
+ }
85
+ }
86
+ if (options.slots?.includeSearch && intentId === 'navbar' && !ids.includes('search-field')) {
87
+ ids.push('search-field');
88
+ }
89
+
90
+ return [...new Set(ids)];
91
+ }
92
+
93
+ function applySlots(html, slots) {
94
+ let out = html;
95
+ out = out.replace(/\bBrand\b/g, slots.brand);
96
+ out = out.replace(/Card one/gi, `${slots.title} — 1`);
97
+ out = out.replace(/Welcome/gi, slots.title);
98
+ out = out.replace(/Get started/gi, slots.cta);
99
+ return out;
100
+ }
101
+
102
+ /**
103
+ * @param {string} prompt
104
+ * @param {{ pkgRoot?: string }} options
105
+ */
106
+ export function scaffoldFromPrompt(prompt, options = {}) {
107
+ if (!prompt || !prompt.trim()) {
108
+ return { ok: false, error: 'Prompt is required.' };
109
+ }
110
+
111
+ const intent = parseIntent(prompt);
112
+ const slots = extractSlots(prompt);
113
+ const blueprintIds = resolveBlueprints(intent.id, { prompt, slots });
114
+
115
+ const parts = [];
116
+ for (const id of blueprintIds) {
117
+ const r = emitBlueprint(id, {});
118
+ if (!r.ok) {
119
+ return { ok: false, error: r.error };
120
+ }
121
+ parts.push(r.text);
122
+ }
123
+
124
+ let html = parts.join('\n\n');
125
+ html = applySlots(html, slots);
126
+
127
+ const banner =
128
+ `<!-- Generated by velinstyle scaffold (0.8.0) — intent: ${intent.id}, confidence: ${intent.confidence} -->\n` +
129
+ `<!-- Blueprints: ${blueprintIds.join(', ')} — run: velinstyle layout suggest & velinstyle scan -->\n`;
130
+
131
+ const fullHtml = banner + html;
132
+ const issues = auditHtml(fullHtml);
133
+ const responsiveHints = suggestFromIssues(issues);
134
+
135
+ return {
136
+ ok: true,
137
+ intent: intent.id,
138
+ confidence: intent.confidence,
139
+ blueprints: blueprintIds,
140
+ html: fullHtml,
141
+ slots,
142
+ responsiveHints,
143
+ nextSteps: ['velinstyle layout suggest <file>', 'velinstyle scan <file>'],
144
+ };
145
+ }
146
+
147
+ export function listIntents() {
148
+ const recipes = loadRecipes();
149
+ return Object.entries(recipes.intents).map(([id, def]) => ({
150
+ id,
151
+ keywords: def.keywords,
152
+ blueprints: def.blueprints,
153
+ confidence: def.confidence,
154
+ }));
155
+ }