@birdapi/velinstyle 1.1.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 (308) hide show
  1. package/README.de.md +87 -162
  2. package/README.md +188 -119
  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-attributes.js +126 -10
  27. package/cli/docgen/extract-cli.js +3 -2
  28. package/cli/docgen/extract-components.js +61 -2
  29. package/cli/docgen/extract-utilities.js +10 -10
  30. package/cli/doctor.js +119 -0
  31. package/cli/index.js +531 -44
  32. package/cli/meta.js +84 -1
  33. package/cli/perf-audit.js +15 -2
  34. package/cli/prompt-engine.js +243 -0
  35. package/cli/review.js +268 -0
  36. package/cli/scaffold.js +119 -4
  37. package/cli/scanner-rules-data.js +7 -0
  38. package/cli/scanner.js +357 -4
  39. package/cli/serve.js +81 -0
  40. package/cli/skills.js +223 -0
  41. package/cli/wc-api.js +89 -0
  42. package/cli/workflow.js +42 -0
  43. package/components/index.js +2 -0
  44. package/components/runtime/component-loaders.js +2 -0
  45. package/components/velin-accordion.js +62 -13
  46. package/components/velin-bottom-nav.js +10 -0
  47. package/components/velin-calendar.js +212 -0
  48. package/components/velin-command.js +18 -2
  49. package/components/velin-data-table.js +78 -1
  50. package/components/velin-dialog.js +4 -1
  51. package/components/velin-drawer.js +2 -4
  52. package/components/velin-file-dropzone.js +162 -0
  53. package/components/velin-icon.js +22 -1
  54. package/components/velin-lightbox.js +5 -3
  55. package/components/velin-modal.js +2 -4
  56. package/components/velin-search.js +1 -1
  57. package/components/velin-sheet.js +2 -4
  58. package/core/a11y/component-contracts.json +20 -3
  59. package/core/meta/build.js +125 -5
  60. package/core/meta/design-constraints/contact.json +9 -0
  61. package/core/meta/design-constraints/faq.json +9 -0
  62. package/core/meta/design-constraints/hero.json +13 -0
  63. package/core/meta/knowledge/components.json +622 -0
  64. package/core/meta/knowledge/tokens.json +282 -0
  65. package/core/meta/pages/registry.json +294 -0
  66. package/core/meta/schema.js +3 -1
  67. package/core/meta/sections/registry.json +185 -0
  68. package/dist/chunks/{attributes-STYCS3O5.js → attributes-A423NX5J.js} +3 -3
  69. package/dist/chunks/{attributes-353XDOAX.js → attributes-F6UPIEVP.js} +1 -1
  70. package/dist/chunks/{attributes-ADOKLKA7.js → attributes-GLYXGGAL.js} +3 -3
  71. package/dist/chunks/{attributes-TPANLMLF.js → attributes-UFTYOSAU.js} +3 -3
  72. package/dist/chunks/attributes-XGLXLR2T.js +391 -0
  73. package/dist/chunks/attributes-YF4NI2QS.js +391 -0
  74. package/dist/chunks/browser-AUABBYUK.js +1544 -0
  75. package/dist/chunks/{chunk-RSMRR27R.js → chunk-2LSKX57P.js} +12 -10
  76. package/dist/chunks/{chunk-IEHKRARD.js → chunk-2TGCVDUL.js} +11 -9
  77. package/dist/chunks/{chunk-SRL6EWHP.js → chunk-5JWGUAPC.js} +17 -13
  78. package/dist/chunks/chunk-7YSO2OQ2.js +113 -0
  79. package/dist/chunks/{chunk-QVHYL3R4.js → chunk-BHR3ZG6U.js} +6 -6
  80. package/dist/chunks/{chunk-J472ERN2.js → chunk-JMSIHEYX.js} +15 -4
  81. package/dist/chunks/{chunk-MKZP2TBH.js → chunk-L7ZGUU4D.js} +2 -2
  82. package/dist/chunks/chunk-TUGIPKW5.js +113 -0
  83. package/dist/chunks/{highlight-GJHHV4KM.js → highlight-M2ELQNYK.js} +1 -1
  84. package/dist/chunks/runtime-entry.js +1 -1
  85. package/dist/chunks/{velin-accordion-FBIPAEXG.js → velin-accordion-WSY6BMLA.js} +64 -10
  86. package/dist/chunks/{velin-bottom-nav-7GEMPIM6.js → velin-bottom-nav-5RVRNGIP.js} +25 -0
  87. package/dist/chunks/{velin-bottom-nav-N3DFQR66.js → velin-bottom-nav-N5F4UO2I.js} +26 -1
  88. package/dist/chunks/velin-calendar-OFE6THUD.js +210 -0
  89. package/dist/chunks/velin-calendar-PGVNJTXK.js +217 -0
  90. package/dist/chunks/velin-calendar-XC7JRDSH.js +217 -0
  91. package/dist/chunks/{velin-code-block-CIAURDRJ.js → velin-code-block-ZGWG7JI5.js} +1 -1
  92. package/dist/chunks/{velin-combobox-NXS4QB4R.js → velin-combobox-56V2ZWT5.js} +1 -1
  93. package/dist/chunks/{velin-command-ONKO2A3J.js → velin-command-2HO2EKR6.js} +29 -2
  94. package/dist/chunks/{velin-command-TMLSRGIU.js → velin-command-ZJSLPK7B.js} +30 -3
  95. package/dist/chunks/{velin-copy-NYDBOKXT.js → velin-copy-XDQSV2XU.js} +1 -1
  96. package/dist/chunks/{velin-countdown-4ZVDGK4R.js → velin-countdown-V4Z5MA63.js} +1 -1
  97. package/dist/chunks/velin-data-table-UVR42UUM.js +372 -0
  98. package/dist/chunks/{velin-dialog-GTTZI564.js → velin-dialog-3YK2RPRV.js} +9 -2
  99. package/dist/chunks/{velin-dialog-ELFOZIXZ.js → velin-dialog-S24KAP4S.js} +3 -1
  100. package/dist/chunks/{velin-drawer-BE6TYN3H.js → velin-drawer-5PNU624J.js} +2 -4
  101. package/dist/chunks/{velin-drawer-DLL2GLT6.js → velin-drawer-CCM6S25D.js} +3 -5
  102. package/dist/chunks/{velin-email-RBX6ZR4S.js → velin-email-TNFONXPW.js} +1 -1
  103. package/dist/chunks/velin-file-dropzone-QRGF2JNA.js +154 -0
  104. package/dist/chunks/velin-file-dropzone-XIZMJ3NB.js +154 -0
  105. package/dist/chunks/{velin-icon-7M76DOPB.js → velin-icon-A6NCXGUK.js} +59 -17
  106. package/dist/chunks/{velin-icon-ZTSKUOVN.js → velin-icon-EC4B5XCM.js} +18 -5
  107. package/dist/chunks/{velin-lightbox-6E7CAATP.js → velin-lightbox-FFK4TUUP.js} +7 -6
  108. package/dist/chunks/{velin-lightbox-NXA3U2FM.js → velin-lightbox-P725FLTY.js} +5 -4
  109. package/dist/chunks/{velin-menubar-J66JXRWW.js → velin-menubar-6I2LM5HL.js} +1 -1
  110. package/dist/chunks/{velin-modal-5HYNNNUI.js → velin-modal-ICTMGRSU.js} +7 -8
  111. package/dist/chunks/{velin-modal-AEOTZ67O.js → velin-modal-JBNODH7D.js} +2 -4
  112. package/dist/chunks/{velin-popover-T72HK3DF.js → velin-popover-WZG2GHBL.js} +25 -4
  113. package/dist/chunks/{velin-rating-N4DI2XNL.js → velin-rating-QBBYRRBU.js} +1 -1
  114. package/dist/chunks/{velin-search-7B4KJ6TX.js → velin-search-B3L6J2RP.js} +2 -2
  115. package/dist/chunks/{velin-search-2XRQFBCS.js → velin-search-L3UG6NDQ.js} +5 -4
  116. package/dist/chunks/{velin-secure-field-6PV7RZDJ.js → velin-secure-field-DGYMP7OK.js} +1 -1
  117. package/dist/chunks/{velin-segmented-control-GYD5BIXQ.js → velin-segmented-control-P74GJPAR.js} +1 -1
  118. package/dist/chunks/{velin-sheet-R2Y67X6H.js → velin-sheet-M5GZZD7G.js} +2 -4
  119. package/dist/chunks/{velin-sheet-OUK6572T.js → velin-sheet-X7RXIWKV.js} +3 -5
  120. package/dist/chunks/{velin-stepper-X54WGHDG.js → velin-stepper-4YQJUHPC.js} +12 -5
  121. package/dist/chunks/{velin-toast-OMMOA2DK.js → velin-toast-UG5LWTG2.js} +5 -1
  122. package/dist/chunks/{velin-tooltip-BDHGRTQB.js → velin-tooltip-ZDH5SDLU.js} +1 -1
  123. package/dist/llms.txt +29 -7
  124. package/dist/search-index.json +156 -10
  125. package/dist/velin-agent.json +3973 -28
  126. package/dist/velinstyle-components.iife.js +1211 -284
  127. package/dist/velinstyle-components.js +1213 -284
  128. package/dist/velinstyle-components.min.js +266 -118
  129. package/dist/velinstyle.css +252 -67
  130. package/dist/velinstyle.d.ts +2 -0
  131. package/dist/velinstyle.min.css +1 -1
  132. package/package.json +20 -6
  133. package/packages/skill-engine/README.md +24 -0
  134. package/packages/skill-engine/package.json +7 -0
  135. package/packages/skill-engine/src/catalog.js +29 -0
  136. package/packages/skill-engine/src/index.js +5 -0
  137. package/packages/skill-engine/src/install.js +69 -0
  138. package/packages/skill-engine/src/registry.js +27 -0
  139. package/packages/skill-engine/src/resolve.js +68 -0
  140. package/packages/skill-engine/src/validate.js +40 -0
  141. package/packages/velinstyle-skills/README.md +6 -0
  142. package/packages/velinstyle-skills/adapter.json +8 -0
  143. package/packages/velinstyle-skills/bundles/landing-starter.bundle.yaml +12 -0
  144. package/packages/velinstyle-skills/bundles/review-suite.bundle.yaml +11 -0
  145. package/packages/velinstyle-skills/catalog.json +1004 -0
  146. package/packages/velinstyle-skills/graphs/component-ship.graph.yaml +14 -0
  147. package/packages/velinstyle-skills/graphs/landingpage.graph.yaml +21 -0
  148. package/packages/velinstyle-skills/graphs/release-gate.graph.yaml +15 -0
  149. package/packages/velinstyle-skills/index.js +22 -0
  150. package/packages/velinstyle-skills/package.json +7 -0
  151. package/packages/velinstyle-skills/packs/accessibility.pack.yaml +9 -0
  152. package/packages/velinstyle-skills/packs/design.pack.yaml +10 -0
  153. package/packages/velinstyle-skills/packs/documentation.pack.yaml +8 -0
  154. package/packages/velinstyle-skills/packs/enterprise.pack.yaml +10 -0
  155. package/packages/velinstyle-skills/packs/frontend.pack.yaml +10 -0
  156. package/packages/velinstyle-skills/packs/review.pack.yaml +9 -0
  157. package/packages/velinstyle-skills/projects/admin-panel.project.yaml +12 -0
  158. package/packages/velinstyle-skills/projects/blog.project.yaml +11 -0
  159. package/packages/velinstyle-skills/projects/documentation-portal.project.yaml +13 -0
  160. package/packages/velinstyle-skills/projects/ecommerce-shop.project.yaml +12 -0
  161. package/packages/velinstyle-skills/projects/landing-page.project.yaml +12 -0
  162. package/packages/velinstyle-skills/projects/laravel-dashboard.project.yaml +12 -0
  163. package/packages/velinstyle-skills/projects/marketing-website.project.yaml +14 -0
  164. package/packages/velinstyle-skills/projects/portfolio.project.yaml +12 -0
  165. package/packages/velinstyle-skills/registry.json +142 -0
  166. package/packages/velinstyle-skills/scripts/generate-assets.mjs +163 -0
  167. package/packages/velinstyle-skills/skills/velin-a11y-focus-keyboard/SKILL.md +57 -0
  168. package/packages/velinstyle-skills/skills/velin-a11y-page-checklist/SKILL.md +57 -0
  169. package/packages/velinstyle-skills/skills/velin-a11y-scan-fix/SKILL.md +57 -0
  170. package/packages/velinstyle-skills/skills/velin-ai-agent-briefing/SKILL.md +57 -0
  171. package/packages/velinstyle-skills/skills/velin-ai-plan-scaffold-review/SKILL.md +57 -0
  172. package/packages/velinstyle-skills/skills/velin-ai-skill-authoring/SKILL.md +57 -0
  173. package/packages/velinstyle-skills/skills/velin-components-accessibility-map/SKILL.md +57 -0
  174. package/packages/velinstyle-skills/skills/velin-components-api-contract/SKILL.md +57 -0
  175. package/packages/velinstyle-skills/skills/velin-components-recipe/SKILL.md +57 -0
  176. package/packages/velinstyle-skills/skills/velin-components-select-compose/SKILL.md +57 -0
  177. package/packages/velinstyle-skills/skills/velin-design-hero-compose/SKILL.md +57 -0
  178. package/packages/velinstyle-skills/skills/velin-design-section-rhythm/SKILL.md +57 -0
  179. package/packages/velinstyle-skills/skills/velin-design-surface-roles/SKILL.md +57 -0
  180. package/packages/velinstyle-skills/skills/velin-dev-component-wire/SKILL.md +57 -0
  181. package/packages/velinstyle-skills/skills/velin-dev-page-scaffold/SKILL.md +57 -0
  182. package/packages/velinstyle-skills/skills/velin-dev-plan-first/SKILL.md +57 -0
  183. package/packages/velinstyle-skills/skills/velin-docs-component-guide/SKILL.md +57 -0
  184. package/packages/velinstyle-skills/skills/velin-docs-generate-api/SKILL.md +57 -0
  185. package/packages/velinstyle-skills/skills/velin-dx-doctor-setup/SKILL.md +57 -0
  186. package/packages/velinstyle-skills/skills/velin-dx-project-init/SKILL.md +57 -0
  187. package/packages/velinstyle-skills/skills/velin-migration-legacy-wc/SKILL.md +57 -0
  188. package/packages/velinstyle-skills/skills/velin-migration-prefix-classes/SKILL.md +57 -0
  189. package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +57 -0
  190. package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +57 -0
  191. package/packages/velinstyle-skills/skills/velin-perf-audit-fix/SKILL.md +57 -0
  192. package/packages/velinstyle-skills/skills/velin-perf-runtime-tree-shake/SKILL.md +57 -0
  193. package/packages/velinstyle-skills/skills/velin-release-changelog-sync/SKILL.md +57 -0
  194. package/packages/velinstyle-skills/skills/velin-release-meta-rebuild/SKILL.md +57 -0
  195. package/packages/velinstyle-skills/skills/velin-review-conversion-hero/SKILL.md +57 -0
  196. package/packages/velinstyle-skills/skills/velin-review-page-gate/SKILL.md +57 -0
  197. package/packages/velinstyle-skills/skills/velin-review-seo-meta/SKILL.md +57 -0
  198. package/packages/velinstyle-skills/skills/velin-security-sanitize-html/SKILL.md +57 -0
  199. package/packages/velinstyle-skills/skills/velin-security-scan-pii/SKILL.md +57 -0
  200. package/packages/velinstyle-skills/skills/velin-testing-a11y-suite/SKILL.md +57 -0
  201. package/packages/velinstyle-skills/skills/velin-testing-visual-smoke/SKILL.md +57 -0
  202. package/packages/velinstyle-skills/skills/velin-themes-apply-token/SKILL.md +57 -0
  203. package/packages/velinstyle-skills/skills/velin-themes-contrast-check/SKILL.md +57 -0
  204. package/packages/velinstyle-skills/skills/velin-utilities-layout-apply/SKILL.md +57 -0
  205. package/packages/velinstyle-skills/skills/velin-utilities-prefix-fix/SKILL.md +57 -0
  206. package/packages/velinstyle-skills/skills/velin-utilities-token-map/SKILL.md +57 -0
  207. package/packages/velinstyle-skills/templates/admin-panel.template.yaml +12 -0
  208. package/packages/velinstyle-skills/templates/blog.template.yaml +12 -0
  209. package/packages/velinstyle-skills/templates/documentation-portal.template.yaml +12 -0
  210. package/packages/velinstyle-skills/templates/ecommerce-shop.template.yaml +13 -0
  211. package/packages/velinstyle-skills/templates/landing-page.template.yaml +12 -0
  212. package/packages/velinstyle-skills/templates/laravel-dashboard.template.yaml +11 -0
  213. package/packages/velinstyle-skills/templates/marketing-website.template.yaml +13 -0
  214. package/packages/velinstyle-skills/templates/portfolio.template.yaml +12 -0
  215. package/schemas/component-knowledge.schema.json +33 -0
  216. package/schemas/design-constraint.schema.json +15 -0
  217. package/schemas/design-intelligence.schema.json +19 -0
  218. package/schemas/design-rule.schema.json +19 -0
  219. package/schemas/page-template.schema.json +24 -0
  220. package/schemas/review-report.schema.json +36 -0
  221. package/schemas/section.schema.json +22 -0
  222. package/schemas/skill-bundle.schema.json +15 -0
  223. package/schemas/skill-graph.schema.json +25 -0
  224. package/schemas/skill-pack.schema.json +14 -0
  225. package/schemas/skill-project.schema.json +16 -0
  226. package/schemas/skill-record.schema.json +27 -0
  227. package/schemas/skill-registry.schema.json +17 -0
  228. package/schemas/skill-template.schema.json +17 -0
  229. package/schemas/token-graph.schema.json +27 -0
  230. package/schemas/utility.schema.json +166 -0
  231. package/src/components/alert.css +22 -1
  232. package/src/components/calendar-dropzone.css +5 -0
  233. package/src/layout/app-shell.css +33 -0
  234. package/src/tokens/shadow.css +13 -0
  235. package/src/utilities/border.css +2 -0
  236. package/src/utilities/display.css +1 -24
  237. package/src/utilities/responsive.css +16 -1
  238. package/src/utilities/spacing.css +32 -0
  239. package/src/velinstyle.css +2 -0
  240. package/dist/chunks/attributes-3BZLI7XF.js +0 -364
  241. package/dist/chunks/attributes-4N5JP4CG.js +0 -364
  242. package/dist/chunks/attributes-5Q2WXGRC.js +0 -374
  243. package/dist/chunks/attributes-AIR7SVLK.js +0 -364
  244. package/dist/chunks/attributes-BGRN5XZO.js +0 -364
  245. package/dist/chunks/attributes-BVSQAYLR.js +0 -364
  246. package/dist/chunks/attributes-CDYFUY7Y.js +0 -364
  247. package/dist/chunks/attributes-JMYJZBPE.js +0 -364
  248. package/dist/chunks/attributes-R5JY3X4O.js +0 -374
  249. package/dist/chunks/attributes-TNSPQTHX.js +0 -374
  250. package/dist/chunks/chunk-2354NEBP.js +0 -109
  251. package/dist/chunks/chunk-2VNA7G7S.js +0 -109
  252. package/dist/chunks/chunk-AQLRJSGG.js +0 -109
  253. package/dist/chunks/chunk-EWPSDMQT.js +0 -109
  254. package/dist/chunks/chunk-LA7264K4.js +0 -85
  255. package/dist/chunks/chunk-QTJJ3DPO.js +0 -109
  256. package/dist/chunks/chunk-RZQVWFHW.js +0 -109
  257. package/dist/chunks/chunk-U4XHMZ7D.js +0 -109
  258. package/dist/chunks/chunk-V5YIUA6G.js +0 -109
  259. package/dist/chunks/chunk-XIET2VSZ.js +0 -109
  260. package/dist/chunks/chunk-XYLJ2GCB.js +0 -205
  261. package/dist/chunks/chunk-ZPSPKUYD.js +0 -109
  262. package/dist/chunks/highlight-XTHRKBA3.js +0 -37
  263. package/dist/chunks/velin-bottom-nav-2N2TYWOF.js +0 -86
  264. package/dist/chunks/velin-carousel-Y5VCOABZ.js +0 -213
  265. package/dist/chunks/velin-code-block-ASUBTAQV.js +0 -119
  266. package/dist/chunks/velin-code-block-TSUKPY4C.js +0 -123
  267. package/dist/chunks/velin-code-block-Y6CM726Q.js +0 -119
  268. package/dist/chunks/velin-combobox-2KDTCQ7N.js +0 -155
  269. package/dist/chunks/velin-command-M7RNHUPR.js +0 -136
  270. package/dist/chunks/velin-copy-T5KDTDXP.js +0 -81
  271. package/dist/chunks/velin-countdown-5XSTN3BH.js +0 -101
  272. package/dist/chunks/velin-counter-ISCTEAHI.js +0 -118
  273. package/dist/chunks/velin-dialog-D2GUHSCD.js +0 -144
  274. package/dist/chunks/velin-dialog-IJLFDXAJ.js +0 -144
  275. package/dist/chunks/velin-dialog-TNS3WXMB.js +0 -144
  276. package/dist/chunks/velin-drawer-MJS6WCF7.js +0 -130
  277. package/dist/chunks/velin-email-N5B5ZYTE.js +0 -90
  278. package/dist/chunks/velin-icon-KZO3IEHG.js +0 -164
  279. package/dist/chunks/velin-lightbox-I75U6JF4.js +0 -140
  280. package/dist/chunks/velin-lightbox-QOTB4PRL.js +0 -140
  281. package/dist/chunks/velin-lightbox-UCD6N7HF.js +0 -140
  282. package/dist/chunks/velin-live-dot-XHUZCIDZ.js +0 -67
  283. package/dist/chunks/velin-menubar-SFEXJE56.js +0 -86
  284. package/dist/chunks/velin-modal-2HOBXE6F.js +0 -176
  285. package/dist/chunks/velin-modal-GLI2XZZB.js +0 -176
  286. package/dist/chunks/velin-popover-EXINRIGJ.js +0 -145
  287. package/dist/chunks/velin-popover-OXX7MRZ6.js +0 -145
  288. package/dist/chunks/velin-popover-YKCCROJG.js +0 -145
  289. package/dist/chunks/velin-rating-XRQJV4LO.js +0 -91
  290. package/dist/chunks/velin-scroll-top-F2FCZNYD.js +0 -58
  291. package/dist/chunks/velin-search-4YHJWYSS.js +0 -556
  292. package/dist/chunks/velin-search-LWHY23Q5.js +0 -474
  293. package/dist/chunks/velin-secure-field-H7FSBUJW.js +0 -95
  294. package/dist/chunks/velin-segmented-control-PQX6LKOJ.js +0 -109
  295. package/dist/chunks/velin-sheet-3OGI4HD7.js +0 -119
  296. package/dist/chunks/velin-sparkline-GVEANJVW.js +0 -184
  297. package/dist/chunks/velin-sparkline-KDA3GQAN.js +0 -162
  298. package/dist/chunks/velin-stepper-2ZIHLKGM.js +0 -141
  299. package/dist/chunks/velin-stepper-FYEUEWE6.js +0 -141
  300. package/dist/chunks/velin-stepper-wc-QU3KRVQR.js +0 -133
  301. package/dist/chunks/velin-theme-toggle-BNNS7WLV.js +0 -285
  302. package/dist/chunks/velin-theme-toggle-NO7V4TWI.js +0 -285
  303. package/dist/chunks/velin-theme-toggle-QELCJM2Q.js +0 -291
  304. package/dist/chunks/velin-toast-RCZ4CSUC.js +0 -109
  305. package/dist/chunks/velin-toast-WEUBTJG6.js +0 -109
  306. package/dist/chunks/velin-tooltip-664IOHZA.js +0 -112
  307. package/dist/chunks/velin-tooltip-wc-LXDVT6DE.js +0 -103
  308. 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 };