@birdapi/velinstyle 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +87 -158
- package/README.md +188 -115
- package/cli/blueprint.js +98 -5
- package/cli/blueprints/app-chrome.html +18 -0
- package/cli/blueprints/benefits-grid.html +9 -0
- package/cli/blueprints/bottom-nav-mobile.html +2 -1
- package/cli/blueprints/cookie-consent.html +1 -1
- package/cli/blueprints/cta-band.html +6 -0
- package/cli/blueprints/faq-section.html +18 -0
- package/cli/blueprints/filter-bar.html +4 -4
- package/cli/blueprints/form-contact.html +6 -5
- package/cli/blueprints/hero-section.html +3 -3
- package/cli/blueprints/notification-center.html +1 -1
- package/cli/blueprints/onboarding.html +2 -1
- package/cli/blueprints/ops-console.html +29 -0
- package/cli/blueprints/pricing-band.html +37 -0
- package/cli/blueprints/process-steps.html +10 -0
- package/cli/blueprints/services-grid.html +9 -0
- package/cli/blueprints/settings-panel.html +8 -6
- package/cli/blueprints/split-hero.html +25 -0
- package/cli/blueprints/testimonials.html +8 -0
- package/cli/blueprints/trust-bar.html +10 -0
- package/cli/cli-manifest.json +81 -17
- package/cli/contrast-utils.js +97 -0
- package/cli/create.js +370 -0
- package/cli/docgen/extract-a11y.js +4 -2
- package/cli/docgen/extract-attributes.js +126 -10
- package/cli/docgen/extract-cli.js +3 -2
- package/cli/docgen/extract-components.js +61 -2
- package/cli/docgen/extract-utilities.js +10 -10
- package/cli/doctor.js +119 -0
- package/cli/index.js +535 -46
- package/cli/meta.js +84 -1
- package/cli/perf-audit.js +15 -2
- package/cli/prompt-engine.js +243 -0
- package/cli/review.js +268 -0
- package/cli/scaffold.js +119 -4
- package/cli/scanner-rules-data.js +7 -0
- package/cli/scanner.js +357 -4
- package/cli/serve.js +81 -0
- package/cli/skills.js +223 -0
- package/cli/wc-api.js +89 -0
- package/cli/workflow.js +42 -0
- package/components/index.js +4 -0
- package/components/runtime/component-loaders.js +4 -0
- package/components/velin-accordion.js +62 -13
- package/components/velin-bottom-nav.js +10 -0
- package/components/velin-calendar.js +212 -0
- package/components/velin-command.js +18 -2
- package/components/velin-data-table.js +438 -0
- package/components/velin-dialog.js +4 -1
- package/components/velin-drawer.js +2 -4
- package/components/velin-file-dropzone.js +162 -0
- package/components/velin-form-summary.js +348 -0
- package/components/velin-icon.js +22 -1
- package/components/velin-lightbox.js +5 -3
- package/components/velin-modal.js +2 -4
- package/components/velin-search.js +1 -1
- package/components/velin-sheet.js +2 -4
- package/core/a11y/component-contracts.json +35 -1
- package/core/highlight/languages/go.js +28 -0
- package/core/highlight/languages/python.js +29 -0
- package/core/highlight/languages/rust.js +32 -0
- package/core/highlight/languages/yaml.js +23 -0
- package/core/highlight/registry.js +13 -2
- package/core/meta/build.js +126 -6
- package/core/meta/design-constraints/contact.json +9 -0
- package/core/meta/design-constraints/faq.json +9 -0
- package/core/meta/design-constraints/hero.json +13 -0
- package/core/meta/knowledge/components.json +622 -0
- package/core/meta/knowledge/tokens.json +282 -0
- package/core/meta/pages/registry.json +294 -0
- package/core/meta/schema.js +3 -1
- package/core/meta/sections/registry.json +185 -0
- package/dist/chunks/{attributes-TPANLMLF.js → attributes-A423NX5J.js} +3 -3
- package/dist/chunks/{attributes-ADOKLKA7.js → attributes-F6UPIEVP.js} +2 -2
- package/dist/chunks/{attributes-STYCS3O5.js → attributes-GLYXGGAL.js} +3 -3
- package/dist/chunks/{attributes-5Q2WXGRC.js → attributes-UFTYOSAU.js} +20 -3
- package/dist/chunks/attributes-XGLXLR2T.js +391 -0
- package/dist/chunks/attributes-YF4NI2QS.js +391 -0
- package/dist/chunks/browser-AUABBYUK.js +1544 -0
- package/dist/chunks/{chunk-SRL6EWHP.js → chunk-2LSKX57P.js} +13 -11
- package/dist/chunks/{chunk-RSMRR27R.js → chunk-2TGCVDUL.js} +11 -9
- package/dist/chunks/{chunk-XIET2VSZ.js → chunk-5JWGUAPC.js} +17 -13
- package/dist/chunks/chunk-7YSO2OQ2.js +113 -0
- package/dist/chunks/{chunk-IEHKRARD.js → chunk-BHR3ZG6U.js} +10 -8
- package/dist/chunks/{chunk-J472ERN2.js → chunk-ERF5YVP4.js} +15 -4
- package/dist/chunks/{chunk-XYLJ2GCB.js → chunk-JMSIHEYX.js} +55 -7
- package/dist/chunks/{chunk-MKZP2TBH.js → chunk-L7ZGUU4D.js} +2 -2
- package/dist/chunks/chunk-TUGIPKW5.js +113 -0
- package/dist/chunks/go-HZ6XTFCK.js +31 -0
- package/dist/chunks/{highlight-GJHHV4KM.js → highlight-IBDX4XWS.js} +1 -1
- package/dist/chunks/{highlight-XTHRKBA3.js → highlight-M2ELQNYK.js} +1 -1
- package/dist/chunks/python-BMPKDKNO.js +32 -0
- package/dist/chunks/runtime-entry.js +1 -1
- package/dist/chunks/rust-2BWKI2MN.js +35 -0
- package/dist/chunks/{velin-accordion-FBIPAEXG.js → velin-accordion-WSY6BMLA.js} +64 -10
- package/dist/chunks/{velin-bottom-nav-7GEMPIM6.js → velin-bottom-nav-5RVRNGIP.js} +25 -0
- package/dist/chunks/{velin-bottom-nav-2N2TYWOF.js → velin-bottom-nav-N5F4UO2I.js} +26 -1
- package/dist/chunks/velin-calendar-OFE6THUD.js +210 -0
- package/dist/chunks/velin-calendar-PGVNJTXK.js +217 -0
- package/dist/chunks/velin-calendar-XC7JRDSH.js +217 -0
- package/dist/chunks/{velin-code-block-CIAURDRJ.js → velin-code-block-ITCLIC6T.js} +1 -1
- package/dist/chunks/{velin-code-block-TSUKPY4C.js → velin-code-block-ZGWG7JI5.js} +12 -3
- package/dist/chunks/{velin-combobox-NXS4QB4R.js → velin-combobox-56V2ZWT5.js} +1 -1
- package/dist/chunks/{velin-command-ONKO2A3J.js → velin-command-2HO2EKR6.js} +29 -2
- package/dist/chunks/{velin-command-TMLSRGIU.js → velin-command-ZJSLPK7B.js} +30 -3
- package/dist/chunks/{velin-copy-NYDBOKXT.js → velin-copy-XDQSV2XU.js} +1 -1
- package/dist/chunks/{velin-countdown-4ZVDGK4R.js → velin-countdown-V4Z5MA63.js} +1 -1
- package/dist/chunks/velin-data-table-KK7IDGTP.js +302 -0
- package/dist/chunks/velin-data-table-UVR42UUM.js +372 -0
- package/dist/chunks/{velin-dialog-GTTZI564.js → velin-dialog-3YK2RPRV.js} +9 -2
- package/dist/chunks/{velin-dialog-ELFOZIXZ.js → velin-dialog-S24KAP4S.js} +3 -1
- package/dist/chunks/{velin-drawer-BE6TYN3H.js → velin-drawer-5PNU624J.js} +2 -4
- package/dist/chunks/{velin-drawer-DLL2GLT6.js → velin-drawer-CCM6S25D.js} +3 -5
- package/dist/chunks/{velin-email-RBX6ZR4S.js → velin-email-TNFONXPW.js} +1 -1
- package/dist/chunks/velin-file-dropzone-QRGF2JNA.js +154 -0
- package/dist/chunks/velin-file-dropzone-XIZMJ3NB.js +154 -0
- package/dist/chunks/velin-form-summary-XRQ7COQH.js +273 -0
- package/dist/chunks/{velin-icon-7M76DOPB.js → velin-icon-A6NCXGUK.js} +59 -17
- package/dist/chunks/{velin-icon-ZTSKUOVN.js → velin-icon-EC4B5XCM.js} +18 -5
- package/dist/chunks/{velin-lightbox-6E7CAATP.js → velin-lightbox-FFK4TUUP.js} +7 -6
- package/dist/chunks/{velin-lightbox-I75U6JF4.js → velin-lightbox-P725FLTY.js} +15 -5
- package/dist/chunks/{velin-menubar-J66JXRWW.js → velin-menubar-6I2LM5HL.js} +1 -1
- package/dist/chunks/{velin-modal-5HYNNNUI.js → velin-modal-ICTMGRSU.js} +7 -8
- package/dist/chunks/{velin-modal-AEOTZ67O.js → velin-modal-JBNODH7D.js} +2 -4
- package/dist/chunks/{velin-popover-T72HK3DF.js → velin-popover-WZG2GHBL.js} +25 -4
- package/dist/chunks/{velin-rating-N4DI2XNL.js → velin-rating-QBBYRRBU.js} +1 -1
- package/dist/chunks/{velin-search-7B4KJ6TX.js → velin-search-B3L6J2RP.js} +2 -2
- package/dist/chunks/{velin-search-2XRQFBCS.js → velin-search-L3UG6NDQ.js} +5 -4
- package/dist/chunks/{velin-secure-field-6PV7RZDJ.js → velin-secure-field-DGYMP7OK.js} +1 -1
- package/dist/chunks/{velin-segmented-control-GYD5BIXQ.js → velin-segmented-control-P74GJPAR.js} +1 -1
- package/dist/chunks/{velin-sheet-R2Y67X6H.js → velin-sheet-M5GZZD7G.js} +2 -4
- package/dist/chunks/{velin-sheet-OUK6572T.js → velin-sheet-X7RXIWKV.js} +3 -5
- package/dist/chunks/{velin-stepper-X54WGHDG.js → velin-stepper-4YQJUHPC.js} +12 -5
- package/dist/chunks/{velin-toast-OMMOA2DK.js → velin-toast-UG5LWTG2.js} +5 -1
- package/dist/chunks/{velin-tooltip-BDHGRTQB.js → velin-tooltip-ZDH5SDLU.js} +1 -1
- package/dist/chunks/yaml-O67LEQYT.js +26 -0
- package/dist/llms.txt +29 -7
- package/dist/search-index.json +182 -10
- package/dist/velin-agent.json +3997 -26
- package/dist/velinstyle-components.iife.js +1952 -287
- package/dist/velinstyle-components.js +1956 -287
- package/dist/velinstyle-components.min.js +267 -119
- package/dist/velinstyle.css +381 -67
- package/dist/velinstyle.d.ts +4 -0
- package/dist/velinstyle.min.css +1 -1
- package/package.json +26 -6
- package/packages/skill-engine/README.md +24 -0
- package/packages/skill-engine/package.json +7 -0
- package/packages/skill-engine/src/catalog.js +29 -0
- package/packages/skill-engine/src/index.js +5 -0
- package/packages/skill-engine/src/install.js +69 -0
- package/packages/skill-engine/src/registry.js +27 -0
- package/packages/skill-engine/src/resolve.js +68 -0
- package/packages/skill-engine/src/validate.js +40 -0
- package/packages/velinstyle-skills/README.md +6 -0
- package/packages/velinstyle-skills/adapter.json +8 -0
- package/packages/velinstyle-skills/bundles/landing-starter.bundle.yaml +12 -0
- package/packages/velinstyle-skills/bundles/review-suite.bundle.yaml +11 -0
- package/packages/velinstyle-skills/catalog.json +1004 -0
- package/packages/velinstyle-skills/graphs/component-ship.graph.yaml +14 -0
- package/packages/velinstyle-skills/graphs/landingpage.graph.yaml +21 -0
- package/packages/velinstyle-skills/graphs/release-gate.graph.yaml +15 -0
- package/packages/velinstyle-skills/index.js +22 -0
- package/packages/velinstyle-skills/package.json +7 -0
- package/packages/velinstyle-skills/packs/accessibility.pack.yaml +9 -0
- package/packages/velinstyle-skills/packs/design.pack.yaml +10 -0
- package/packages/velinstyle-skills/packs/documentation.pack.yaml +8 -0
- package/packages/velinstyle-skills/packs/enterprise.pack.yaml +10 -0
- package/packages/velinstyle-skills/packs/frontend.pack.yaml +10 -0
- package/packages/velinstyle-skills/packs/review.pack.yaml +9 -0
- package/packages/velinstyle-skills/projects/admin-panel.project.yaml +12 -0
- package/packages/velinstyle-skills/projects/blog.project.yaml +11 -0
- package/packages/velinstyle-skills/projects/documentation-portal.project.yaml +13 -0
- package/packages/velinstyle-skills/projects/ecommerce-shop.project.yaml +12 -0
- package/packages/velinstyle-skills/projects/landing-page.project.yaml +12 -0
- package/packages/velinstyle-skills/projects/laravel-dashboard.project.yaml +12 -0
- package/packages/velinstyle-skills/projects/marketing-website.project.yaml +14 -0
- package/packages/velinstyle-skills/projects/portfolio.project.yaml +12 -0
- package/packages/velinstyle-skills/registry.json +142 -0
- package/packages/velinstyle-skills/scripts/generate-assets.mjs +163 -0
- package/packages/velinstyle-skills/skills/velin-a11y-focus-keyboard/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-a11y-page-checklist/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-a11y-scan-fix/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-ai-agent-briefing/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-ai-plan-scaffold-review/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-ai-skill-authoring/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-components-accessibility-map/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-components-api-contract/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-components-recipe/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-components-select-compose/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-design-hero-compose/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-design-section-rhythm/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-design-surface-roles/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-dev-component-wire/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-dev-page-scaffold/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-dev-plan-first/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-docs-component-guide/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-docs-generate-api/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-dx-doctor-setup/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-dx-project-init/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-migration-legacy-wc/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-migration-prefix-classes/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-perf-audit-fix/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-perf-runtime-tree-shake/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-release-changelog-sync/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-release-meta-rebuild/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-review-conversion-hero/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-review-page-gate/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-review-seo-meta/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-security-sanitize-html/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-security-scan-pii/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-testing-a11y-suite/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-testing-visual-smoke/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-themes-apply-token/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-themes-contrast-check/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-utilities-layout-apply/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-utilities-prefix-fix/SKILL.md +57 -0
- package/packages/velinstyle-skills/skills/velin-utilities-token-map/SKILL.md +57 -0
- package/packages/velinstyle-skills/templates/admin-panel.template.yaml +12 -0
- package/packages/velinstyle-skills/templates/blog.template.yaml +12 -0
- package/packages/velinstyle-skills/templates/documentation-portal.template.yaml +12 -0
- package/packages/velinstyle-skills/templates/ecommerce-shop.template.yaml +13 -0
- package/packages/velinstyle-skills/templates/landing-page.template.yaml +12 -0
- package/packages/velinstyle-skills/templates/laravel-dashboard.template.yaml +11 -0
- package/packages/velinstyle-skills/templates/marketing-website.template.yaml +13 -0
- package/packages/velinstyle-skills/templates/portfolio.template.yaml +12 -0
- package/schemas/component-knowledge.schema.json +33 -0
- package/schemas/design-constraint.schema.json +15 -0
- package/schemas/design-intelligence.schema.json +19 -0
- package/schemas/design-rule.schema.json +19 -0
- package/schemas/page-template.schema.json +24 -0
- package/schemas/review-report.schema.json +36 -0
- package/schemas/section.schema.json +22 -0
- package/schemas/skill-bundle.schema.json +15 -0
- package/schemas/skill-graph.schema.json +25 -0
- package/schemas/skill-pack.schema.json +14 -0
- package/schemas/skill-project.schema.json +16 -0
- package/schemas/skill-record.schema.json +27 -0
- package/schemas/skill-registry.schema.json +17 -0
- package/schemas/skill-template.schema.json +17 -0
- package/schemas/token-graph.schema.json +27 -0
- package/schemas/utility.schema.json +166 -0
- package/src/base/wc-placeholder.css +7 -0
- package/src/components/alert.css +22 -1
- package/src/components/calendar-dropzone.css +5 -0
- package/src/components/data-table.css +93 -0
- package/src/components/form-validation.css +40 -0
- package/src/layout/app-shell.css +33 -0
- package/src/tokens/shadow.css +13 -0
- package/src/utilities/border.css +2 -0
- package/src/utilities/display.css +1 -24
- package/src/utilities/responsive.css +16 -1
- package/src/utilities/spacing.css +32 -0
- package/src/velinstyle.css +3 -0
- package/dist/chunks/attributes-3BZLI7XF.js +0 -364
- package/dist/chunks/attributes-4N5JP4CG.js +0 -364
- package/dist/chunks/attributes-AIR7SVLK.js +0 -364
- package/dist/chunks/attributes-BGRN5XZO.js +0 -364
- package/dist/chunks/attributes-BVSQAYLR.js +0 -364
- package/dist/chunks/attributes-CDYFUY7Y.js +0 -364
- package/dist/chunks/attributes-JMYJZBPE.js +0 -364
- package/dist/chunks/attributes-R5JY3X4O.js +0 -374
- package/dist/chunks/attributes-TNSPQTHX.js +0 -374
- package/dist/chunks/chunk-2354NEBP.js +0 -109
- package/dist/chunks/chunk-2VNA7G7S.js +0 -109
- package/dist/chunks/chunk-AQLRJSGG.js +0 -109
- package/dist/chunks/chunk-EWPSDMQT.js +0 -109
- package/dist/chunks/chunk-LA7264K4.js +0 -85
- package/dist/chunks/chunk-QTJJ3DPO.js +0 -109
- package/dist/chunks/chunk-RZQVWFHW.js +0 -109
- package/dist/chunks/chunk-U4XHMZ7D.js +0 -109
- package/dist/chunks/chunk-V5YIUA6G.js +0 -109
- package/dist/chunks/chunk-ZPSPKUYD.js +0 -109
- package/dist/chunks/velin-bottom-nav-N3DFQR66.js +0 -86
- package/dist/chunks/velin-carousel-Y5VCOABZ.js +0 -213
- package/dist/chunks/velin-code-block-ASUBTAQV.js +0 -119
- package/dist/chunks/velin-code-block-Y6CM726Q.js +0 -119
- package/dist/chunks/velin-combobox-2KDTCQ7N.js +0 -155
- package/dist/chunks/velin-command-M7RNHUPR.js +0 -136
- package/dist/chunks/velin-copy-T5KDTDXP.js +0 -81
- package/dist/chunks/velin-countdown-5XSTN3BH.js +0 -101
- package/dist/chunks/velin-counter-ISCTEAHI.js +0 -118
- package/dist/chunks/velin-dialog-D2GUHSCD.js +0 -144
- package/dist/chunks/velin-dialog-IJLFDXAJ.js +0 -144
- package/dist/chunks/velin-dialog-TNS3WXMB.js +0 -144
- package/dist/chunks/velin-drawer-MJS6WCF7.js +0 -130
- package/dist/chunks/velin-email-N5B5ZYTE.js +0 -90
- package/dist/chunks/velin-icon-KZO3IEHG.js +0 -164
- package/dist/chunks/velin-lightbox-QOTB4PRL.js +0 -140
- package/dist/chunks/velin-lightbox-UCD6N7HF.js +0 -140
- package/dist/chunks/velin-live-dot-XHUZCIDZ.js +0 -67
- package/dist/chunks/velin-menubar-SFEXJE56.js +0 -86
- package/dist/chunks/velin-modal-2HOBXE6F.js +0 -176
- package/dist/chunks/velin-modal-GLI2XZZB.js +0 -176
- package/dist/chunks/velin-popover-EXINRIGJ.js +0 -145
- package/dist/chunks/velin-popover-OXX7MRZ6.js +0 -145
- package/dist/chunks/velin-popover-YKCCROJG.js +0 -145
- package/dist/chunks/velin-rating-XRQJV4LO.js +0 -91
- package/dist/chunks/velin-scroll-top-F2FCZNYD.js +0 -58
- package/dist/chunks/velin-search-4YHJWYSS.js +0 -556
- package/dist/chunks/velin-search-LWHY23Q5.js +0 -474
- package/dist/chunks/velin-secure-field-H7FSBUJW.js +0 -95
- package/dist/chunks/velin-segmented-control-PQX6LKOJ.js +0 -109
- package/dist/chunks/velin-sheet-3OGI4HD7.js +0 -119
- package/dist/chunks/velin-sparkline-GVEANJVW.js +0 -184
- package/dist/chunks/velin-sparkline-KDA3GQAN.js +0 -162
- package/dist/chunks/velin-stepper-2ZIHLKGM.js +0 -141
- package/dist/chunks/velin-stepper-FYEUEWE6.js +0 -141
- package/dist/chunks/velin-stepper-wc-QU3KRVQR.js +0 -133
- package/dist/chunks/velin-theme-toggle-BNNS7WLV.js +0 -285
- package/dist/chunks/velin-theme-toggle-NO7V4TWI.js +0 -285
- package/dist/chunks/velin-theme-toggle-QELCJM2Q.js +0 -291
- package/dist/chunks/velin-toast-RCZ4CSUC.js +0 -109
- package/dist/chunks/velin-toast-WEUBTJG6.js +0 -109
- package/dist/chunks/velin-tooltip-664IOHZA.js +0 -112
- package/dist/chunks/velin-tooltip-wc-LXDVT6DE.js +0 -103
- 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
|
-
|
|
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 =
|
|
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 };
|