@birdapi/velinstyle 1.1.0 → 1.2.1

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