@birdapi/velinstyle 1.0.0 → 1.2.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 (321) hide show
  1. package/README.de.md +87 -158
  2. package/README.md +188 -115
  3. package/cli/blueprint.js +98 -5
  4. package/cli/blueprints/app-chrome.html +18 -0
  5. package/cli/blueprints/benefits-grid.html +9 -0
  6. package/cli/blueprints/bottom-nav-mobile.html +2 -1
  7. package/cli/blueprints/cookie-consent.html +1 -1
  8. package/cli/blueprints/cta-band.html +6 -0
  9. package/cli/blueprints/faq-section.html +18 -0
  10. package/cli/blueprints/filter-bar.html +4 -4
  11. package/cli/blueprints/form-contact.html +6 -5
  12. package/cli/blueprints/hero-section.html +3 -3
  13. package/cli/blueprints/notification-center.html +1 -1
  14. package/cli/blueprints/onboarding.html +2 -1
  15. package/cli/blueprints/ops-console.html +29 -0
  16. package/cli/blueprints/pricing-band.html +37 -0
  17. package/cli/blueprints/process-steps.html +10 -0
  18. package/cli/blueprints/services-grid.html +9 -0
  19. package/cli/blueprints/settings-panel.html +8 -6
  20. package/cli/blueprints/split-hero.html +25 -0
  21. package/cli/blueprints/testimonials.html +8 -0
  22. package/cli/blueprints/trust-bar.html +10 -0
  23. package/cli/cli-manifest.json +81 -17
  24. package/cli/contrast-utils.js +97 -0
  25. package/cli/create.js +370 -0
  26. package/cli/docgen/extract-a11y.js +4 -2
  27. package/cli/docgen/extract-attributes.js +126 -10
  28. package/cli/docgen/extract-cli.js +3 -2
  29. package/cli/docgen/extract-components.js +61 -2
  30. package/cli/docgen/extract-utilities.js +10 -10
  31. package/cli/doctor.js +119 -0
  32. package/cli/index.js +535 -46
  33. package/cli/meta.js +84 -1
  34. package/cli/perf-audit.js +15 -2
  35. package/cli/prompt-engine.js +243 -0
  36. package/cli/review.js +268 -0
  37. package/cli/scaffold.js +119 -4
  38. package/cli/scanner-rules-data.js +7 -0
  39. package/cli/scanner.js +357 -4
  40. package/cli/serve.js +81 -0
  41. package/cli/skills.js +223 -0
  42. package/cli/wc-api.js +89 -0
  43. package/cli/workflow.js +42 -0
  44. package/components/index.js +4 -0
  45. package/components/runtime/component-loaders.js +4 -0
  46. package/components/velin-accordion.js +62 -13
  47. package/components/velin-bottom-nav.js +10 -0
  48. package/components/velin-calendar.js +212 -0
  49. package/components/velin-command.js +18 -2
  50. package/components/velin-data-table.js +438 -0
  51. package/components/velin-dialog.js +4 -1
  52. package/components/velin-drawer.js +2 -4
  53. package/components/velin-file-dropzone.js +162 -0
  54. package/components/velin-form-summary.js +348 -0
  55. package/components/velin-icon.js +22 -1
  56. package/components/velin-lightbox.js +5 -3
  57. package/components/velin-modal.js +2 -4
  58. package/components/velin-search.js +1 -1
  59. package/components/velin-sheet.js +2 -4
  60. package/core/a11y/component-contracts.json +35 -1
  61. package/core/highlight/languages/go.js +28 -0
  62. package/core/highlight/languages/python.js +29 -0
  63. package/core/highlight/languages/rust.js +32 -0
  64. package/core/highlight/languages/yaml.js +23 -0
  65. package/core/highlight/registry.js +13 -2
  66. package/core/meta/build.js +126 -6
  67. package/core/meta/design-constraints/contact.json +9 -0
  68. package/core/meta/design-constraints/faq.json +9 -0
  69. package/core/meta/design-constraints/hero.json +13 -0
  70. package/core/meta/knowledge/components.json +622 -0
  71. package/core/meta/knowledge/tokens.json +282 -0
  72. package/core/meta/pages/registry.json +294 -0
  73. package/core/meta/schema.js +3 -1
  74. package/core/meta/sections/registry.json +185 -0
  75. package/dist/chunks/{attributes-TPANLMLF.js → attributes-A423NX5J.js} +3 -3
  76. package/dist/chunks/{attributes-ADOKLKA7.js → attributes-F6UPIEVP.js} +2 -2
  77. package/dist/chunks/{attributes-STYCS3O5.js → attributes-GLYXGGAL.js} +3 -3
  78. package/dist/chunks/{attributes-5Q2WXGRC.js → attributes-UFTYOSAU.js} +20 -3
  79. package/dist/chunks/attributes-XGLXLR2T.js +391 -0
  80. package/dist/chunks/attributes-YF4NI2QS.js +391 -0
  81. package/dist/chunks/browser-AUABBYUK.js +1544 -0
  82. package/dist/chunks/{chunk-SRL6EWHP.js → chunk-2LSKX57P.js} +13 -11
  83. package/dist/chunks/{chunk-RSMRR27R.js → chunk-2TGCVDUL.js} +11 -9
  84. package/dist/chunks/{chunk-XIET2VSZ.js → chunk-5JWGUAPC.js} +17 -13
  85. package/dist/chunks/chunk-7YSO2OQ2.js +113 -0
  86. package/dist/chunks/{chunk-IEHKRARD.js → chunk-BHR3ZG6U.js} +10 -8
  87. package/dist/chunks/{chunk-J472ERN2.js → chunk-ERF5YVP4.js} +15 -4
  88. package/dist/chunks/{chunk-XYLJ2GCB.js → chunk-JMSIHEYX.js} +55 -7
  89. package/dist/chunks/{chunk-MKZP2TBH.js → chunk-L7ZGUU4D.js} +2 -2
  90. package/dist/chunks/chunk-TUGIPKW5.js +113 -0
  91. package/dist/chunks/go-HZ6XTFCK.js +31 -0
  92. package/dist/chunks/{highlight-GJHHV4KM.js → highlight-IBDX4XWS.js} +1 -1
  93. package/dist/chunks/{highlight-XTHRKBA3.js → highlight-M2ELQNYK.js} +1 -1
  94. package/dist/chunks/python-BMPKDKNO.js +32 -0
  95. package/dist/chunks/runtime-entry.js +1 -1
  96. package/dist/chunks/rust-2BWKI2MN.js +35 -0
  97. package/dist/chunks/{velin-accordion-FBIPAEXG.js → velin-accordion-WSY6BMLA.js} +64 -10
  98. package/dist/chunks/{velin-bottom-nav-7GEMPIM6.js → velin-bottom-nav-5RVRNGIP.js} +25 -0
  99. package/dist/chunks/{velin-bottom-nav-2N2TYWOF.js → velin-bottom-nav-N5F4UO2I.js} +26 -1
  100. package/dist/chunks/velin-calendar-OFE6THUD.js +210 -0
  101. package/dist/chunks/velin-calendar-PGVNJTXK.js +217 -0
  102. package/dist/chunks/velin-calendar-XC7JRDSH.js +217 -0
  103. package/dist/chunks/{velin-code-block-CIAURDRJ.js → velin-code-block-ITCLIC6T.js} +1 -1
  104. package/dist/chunks/{velin-code-block-TSUKPY4C.js → velin-code-block-ZGWG7JI5.js} +12 -3
  105. package/dist/chunks/{velin-combobox-NXS4QB4R.js → velin-combobox-56V2ZWT5.js} +1 -1
  106. package/dist/chunks/{velin-command-ONKO2A3J.js → velin-command-2HO2EKR6.js} +29 -2
  107. package/dist/chunks/{velin-command-TMLSRGIU.js → velin-command-ZJSLPK7B.js} +30 -3
  108. package/dist/chunks/{velin-copy-NYDBOKXT.js → velin-copy-XDQSV2XU.js} +1 -1
  109. package/dist/chunks/{velin-countdown-4ZVDGK4R.js → velin-countdown-V4Z5MA63.js} +1 -1
  110. package/dist/chunks/velin-data-table-KK7IDGTP.js +302 -0
  111. package/dist/chunks/velin-data-table-UVR42UUM.js +372 -0
  112. package/dist/chunks/{velin-dialog-GTTZI564.js → velin-dialog-3YK2RPRV.js} +9 -2
  113. package/dist/chunks/{velin-dialog-ELFOZIXZ.js → velin-dialog-S24KAP4S.js} +3 -1
  114. package/dist/chunks/{velin-drawer-BE6TYN3H.js → velin-drawer-5PNU624J.js} +2 -4
  115. package/dist/chunks/{velin-drawer-DLL2GLT6.js → velin-drawer-CCM6S25D.js} +3 -5
  116. package/dist/chunks/{velin-email-RBX6ZR4S.js → velin-email-TNFONXPW.js} +1 -1
  117. package/dist/chunks/velin-file-dropzone-QRGF2JNA.js +154 -0
  118. package/dist/chunks/velin-file-dropzone-XIZMJ3NB.js +154 -0
  119. package/dist/chunks/velin-form-summary-XRQ7COQH.js +273 -0
  120. package/dist/chunks/{velin-icon-7M76DOPB.js → velin-icon-A6NCXGUK.js} +59 -17
  121. package/dist/chunks/{velin-icon-ZTSKUOVN.js → velin-icon-EC4B5XCM.js} +18 -5
  122. package/dist/chunks/{velin-lightbox-6E7CAATP.js → velin-lightbox-FFK4TUUP.js} +7 -6
  123. package/dist/chunks/{velin-lightbox-I75U6JF4.js → velin-lightbox-P725FLTY.js} +15 -5
  124. package/dist/chunks/{velin-menubar-J66JXRWW.js → velin-menubar-6I2LM5HL.js} +1 -1
  125. package/dist/chunks/{velin-modal-5HYNNNUI.js → velin-modal-ICTMGRSU.js} +7 -8
  126. package/dist/chunks/{velin-modal-AEOTZ67O.js → velin-modal-JBNODH7D.js} +2 -4
  127. package/dist/chunks/{velin-popover-T72HK3DF.js → velin-popover-WZG2GHBL.js} +25 -4
  128. package/dist/chunks/{velin-rating-N4DI2XNL.js → velin-rating-QBBYRRBU.js} +1 -1
  129. package/dist/chunks/{velin-search-7B4KJ6TX.js → velin-search-B3L6J2RP.js} +2 -2
  130. package/dist/chunks/{velin-search-2XRQFBCS.js → velin-search-L3UG6NDQ.js} +5 -4
  131. package/dist/chunks/{velin-secure-field-6PV7RZDJ.js → velin-secure-field-DGYMP7OK.js} +1 -1
  132. package/dist/chunks/{velin-segmented-control-GYD5BIXQ.js → velin-segmented-control-P74GJPAR.js} +1 -1
  133. package/dist/chunks/{velin-sheet-R2Y67X6H.js → velin-sheet-M5GZZD7G.js} +2 -4
  134. package/dist/chunks/{velin-sheet-OUK6572T.js → velin-sheet-X7RXIWKV.js} +3 -5
  135. package/dist/chunks/{velin-stepper-X54WGHDG.js → velin-stepper-4YQJUHPC.js} +12 -5
  136. package/dist/chunks/{velin-toast-OMMOA2DK.js → velin-toast-UG5LWTG2.js} +5 -1
  137. package/dist/chunks/{velin-tooltip-BDHGRTQB.js → velin-tooltip-ZDH5SDLU.js} +1 -1
  138. package/dist/chunks/yaml-O67LEQYT.js +26 -0
  139. package/dist/llms.txt +29 -7
  140. package/dist/search-index.json +182 -10
  141. package/dist/velin-agent.json +3997 -26
  142. package/dist/velinstyle-components.iife.js +1952 -287
  143. package/dist/velinstyle-components.js +1956 -287
  144. package/dist/velinstyle-components.min.js +267 -119
  145. package/dist/velinstyle.css +381 -67
  146. package/dist/velinstyle.d.ts +4 -0
  147. package/dist/velinstyle.min.css +1 -1
  148. package/package.json +26 -6
  149. package/packages/skill-engine/README.md +24 -0
  150. package/packages/skill-engine/package.json +7 -0
  151. package/packages/skill-engine/src/catalog.js +29 -0
  152. package/packages/skill-engine/src/index.js +5 -0
  153. package/packages/skill-engine/src/install.js +69 -0
  154. package/packages/skill-engine/src/registry.js +27 -0
  155. package/packages/skill-engine/src/resolve.js +68 -0
  156. package/packages/skill-engine/src/validate.js +40 -0
  157. package/packages/velinstyle-skills/README.md +6 -0
  158. package/packages/velinstyle-skills/adapter.json +8 -0
  159. package/packages/velinstyle-skills/bundles/landing-starter.bundle.yaml +12 -0
  160. package/packages/velinstyle-skills/bundles/review-suite.bundle.yaml +11 -0
  161. package/packages/velinstyle-skills/catalog.json +1004 -0
  162. package/packages/velinstyle-skills/graphs/component-ship.graph.yaml +14 -0
  163. package/packages/velinstyle-skills/graphs/landingpage.graph.yaml +21 -0
  164. package/packages/velinstyle-skills/graphs/release-gate.graph.yaml +15 -0
  165. package/packages/velinstyle-skills/index.js +22 -0
  166. package/packages/velinstyle-skills/package.json +7 -0
  167. package/packages/velinstyle-skills/packs/accessibility.pack.yaml +9 -0
  168. package/packages/velinstyle-skills/packs/design.pack.yaml +10 -0
  169. package/packages/velinstyle-skills/packs/documentation.pack.yaml +8 -0
  170. package/packages/velinstyle-skills/packs/enterprise.pack.yaml +10 -0
  171. package/packages/velinstyle-skills/packs/frontend.pack.yaml +10 -0
  172. package/packages/velinstyle-skills/packs/review.pack.yaml +9 -0
  173. package/packages/velinstyle-skills/projects/admin-panel.project.yaml +12 -0
  174. package/packages/velinstyle-skills/projects/blog.project.yaml +11 -0
  175. package/packages/velinstyle-skills/projects/documentation-portal.project.yaml +13 -0
  176. package/packages/velinstyle-skills/projects/ecommerce-shop.project.yaml +12 -0
  177. package/packages/velinstyle-skills/projects/landing-page.project.yaml +12 -0
  178. package/packages/velinstyle-skills/projects/laravel-dashboard.project.yaml +12 -0
  179. package/packages/velinstyle-skills/projects/marketing-website.project.yaml +14 -0
  180. package/packages/velinstyle-skills/projects/portfolio.project.yaml +12 -0
  181. package/packages/velinstyle-skills/registry.json +142 -0
  182. package/packages/velinstyle-skills/scripts/generate-assets.mjs +163 -0
  183. package/packages/velinstyle-skills/skills/velin-a11y-focus-keyboard/SKILL.md +57 -0
  184. package/packages/velinstyle-skills/skills/velin-a11y-page-checklist/SKILL.md +57 -0
  185. package/packages/velinstyle-skills/skills/velin-a11y-scan-fix/SKILL.md +57 -0
  186. package/packages/velinstyle-skills/skills/velin-ai-agent-briefing/SKILL.md +57 -0
  187. package/packages/velinstyle-skills/skills/velin-ai-plan-scaffold-review/SKILL.md +57 -0
  188. package/packages/velinstyle-skills/skills/velin-ai-skill-authoring/SKILL.md +57 -0
  189. package/packages/velinstyle-skills/skills/velin-components-accessibility-map/SKILL.md +57 -0
  190. package/packages/velinstyle-skills/skills/velin-components-api-contract/SKILL.md +57 -0
  191. package/packages/velinstyle-skills/skills/velin-components-recipe/SKILL.md +57 -0
  192. package/packages/velinstyle-skills/skills/velin-components-select-compose/SKILL.md +57 -0
  193. package/packages/velinstyle-skills/skills/velin-design-hero-compose/SKILL.md +57 -0
  194. package/packages/velinstyle-skills/skills/velin-design-section-rhythm/SKILL.md +57 -0
  195. package/packages/velinstyle-skills/skills/velin-design-surface-roles/SKILL.md +57 -0
  196. package/packages/velinstyle-skills/skills/velin-dev-component-wire/SKILL.md +57 -0
  197. package/packages/velinstyle-skills/skills/velin-dev-page-scaffold/SKILL.md +57 -0
  198. package/packages/velinstyle-skills/skills/velin-dev-plan-first/SKILL.md +57 -0
  199. package/packages/velinstyle-skills/skills/velin-docs-component-guide/SKILL.md +57 -0
  200. package/packages/velinstyle-skills/skills/velin-docs-generate-api/SKILL.md +57 -0
  201. package/packages/velinstyle-skills/skills/velin-dx-doctor-setup/SKILL.md +57 -0
  202. package/packages/velinstyle-skills/skills/velin-dx-project-init/SKILL.md +57 -0
  203. package/packages/velinstyle-skills/skills/velin-migration-legacy-wc/SKILL.md +57 -0
  204. package/packages/velinstyle-skills/skills/velin-migration-prefix-classes/SKILL.md +57 -0
  205. package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +57 -0
  206. package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +57 -0
  207. package/packages/velinstyle-skills/skills/velin-perf-audit-fix/SKILL.md +57 -0
  208. package/packages/velinstyle-skills/skills/velin-perf-runtime-tree-shake/SKILL.md +57 -0
  209. package/packages/velinstyle-skills/skills/velin-release-changelog-sync/SKILL.md +57 -0
  210. package/packages/velinstyle-skills/skills/velin-release-meta-rebuild/SKILL.md +57 -0
  211. package/packages/velinstyle-skills/skills/velin-review-conversion-hero/SKILL.md +57 -0
  212. package/packages/velinstyle-skills/skills/velin-review-page-gate/SKILL.md +57 -0
  213. package/packages/velinstyle-skills/skills/velin-review-seo-meta/SKILL.md +57 -0
  214. package/packages/velinstyle-skills/skills/velin-security-sanitize-html/SKILL.md +57 -0
  215. package/packages/velinstyle-skills/skills/velin-security-scan-pii/SKILL.md +57 -0
  216. package/packages/velinstyle-skills/skills/velin-testing-a11y-suite/SKILL.md +57 -0
  217. package/packages/velinstyle-skills/skills/velin-testing-visual-smoke/SKILL.md +57 -0
  218. package/packages/velinstyle-skills/skills/velin-themes-apply-token/SKILL.md +57 -0
  219. package/packages/velinstyle-skills/skills/velin-themes-contrast-check/SKILL.md +57 -0
  220. package/packages/velinstyle-skills/skills/velin-utilities-layout-apply/SKILL.md +57 -0
  221. package/packages/velinstyle-skills/skills/velin-utilities-prefix-fix/SKILL.md +57 -0
  222. package/packages/velinstyle-skills/skills/velin-utilities-token-map/SKILL.md +57 -0
  223. package/packages/velinstyle-skills/templates/admin-panel.template.yaml +12 -0
  224. package/packages/velinstyle-skills/templates/blog.template.yaml +12 -0
  225. package/packages/velinstyle-skills/templates/documentation-portal.template.yaml +12 -0
  226. package/packages/velinstyle-skills/templates/ecommerce-shop.template.yaml +13 -0
  227. package/packages/velinstyle-skills/templates/landing-page.template.yaml +12 -0
  228. package/packages/velinstyle-skills/templates/laravel-dashboard.template.yaml +11 -0
  229. package/packages/velinstyle-skills/templates/marketing-website.template.yaml +13 -0
  230. package/packages/velinstyle-skills/templates/portfolio.template.yaml +12 -0
  231. package/schemas/component-knowledge.schema.json +33 -0
  232. package/schemas/design-constraint.schema.json +15 -0
  233. package/schemas/design-intelligence.schema.json +19 -0
  234. package/schemas/design-rule.schema.json +19 -0
  235. package/schemas/page-template.schema.json +24 -0
  236. package/schemas/review-report.schema.json +36 -0
  237. package/schemas/section.schema.json +22 -0
  238. package/schemas/skill-bundle.schema.json +15 -0
  239. package/schemas/skill-graph.schema.json +25 -0
  240. package/schemas/skill-pack.schema.json +14 -0
  241. package/schemas/skill-project.schema.json +16 -0
  242. package/schemas/skill-record.schema.json +27 -0
  243. package/schemas/skill-registry.schema.json +17 -0
  244. package/schemas/skill-template.schema.json +17 -0
  245. package/schemas/token-graph.schema.json +27 -0
  246. package/schemas/utility.schema.json +166 -0
  247. package/src/base/wc-placeholder.css +7 -0
  248. package/src/components/alert.css +22 -1
  249. package/src/components/calendar-dropzone.css +5 -0
  250. package/src/components/data-table.css +93 -0
  251. package/src/components/form-validation.css +40 -0
  252. package/src/layout/app-shell.css +33 -0
  253. package/src/tokens/shadow.css +13 -0
  254. package/src/utilities/border.css +2 -0
  255. package/src/utilities/display.css +1 -24
  256. package/src/utilities/responsive.css +16 -1
  257. package/src/utilities/spacing.css +32 -0
  258. package/src/velinstyle.css +3 -0
  259. package/dist/chunks/attributes-3BZLI7XF.js +0 -364
  260. package/dist/chunks/attributes-4N5JP4CG.js +0 -364
  261. package/dist/chunks/attributes-AIR7SVLK.js +0 -364
  262. package/dist/chunks/attributes-BGRN5XZO.js +0 -364
  263. package/dist/chunks/attributes-BVSQAYLR.js +0 -364
  264. package/dist/chunks/attributes-CDYFUY7Y.js +0 -364
  265. package/dist/chunks/attributes-JMYJZBPE.js +0 -364
  266. package/dist/chunks/attributes-R5JY3X4O.js +0 -374
  267. package/dist/chunks/attributes-TNSPQTHX.js +0 -374
  268. package/dist/chunks/chunk-2354NEBP.js +0 -109
  269. package/dist/chunks/chunk-2VNA7G7S.js +0 -109
  270. package/dist/chunks/chunk-AQLRJSGG.js +0 -109
  271. package/dist/chunks/chunk-EWPSDMQT.js +0 -109
  272. package/dist/chunks/chunk-LA7264K4.js +0 -85
  273. package/dist/chunks/chunk-QTJJ3DPO.js +0 -109
  274. package/dist/chunks/chunk-RZQVWFHW.js +0 -109
  275. package/dist/chunks/chunk-U4XHMZ7D.js +0 -109
  276. package/dist/chunks/chunk-V5YIUA6G.js +0 -109
  277. package/dist/chunks/chunk-ZPSPKUYD.js +0 -109
  278. package/dist/chunks/velin-bottom-nav-N3DFQR66.js +0 -86
  279. package/dist/chunks/velin-carousel-Y5VCOABZ.js +0 -213
  280. package/dist/chunks/velin-code-block-ASUBTAQV.js +0 -119
  281. package/dist/chunks/velin-code-block-Y6CM726Q.js +0 -119
  282. package/dist/chunks/velin-combobox-2KDTCQ7N.js +0 -155
  283. package/dist/chunks/velin-command-M7RNHUPR.js +0 -136
  284. package/dist/chunks/velin-copy-T5KDTDXP.js +0 -81
  285. package/dist/chunks/velin-countdown-5XSTN3BH.js +0 -101
  286. package/dist/chunks/velin-counter-ISCTEAHI.js +0 -118
  287. package/dist/chunks/velin-dialog-D2GUHSCD.js +0 -144
  288. package/dist/chunks/velin-dialog-IJLFDXAJ.js +0 -144
  289. package/dist/chunks/velin-dialog-TNS3WXMB.js +0 -144
  290. package/dist/chunks/velin-drawer-MJS6WCF7.js +0 -130
  291. package/dist/chunks/velin-email-N5B5ZYTE.js +0 -90
  292. package/dist/chunks/velin-icon-KZO3IEHG.js +0 -164
  293. package/dist/chunks/velin-lightbox-QOTB4PRL.js +0 -140
  294. package/dist/chunks/velin-lightbox-UCD6N7HF.js +0 -140
  295. package/dist/chunks/velin-live-dot-XHUZCIDZ.js +0 -67
  296. package/dist/chunks/velin-menubar-SFEXJE56.js +0 -86
  297. package/dist/chunks/velin-modal-2HOBXE6F.js +0 -176
  298. package/dist/chunks/velin-modal-GLI2XZZB.js +0 -176
  299. package/dist/chunks/velin-popover-EXINRIGJ.js +0 -145
  300. package/dist/chunks/velin-popover-OXX7MRZ6.js +0 -145
  301. package/dist/chunks/velin-popover-YKCCROJG.js +0 -145
  302. package/dist/chunks/velin-rating-XRQJV4LO.js +0 -91
  303. package/dist/chunks/velin-scroll-top-F2FCZNYD.js +0 -58
  304. package/dist/chunks/velin-search-4YHJWYSS.js +0 -556
  305. package/dist/chunks/velin-search-LWHY23Q5.js +0 -474
  306. package/dist/chunks/velin-secure-field-H7FSBUJW.js +0 -95
  307. package/dist/chunks/velin-segmented-control-PQX6LKOJ.js +0 -109
  308. package/dist/chunks/velin-sheet-3OGI4HD7.js +0 -119
  309. package/dist/chunks/velin-sparkline-GVEANJVW.js +0 -184
  310. package/dist/chunks/velin-sparkline-KDA3GQAN.js +0 -162
  311. package/dist/chunks/velin-stepper-2ZIHLKGM.js +0 -141
  312. package/dist/chunks/velin-stepper-FYEUEWE6.js +0 -141
  313. package/dist/chunks/velin-stepper-wc-QU3KRVQR.js +0 -133
  314. package/dist/chunks/velin-theme-toggle-BNNS7WLV.js +0 -285
  315. package/dist/chunks/velin-theme-toggle-NO7V4TWI.js +0 -285
  316. package/dist/chunks/velin-theme-toggle-QELCJM2Q.js +0 -291
  317. package/dist/chunks/velin-toast-RCZ4CSUC.js +0 -109
  318. package/dist/chunks/velin-toast-WEUBTJG6.js +0 -109
  319. package/dist/chunks/velin-tooltip-664IOHZA.js +0 -112
  320. package/dist/chunks/velin-tooltip-wc-LXDVT6DE.js +0 -103
  321. package/dist/chunks/worker-client-R6DBYPFT.js +0 -46
package/cli/meta.js CHANGED
@@ -29,6 +29,31 @@ export async function buildMeta(options = {}) {
29
29
  written.push(llmsFile);
30
30
  }
31
31
 
32
+ // JSON twins for agents (pages, sections, knowledge, constraints)
33
+ const twinDir = join(PKG_ROOT, 'docs', 'generated', 'intelligence');
34
+ mkdirSync(twinDir, { recursive: true });
35
+ const twins = {
36
+ 'pages.json': bundle.knowledgeGraph?.pages || [],
37
+ 'sections.json': bundle.knowledgeGraph?.sections || [],
38
+ 'components.json': bundle.knowledgeGraph?.components || [],
39
+ 'design-constraints.json': bundle.knowledgeGraph?.designConstraints || [],
40
+ 'index.json': {
41
+ version: bundle.framework.version,
42
+ generatedAt: bundle.generatedAt,
43
+ pageCount: bundle.knowledgeGraph?.pageCount || 0,
44
+ sectionCount: bundle.knowledgeGraph?.sectionCount || 0,
45
+ componentCount: bundle.knowledgeGraph?.componentCount || 0,
46
+ constraintCount: bundle.knowledgeGraph?.designConstraints?.length || 0,
47
+ agentBundle: 'dist/velin-agent.json',
48
+ llmsTxt: 'dist/llms.txt',
49
+ },
50
+ };
51
+ for (const [name, data] of Object.entries(twins)) {
52
+ const target = join(twinDir, name);
53
+ writeFileSync(target, JSON.stringify(data, null, 2), 'utf-8');
54
+ written.push(target);
55
+ }
56
+
32
57
  return { ok: true, bundle, written, count: written.length };
33
58
  }
34
59
 
@@ -43,7 +68,30 @@ export function metaPage(htmlPath, options = {}) {
43
68
  }
44
69
  const html = readFileSync(abs, 'utf-8');
45
70
  const rel = htmlPath.replace(/\\/g, '/');
46
- const meta = buildPageMeta(html, rel, PKG_ROOT);
71
+ let meta = buildPageMeta(html, rel, PKG_ROOT);
72
+
73
+ // Merge curated fields from existing velin-meta so --write does not clobber goals/intent.
74
+ const existingMatch = html.match(
75
+ /<script[^>]*id=["']velin-meta["'][^>]*>([\s\S]*?)<\/script>/i,
76
+ );
77
+ if (existingMatch) {
78
+ try {
79
+ const prev = JSON.parse(existingMatch[1]);
80
+ meta = mergePageMeta(prev, meta);
81
+ if (options.write && meta._droppedKeys?.length) {
82
+ console.warn(
83
+ `velin-meta --write: dropping non-allowlisted keys: ${meta._droppedKeys.join(', ')} ` +
84
+ `(kept: ${[...META_MERGE_ALLOWLIST].join(', ')})`,
85
+ );
86
+ }
87
+ delete meta._droppedKeys;
88
+ } catch {
89
+ /* keep generated meta */
90
+ }
91
+ } else {
92
+ delete meta._droppedKeys;
93
+ }
94
+
47
95
  const script = serializePageMetaScript(meta);
48
96
 
49
97
  if (options.write) {
@@ -63,6 +111,41 @@ export function metaPage(htmlPath, options = {}) {
63
111
  return { ok: true, meta, script };
64
112
  }
65
113
 
114
+ /** Keys preserved from previous velin-meta on --write (plus nested page.*). */
115
+ const META_MERGE_ALLOWLIST = new Set([
116
+ 'page', 'goals', 'intent', 'notes', 'curated', 'id', 'title',
117
+ ]);
118
+
119
+ /** Preserve curated page goals/intent while refreshing component inventory. */
120
+ function mergePageMeta(prev, next) {
121
+ const out = { ...next };
122
+ const dropped = [];
123
+ if (prev && typeof prev === 'object') {
124
+ for (const key of Object.keys(prev)) {
125
+ if (!META_MERGE_ALLOWLIST.has(key) && !(key in next)) {
126
+ dropped.push(key);
127
+ }
128
+ }
129
+ out.page = { ...(next.page || {}), ...(prev.page || {}), ...(next.page || {}) };
130
+ if (prev.page?.intent) out.page.intent = prev.page.intent;
131
+ if (prev.page?.goals) out.page.goals = prev.page.goals;
132
+ if (prev.page?.title) out.page.title = prev.page.title;
133
+ if (prev.page?.id) out.page.id = prev.page.id;
134
+ if (prev.goals) out.goals = prev.goals;
135
+ if (prev.intent) out.intent = prev.intent;
136
+ if (prev.notes) out.notes = prev.notes;
137
+ if (prev.curated) out.curated = prev.curated;
138
+ if (prev.id && !out.id) out.id = prev.id;
139
+ }
140
+ out.allowed = next.allowed;
141
+ out.attributes = next.attributes;
142
+ out.a11y = next.a11y;
143
+ out.version = next.version;
144
+ out.mime = next.mime;
145
+ out._droppedKeys = dropped;
146
+ return out;
147
+ }
148
+
66
149
  export async function metaMain(argv = process.argv.slice(2)) {
67
150
  const pageIdx = argv.indexOf('page');
68
151
  if (pageIdx !== -1 && argv[pageIdx + 1]) {
package/cli/perf-audit.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Performance audit for static HTML — images, scripts, inline styles.
3
3
  */
4
- import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
4
+ import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
5
5
  import { join, extname, relative } from 'path';
6
6
 
7
7
  const DEFAULT_IGNORE = ['node_modules', 'dist', '.git', '.next', 'build'];
@@ -18,6 +18,19 @@ export function walkHtmlFiles(dir, ignore = DEFAULT_IGNORE) {
18
18
  return results;
19
19
  }
20
20
 
21
+ /** Accept a single .html file or a directory. */
22
+ function collectHtmlFiles(targetPath, ignore = DEFAULT_IGNORE) {
23
+ if (!existsSync(targetPath)) return [];
24
+ const st = statSync(targetPath);
25
+ if (st.isFile()) {
26
+ return extname(targetPath).toLowerCase() === '.html' ? [targetPath] : [];
27
+ }
28
+ if (st.isDirectory()) {
29
+ return walkHtmlFiles(targetPath, ignore);
30
+ }
31
+ return [];
32
+ }
33
+
21
34
  function lineOf(content, index) {
22
35
  return content.slice(0, index).split('\n').length;
23
36
  }
@@ -118,7 +131,7 @@ export function auditHtml(html, meta = {}) {
118
131
  }
119
132
 
120
133
  export function auditPath(targetPath) {
121
- const files = walkHtmlFiles(targetPath);
134
+ const files = collectHtmlFiles(targetPath);
122
135
  const issues = [];
123
136
  for (const file of files) {
124
137
  const html = readFileSync(file, 'utf-8');
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Prompt Engine — plan before HTML.
3
+ * Order: Mensch → Framework → KI (page registry + section graphs).
4
+ */
5
+ import { readFileSync, existsSync } from 'fs';
6
+ import { dirname, join } from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { emitBlueprint } from './blueprint.js';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const ROOT = join(__dirname, '..');
12
+ const PKG_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8')).version;
13
+
14
+ function readJson(rel) {
15
+ const p = join(ROOT, rel);
16
+ if (!existsSync(p)) return null;
17
+ return JSON.parse(readFileSync(p, 'utf-8'));
18
+ }
19
+
20
+ function loadPages() {
21
+ return readJson('core/meta/pages/registry.json')?.pages || [];
22
+ }
23
+
24
+ function loadSections() {
25
+ return readJson('core/meta/sections/registry.json')?.sections || [];
26
+ }
27
+
28
+ function loadConstraintPack(id) {
29
+ const file = join(ROOT, 'core/meta/design-constraints', `${id}.json`);
30
+ if (!existsSync(file)) return null;
31
+ return JSON.parse(readFileSync(file, 'utf-8'));
32
+ }
33
+
34
+ function mapSectionToBlueprint(sectionId) {
35
+ const map = {
36
+ hero: 'hero-section',
37
+ 'trust-bar': 'trust-bar',
38
+ benefits: 'benefits-grid',
39
+ services: 'services-grid',
40
+ process: 'process-steps',
41
+ testimonials: 'testimonials',
42
+ pricing: 'pricing-table',
43
+ faq: 'faq-section',
44
+ contact: 'form-contact',
45
+ cta: 'cta-band',
46
+ footer: 'footer-simple',
47
+ navbar: 'navbar-header',
48
+ };
49
+ return map[sectionId] || sectionId;
50
+ }
51
+
52
+ /**
53
+ * @param {string} prompt
54
+ */
55
+ export function analyzePrompt(prompt) {
56
+ const lower = (prompt || '').toLowerCase();
57
+ const pages = loadPages();
58
+ let best = null;
59
+ let bestScore = 0;
60
+ for (const page of pages) {
61
+ let score = 0;
62
+ for (const kw of page.promptKeywords || []) {
63
+ if (lower.includes(kw.toLowerCase())) score += kw.length;
64
+ }
65
+ if (score > bestScore) {
66
+ bestScore = score;
67
+ best = page;
68
+ }
69
+ }
70
+
71
+ const audience =
72
+ /patient|clinic|healthcare|praxis/i.test(prompt) ? 'patients'
73
+ : /developer|docs|api/i.test(prompt) ? 'developers'
74
+ : /buyer|shop|store/i.test(prompt) ? 'buyers'
75
+ : 'prospects';
76
+
77
+ const conversionGoal =
78
+ /contact|beratung|consultation|anfrage|kontakt/i.test(prompt) ? 'contact form'
79
+ : /signup|trial|register/i.test(prompt) ? 'signup'
80
+ : best?.conversionGoal || 'lead';
81
+
82
+ return {
83
+ intent: best ? `page:${best.id}` : 'page:landing',
84
+ targetAudience: audience,
85
+ businessType: best?.businessType || 'general',
86
+ pageGoal: best?.pageGoal || 'explain offer and convert',
87
+ conversionGoal,
88
+ pageId: best?.id || 'landing',
89
+ confidence: bestScore > 0 ? 'high' : 'low',
90
+ };
91
+ }
92
+
93
+ /**
94
+ * @param {ReturnType<typeof analyzePrompt>} analysis
95
+ * @param {string} prompt
96
+ */
97
+ export function buildPlan(analysis, prompt) {
98
+ const pages = loadPages();
99
+ const sectionsMeta = loadSections();
100
+ const page = pages.find((p) => p.id === analysis.pageId) || pages.find((p) => p.id === 'landing');
101
+ const lower = (prompt || '').toLowerCase();
102
+
103
+ const sections = [...(page?.requiredSections || [])];
104
+ for (const opt of page?.optionalSections || []) {
105
+ if (opt === 'pricing' && /pric|tarif|abo/i.test(lower) && !sections.includes(opt)) {
106
+ sections.splice(Math.max(sections.length - 2, 0), 0, opt);
107
+ }
108
+ if (opt === 'trust-bar' && /trust|kunden|marken/i.test(lower) && !sections.includes(opt)) {
109
+ sections.splice(1, 0, opt);
110
+ }
111
+ if (opt === 'process' && /process|ablauf|how it works|schritte/i.test(lower) && !sections.includes(opt)) {
112
+ sections.splice(Math.min(4, sections.length), 0, opt);
113
+ }
114
+ if (opt === 'contact' && /contact|kontakt|formular/i.test(lower) && !sections.includes(opt)) {
115
+ sections.splice(Math.max(sections.length - 1, 0), 0, opt);
116
+ }
117
+ }
118
+
119
+ if (/kontakt|contact|formular|steuerberater|leistungen/i.test(lower) && !sections.includes('contact')) {
120
+ sections.splice(Math.max(sections.length - 1, 0), 0, 'contact');
121
+ }
122
+
123
+ const uniqueSections = [...new Set(sections)];
124
+ const resolved = uniqueSections.map((id) => {
125
+ const meta = sectionsMeta.find((s) => s.id === id);
126
+ return {
127
+ id,
128
+ role: meta?.role || id,
129
+ blueprint: mapSectionToBlueprint(id),
130
+ designRules: meta?.designRules || [],
131
+ a11yRules: meta?.a11yRules || [],
132
+ seoRules: meta?.seoRules || [],
133
+ };
134
+ });
135
+
136
+ const constraints = ['hero', 'faq', 'contact']
137
+ .map(loadConstraintPack)
138
+ .filter(Boolean);
139
+
140
+ return {
141
+ version: 1,
142
+ analysis,
143
+ page: {
144
+ id: page?.id,
145
+ name: page?.name,
146
+ seoIntent: page?.seoIntent,
147
+ defaultTone: page?.defaultTone,
148
+ recommendedComponents: page?.recommendedComponents || [],
149
+ },
150
+ sections: resolved,
151
+ layout: {
152
+ container: 'velin-container',
153
+ density: ['dashboard', 'crm', 'erp'].includes(page?.id) ? 'data-dense' : 'comfortable',
154
+ },
155
+ designConstraints: constraints,
156
+ accessibility: {
157
+ checklist: [
158
+ 'One H1',
159
+ 'Labeled form fields',
160
+ 'Focus-visible controls',
161
+ 'Respect prefers-reduced-motion',
162
+ ],
163
+ risks: page?.accessibilityRisks || [],
164
+ },
165
+ seo: {
166
+ intent: page?.seoIntent,
167
+ notes: ['Meaningful H1', 'Section H2s', 'Descriptive link text'],
168
+ },
169
+ warnings: analysis.confidence === 'low'
170
+ ? ['Low page-type confidence — defaulted to landing. Refine prompt with business type keywords.']
171
+ : [],
172
+ };
173
+ }
174
+
175
+ /**
176
+ * @param {object} plan
177
+ * @param {{ brand?: string, title?: string, cta?: string }} slots
178
+ */
179
+ export function renderPlan(plan, slots = {}) {
180
+ const parts = [];
181
+ const missing = [];
182
+ for (const section of plan.sections || []) {
183
+ const emitId = section.blueprint || mapSectionToBlueprint(section.id);
184
+ const r = emitBlueprint(emitId);
185
+ if (!r.ok) {
186
+ missing.push(`${section.id}: ${r.error}`);
187
+ continue;
188
+ }
189
+ parts.push(r.text);
190
+ }
191
+
192
+ let html = parts.join('\n\n');
193
+ if (slots.brand) html = html.replace(/\bBrand\b/g, slots.brand);
194
+ if (slots.title) html = html.replace(/>Headline</g, `>${slots.title}<`);
195
+ if (slots.cta) {
196
+ html = html.replace(/Primary CTA/g, slots.cta);
197
+ html = html.replace(/Request a consultation/g, slots.cta);
198
+ html = html.replace(/Kontakt aufnehmen/g, slots.cta);
199
+ }
200
+
201
+ const doc = `<!DOCTYPE html>
202
+ <html lang="de" data-velin-theme="light">
203
+ <head>
204
+ <meta charset="UTF-8">
205
+ <meta name="viewport" content="width=device-width, initial-scale=1">
206
+ <title>${slots.title || plan.page?.name || 'VelinStyle page'}</title>
207
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@birdapi/velinstyle@${PKG_VERSION}/dist/velinstyle.min.css">
208
+ </head>
209
+ <body>
210
+ ${html}
211
+ <script type="module">
212
+ import { bootFromDOM } from 'https://cdn.jsdelivr.net/npm/@birdapi/velinstyle@${PKG_VERSION}/+esm';
213
+ bootFromDOM();
214
+ </script>
215
+ </body>
216
+ </html>
217
+ `;
218
+
219
+ return { html: doc, missing };
220
+ }
221
+
222
+ /**
223
+ * @param {string} prompt
224
+ */
225
+ export function planFromPrompt(prompt) {
226
+ if (!prompt?.trim()) return { ok: false, error: 'Prompt is required.' };
227
+ const analysis = analyzePrompt(prompt);
228
+ const plan = buildPlan(analysis, prompt);
229
+ const slots = {
230
+ brand: 'Brand',
231
+ title: /steuerberater/i.test(prompt) ? 'Steuerberatung mit Klarheit' : 'Headline',
232
+ cta: /kontakt|consultation|beratung/i.test(prompt) ? 'Kontakt aufnehmen' : 'Primary CTA',
233
+ };
234
+ const rendered = renderPlan(plan, slots);
235
+ return {
236
+ ok: true,
237
+ analysis,
238
+ plan,
239
+ html: rendered.html,
240
+ missing: rendered.missing,
241
+ warnings: plan.warnings,
242
+ };
243
+ }
package/cli/review.js ADDED
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Review Engine — design / a11y / seo / performance / conversion / prompt score.
3
+ */
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { dirname, join } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const ROOT = join(__dirname, '..');
10
+
11
+ const PROFILES = new Set(['marketing', 'app', 'docs', 'fragment', 'ecommerce']);
12
+
13
+ function loadConstraint(id) {
14
+ const p = join(ROOT, 'core/meta/design-constraints', `${id}.json`);
15
+ if (!existsSync(p)) return null;
16
+ return JSON.parse(readFileSync(p, 'utf-8'));
17
+ }
18
+
19
+ /** Strip non-DOM noise so review heuristics do not count meta JSON or code demos. */
20
+ function stripNonRenderableHtml(html) {
21
+ return String(html || '')
22
+ .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
23
+ .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
24
+ .replace(/<velin-code-block\b[^>]*>[\s\S]*?<\/velin-code-block>/gi, ' ')
25
+ .replace(/<!--[\s\S]*?-->/g, ' ');
26
+ }
27
+
28
+ function visibleWordCount(html) {
29
+ const clean = stripNonRenderableHtml(html)
30
+ .replace(/<[^>]+>/g, ' ')
31
+ .replace(/&[a-z]+;/gi, ' ')
32
+ .replace(/\s+/g, ' ')
33
+ .trim();
34
+ if (!clean) return 0;
35
+ return clean.split(' ').filter(Boolean).length;
36
+ }
37
+
38
+ function detectProfile(html, ctx = {}) {
39
+ if (ctx.profile && PROFILES.has(ctx.profile)) return ctx.profile;
40
+ const intent = ctx.plan?.page?.intent || ctx.plan?.page?.id || '';
41
+ const metaIntent = html.match(/"intent"\s*:\s*"([^"]+)"/)?.[1] || '';
42
+ const blob = `${intent} ${metaIntent}`.toLowerCase();
43
+ if (/dashboard|crm|erp|admin|app-shell|auth|login/.test(blob)) return 'app';
44
+ if (/docs|documentation|guide/.test(blob)) return 'docs';
45
+ if (/shop|ecommerce|store|checkout|cart/.test(blob)) return 'ecommerce';
46
+ if (ctx.fragment) return 'fragment';
47
+ // Short recipe-like HTML without document chrome → fragment
48
+ if (!/<html\b/i.test(html) && !/<main\b/i.test(html)) return 'fragment';
49
+ return 'marketing';
50
+ }
51
+
52
+ /**
53
+ * Hero scope: explicit hero markers first, else prefix until common section ids.
54
+ * Counts only real <a>/<button> tags with primary/outline CTA classes.
55
+ */
56
+ function countHeroCtas(html) {
57
+ const clean = stripNonRenderableHtml(html);
58
+ let heroChunk = '';
59
+ const heroSection =
60
+ clean.match(/<section\b[^>]*(?:\bid=["']hero["']|data-velin-section=["']hero["'])[^>]*>[\s\S]*?<\/section>/i)
61
+ || clean.match(/<(?:header|section)\b[^>]*\bid=["']top["'][^>]*>[\s\S]*?<\/(?:header|section)>/i);
62
+ if (heroSection) {
63
+ heroChunk = heroSection[0];
64
+ } else {
65
+ heroChunk = clean.split(/id=["'](?:services|faq|testimonials)["']/i)[0] || clean.slice(0, 2000);
66
+ }
67
+ const ctaRe = /<(?:a|button)\b[^>]*\bvelin-btn--(?:primary|outline)\b[^>]*>/gi;
68
+ return [...heroChunk.matchAll(ctaRe)].length;
69
+ }
70
+
71
+ /**
72
+ * @param {string} html
73
+ * @param {{ plan?: object, prompt?: string, profile?: string, fragment?: boolean }} [ctx]
74
+ */
75
+ export function reviewHtml(html, ctx = {}) {
76
+ const issues = [];
77
+ const text = html || '';
78
+ const profile = detectProfile(text, ctx);
79
+ const isFragment = profile === 'fragment';
80
+
81
+ const isMarketing = profile === 'marketing' || profile === 'ecommerce';
82
+
83
+ const h1 = [...text.matchAll(/<h1\b/gi)];
84
+ if (!isFragment) {
85
+ if (h1.length === 0) {
86
+ const h1Fix = profile === 'app' || profile === 'docs'
87
+ ? 'Add a single page H1 in the primary content (header/main) — not necessarily a marketing hero.'
88
+ : 'Add a single page H1 in the hero.';
89
+ issues.push({ code: 'a11y.missing-h1', severity: 'error', message: 'Missing H1', fix: h1Fix });
90
+ } else if (h1.length > 1) {
91
+ issues.push({ code: 'a11y.multiple-h1', severity: 'warning', message: `Found ${h1.length} H1 elements`, fix: 'Keep exactly one H1.' });
92
+ }
93
+ }
94
+
95
+ if (!isFragment && !/<html\b[^>]*\blang=/i.test(text) && /<html\b/i.test(text)) {
96
+ issues.push({
97
+ code: 'a11y.missing-lang',
98
+ severity: 'error',
99
+ message: 'Missing lang on <html>',
100
+ fix: 'Add lang="en" (or your locale) on <html>.',
101
+ });
102
+ }
103
+
104
+ if (!isFragment) {
105
+ const imgs = [...text.matchAll(/<img\b[^>]*>/gi)];
106
+ for (const m of imgs) {
107
+ const tag = m[0];
108
+ if (/\brole=["']presentation["']/i.test(tag) || /\baria-hidden=["']true["']/i.test(tag)) continue;
109
+ if (!/\balt=/i.test(tag)) {
110
+ issues.push({
111
+ code: 'a11y.img-missing-alt',
112
+ severity: 'error',
113
+ message: 'Image without alt attribute',
114
+ fix: 'Add alt text (or alt="" for decorative images).',
115
+ });
116
+ break;
117
+ }
118
+ }
119
+ }
120
+
121
+ if (/<input\b/i.test(text) && !isFragment) {
122
+ const inputs = [...text.matchAll(/<input\b[^>]*>/gi)];
123
+ let unlabeled = 0;
124
+ for (const m of inputs) {
125
+ const tag = m[0];
126
+ if (/\btype=["']hidden["']/i.test(tag)) continue;
127
+ if (/\baria-label=/i.test(tag) || /\baria-labelledby=/i.test(tag) || /\bid=/i.test(tag)) continue;
128
+ if (/\bplaceholder=/i.test(tag)) unlabeled += 1;
129
+ }
130
+ if (unlabeled > 0 && !/<label\b/i.test(text)) {
131
+ issues.push({
132
+ code: 'a11y.unlabeled-input',
133
+ severity: 'error',
134
+ message: 'Input(s) without associated label',
135
+ fix: 'Wire <label for="…"> to each input id (or use aria-label).',
136
+ });
137
+ }
138
+ }
139
+
140
+ const ctaPrimary = [...text.matchAll(/velin-btn--primary/g)];
141
+ if (isMarketing && ctaPrimary.length > 4) {
142
+ issues.push({ code: 'design.too-many-primary-ctas', severity: 'warning', message: 'Many primary CTAs competing', fix: 'Limit primary CTAs; demote extras to outline/ghost.' });
143
+ }
144
+ if (/<form\b/i.test(text)) {
145
+ const skipFormSummary = profile === 'docs' && /type=["']search["']/i.test(text) && !/<textarea\b/i.test(text);
146
+ if (!skipFormSummary && !/velin-form-summary/i.test(text)) {
147
+ issues.push({ code: 'a11y.missing-form-summary', severity: 'error', message: 'Form without velin-form-summary', fix: 'Add <velin-form-summary> before fields.' });
148
+ }
149
+ if (/placeholder=/.test(text) && !/<label\b/i.test(text)) {
150
+ issues.push({ code: 'a11y.placeholder-only', severity: 'error', message: 'Possible placeholder-only fields', fix: 'Use visible <label> elements.' });
151
+ }
152
+ }
153
+
154
+ if (/id="faq"|FAQ/i.test(text) && !/<(details|velin-accordion)\b/i.test(text)) {
155
+ issues.push({ code: 'design.faq-not-disclosure', severity: 'warning', message: 'FAQ section without disclosure pattern', fix: 'Use velin-accordion or details/summary.' });
156
+ }
157
+
158
+ if (!isFragment && !/<title\b/i.test(text)) {
159
+ issues.push({ code: 'seo.missing-title', severity: 'warning', message: 'Missing document title', fix: 'Add a descriptive <title>.' });
160
+ }
161
+
162
+ if (/style="[^"]*background:[^"]*#|color:#[0-9a-f]{3,8}/i.test(text)) {
163
+ issues.push({ code: 'design.raw-hex', severity: 'warning', message: 'Raw hex colors detected', fix: 'Prefer --velin-color-* tokens.' });
164
+ }
165
+
166
+ if (/animation:\s*[^;]*infinite/i.test(text)) {
167
+ issues.push({ code: 'perf.infinite-animation', severity: 'warning', message: 'Infinite animation found', fix: 'Avoid infinite motion; respect prefers-reduced-motion.' });
168
+ }
169
+
170
+ // Thin / junk page floors (anti false-confidence)
171
+ const words = visibleWordCount(text);
172
+ const hasMain = /<main\b/i.test(text);
173
+ const mainEmpty = hasMain && /<main\b[^>]*>\s*<\/main>/i.test(text);
174
+ const thin = !isFragment && (words < 40 || !hasMain || mainEmpty || h1.length === 0);
175
+
176
+ if (!isFragment && words < 40) {
177
+ issues.push({
178
+ code: 'seo.thin-content',
179
+ severity: 'warning',
180
+ message: `Very little visible content (${words} words)`,
181
+ fix: 'Add meaningful page copy, headings, and sections.',
182
+ });
183
+ issues.push({
184
+ code: 'conversion.thin-page',
185
+ severity: 'warning',
186
+ message: 'Page is too thin to score conversion highly',
187
+ fix: 'Add a clear offer, proof, and primary CTA path.',
188
+ });
189
+ }
190
+ if (!isFragment && (!hasMain || mainEmpty)) {
191
+ issues.push({
192
+ code: 'seo.thin-content',
193
+ severity: 'warning',
194
+ message: hasMain ? 'Empty <main> landmark' : 'Missing <main> landmark',
195
+ fix: 'Wrap primary content in <main>.',
196
+ });
197
+ }
198
+
199
+ const plan = ctx.plan;
200
+ if (plan?.sections && isMarketing) {
201
+ const ids = plan.sections.map((s) => s.id);
202
+ for (const need of ['hero', 'footer']) {
203
+ if (!ids.includes(need) && /landing|lawyer|saas/i.test(plan.page?.id || '')) {
204
+ issues.push({ code: `plan.missing-${need}`, severity: 'error', message: `Plan missing section ${need}`, fix: `Include ${need} in page template.` });
205
+ }
206
+ }
207
+ }
208
+
209
+ if (ctx.prompt && /steuerberater|lawyer|tax advisor/i.test(ctx.prompt)) {
210
+ for (const need of ['services', 'testimonials', 'faq', 'contact']) {
211
+ if (plan && !plan.sections.some((s) => s.id === need)) {
212
+ issues.push({ code: `conversion.missing-${need}`, severity: 'error', message: `Professional-services page missing ${need}`, fix: `Add ${need} section.` });
213
+ }
214
+ }
215
+ }
216
+
217
+ const heroRules = loadConstraint('hero');
218
+ if (heroRules && isMarketing && /velin-btn--primary/g.test(text)) {
219
+ const heroCtas = countHeroCtas(text);
220
+ if (heroCtas > 2) {
221
+ issues.push({ code: 'design.hero-cta-max', severity: 'error', message: 'Hero exceeds 2 CTAs', fix: 'Keep at most two CTAs in the hero (real <a>/<button> only; scripts and code samples are ignored).' });
222
+ }
223
+ }
224
+
225
+ const errors = issues.filter((i) => i.severity === 'error').length;
226
+ const warnings = issues.filter((i) => i.severity === 'warning').length;
227
+
228
+ const score = (base, penalty) => Math.max(0, Math.round((base - penalty) * 10) / 10);
229
+
230
+ let scores = {
231
+ design: score(10, errors * 1.5 + warnings * 0.5),
232
+ accessibility: score(10, issues.filter((i) => i.code.startsWith('a11y')).length * 2),
233
+ seo: score(10, issues.filter((i) => i.code.startsWith('seo')).length * 2),
234
+ performance: score(10, issues.filter((i) => i.code.startsWith('perf')).length * 2),
235
+ conversion: score(10, issues.filter((i) => i.code.startsWith('conversion')).length * 2),
236
+ visual: score(9, issues.filter((i) => i.code.startsWith('design')).length * 1.2),
237
+ };
238
+
239
+ if (thin) {
240
+ // Cap soft categories so junk pages cannot claim 10/10
241
+ scores.seo = Math.min(scores.seo, 4);
242
+ scores.conversion = Math.min(scores.conversion, 4);
243
+ scores.design = Math.min(scores.design, 6);
244
+ scores.visual = Math.min(scores.visual, 5);
245
+ }
246
+
247
+ if (profile === 'app' || profile === 'docs') {
248
+ // Conversion/SEO marketing heuristics are not the primary signal for app/docs shells
249
+ scores.conversion = Math.min(scores.conversion, 6);
250
+ if (profile === 'app') scores.seo = Math.min(scores.seo, 7);
251
+ }
252
+
253
+ const avg = Object.values(scores).reduce((a, b) => a + b, 0) / Object.values(scores).length;
254
+ const promptScore = Math.max(0, Math.min(10, avg - (ctx.plan?.warnings?.length ? 1 : 0)));
255
+
256
+ const gate = errors > 0 ? 'fail' : warnings > 0 ? 'warn' : 'pass';
257
+
258
+ return {
259
+ version: 1,
260
+ profile,
261
+ promptScore,
262
+ scores,
263
+ issues,
264
+ gate,
265
+ };
266
+ }
267
+
268
+ export { PROFILES, detectProfile };