@birdapi/velinstyle 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.de.md +135 -246
  2. package/README.md +134 -245
  3. package/cli/blueprints/onboarding.html +2 -2
  4. package/cli/cli-manifest.json +97 -0
  5. package/cli/docgen/extract-a11y.js +89 -0
  6. package/cli/docgen/extract-attributes.js +95 -0
  7. package/cli/docgen/extract-cli.js +50 -0
  8. package/cli/docgen/extract-components.js +175 -0
  9. package/cli/docgen/extract-meta.js +32 -0
  10. package/cli/docgen/extract-rules.js +76 -0
  11. package/cli/docgen/extract-tokens.js +79 -0
  12. package/cli/docgen/extract-utilities.js +75 -0
  13. package/cli/docgen/markdown.js +43 -0
  14. package/cli/docs-generate.js +196 -0
  15. package/cli/index.js +121 -5
  16. package/cli/meta.js +97 -0
  17. package/cli/perf-audit.js +174 -0
  18. package/cli/pii-scanner.js +134 -0
  19. package/cli/scanner-rules-data.js +62 -0
  20. package/cli/scanner.js +68 -1
  21. package/cli/search-index.js +163 -0
  22. package/cli/tokens-build.js +21 -1
  23. package/cli/tokens-validate.js +69 -0
  24. package/components/a11y-entry.js +42 -0
  25. package/components/a11y-utils.js +71 -0
  26. package/components/focus-manager.js +45 -8
  27. package/components/index.js +22 -12
  28. package/components/runtime/component-loaders.js +43 -0
  29. package/components/runtime/index.js +77 -0
  30. package/components/runtime-entry.js +10 -0
  31. package/components/sanitize.js +135 -54
  32. package/components/velin-accordion.js +12 -0
  33. package/components/velin-bottom-nav.js +19 -0
  34. package/components/velin-carousel.js +2 -2
  35. package/components/velin-code-block.js +142 -0
  36. package/components/velin-command.js +15 -0
  37. package/components/velin-copy.js +18 -5
  38. package/components/velin-counter.js +3 -0
  39. package/components/velin-dialog.js +9 -1
  40. package/components/velin-email.js +94 -0
  41. package/components/velin-icon.js +65 -22
  42. package/components/velin-lightbox.js +8 -3
  43. package/components/velin-live-dot.js +5 -0
  44. package/components/velin-modal.js +6 -3
  45. package/components/velin-popover.js +34 -2
  46. package/components/velin-reveal.js +7 -65
  47. package/components/velin-scroll-top.js +1 -1
  48. package/components/velin-search.js +267 -0
  49. package/components/velin-secure-field.js +99 -0
  50. package/components/velin-sparkline.js +27 -7
  51. package/components/{velin-stepper-wc.js → velin-stepper.js} +37 -8
  52. package/components/velin-theme-toggle.js +12 -5
  53. package/components/velin-toast.js +4 -0
  54. package/components/{velin-tooltip-wc.js → velin-tooltip.js} +22 -3
  55. package/core/a11y/component-contracts.json +221 -0
  56. package/core/attributes/index.js +6 -0
  57. package/core/attributes/registry.js +268 -0
  58. package/core/highlight/highlight.js +114 -0
  59. package/core/highlight/index.js +29 -0
  60. package/core/highlight/languages/_utils.js +52 -0
  61. package/core/highlight/languages/blade.js +16 -0
  62. package/core/highlight/languages/css.js +26 -0
  63. package/core/highlight/languages/html.js +17 -0
  64. package/core/highlight/languages/js.js +58 -0
  65. package/core/highlight/languages/json.js +14 -0
  66. package/core/highlight/languages/markdown.js +16 -0
  67. package/core/highlight/languages/php.js +39 -0
  68. package/core/highlight/languages/plain.js +7 -0
  69. package/core/highlight/languages/shell.js +33 -0
  70. package/core/highlight/languages/sql.js +20 -0
  71. package/core/highlight/languages/typescript.js +28 -0
  72. package/core/highlight/observe.js +53 -0
  73. package/core/highlight/registry.js +107 -0
  74. package/core/highlight/render.js +35 -0
  75. package/core/highlight/types.js +28 -0
  76. package/core/meta/build.js +293 -0
  77. package/core/meta/index.js +17 -0
  78. package/core/meta/schema.js +49 -0
  79. package/core/motion/effects.js +54 -0
  80. package/core/motion/index.js +76 -0
  81. package/core/motion/scheduler.js +72 -0
  82. package/core/motion/scroll.js +32 -0
  83. package/core/motion/stagger.js +24 -0
  84. package/core/search/docs-url.js +96 -0
  85. package/core/search/engine.js +139 -0
  86. package/core/search/highlight.js +48 -0
  87. package/core/search/index.js +126 -0
  88. package/core/search/providers.js +36 -0
  89. package/core/search/types.js +61 -0
  90. package/core/search/worker-client.js +56 -0
  91. package/core/search/worker.js +20 -0
  92. package/dist/chunks/a11y-utils-IRC7LOPF.js +16 -0
  93. package/dist/chunks/attributes-3BZLI7XF.js +364 -0
  94. package/dist/chunks/attributes-4N5JP4CG.js +364 -0
  95. package/dist/chunks/attributes-5Q2WXGRC.js +374 -0
  96. package/dist/chunks/attributes-AIR7SVLK.js +364 -0
  97. package/dist/chunks/attributes-BGRN5XZO.js +364 -0
  98. package/dist/chunks/attributes-BVSQAYLR.js +364 -0
  99. package/dist/chunks/attributes-CDYFUY7Y.js +364 -0
  100. package/dist/chunks/attributes-JMYJZBPE.js +364 -0
  101. package/dist/chunks/attributes-R5JY3X4O.js +374 -0
  102. package/dist/chunks/attributes-STYCS3O5.js +391 -0
  103. package/dist/chunks/attributes-TNSPQTHX.js +374 -0
  104. package/dist/chunks/attributes-TPANLMLF.js +391 -0
  105. package/dist/chunks/blade-LIQLVKZQ.js +19 -0
  106. package/dist/chunks/browser-MRKYZPEH.js +1149 -0
  107. package/dist/chunks/chunk-2354NEBP.js +109 -0
  108. package/dist/chunks/chunk-2VNA7G7S.js +109 -0
  109. package/dist/chunks/chunk-42VVPW6N.js +132 -0
  110. package/dist/chunks/chunk-7FFAMRFV.js +44 -0
  111. package/dist/chunks/chunk-7MCGPHVC.js +69 -0
  112. package/dist/chunks/chunk-AQLRJSGG.js +109 -0
  113. package/dist/chunks/chunk-EWPSDMQT.js +109 -0
  114. package/dist/chunks/chunk-F7OLHTGX.js +22 -0
  115. package/dist/chunks/chunk-J472ERN2.js +242 -0
  116. package/dist/chunks/chunk-LA7264K4.js +85 -0
  117. package/dist/chunks/chunk-M5CHQVP6.js +61 -0
  118. package/dist/chunks/chunk-MKZP2TBH.js +89 -0
  119. package/dist/chunks/chunk-QTJJ3DPO.js +109 -0
  120. package/dist/chunks/chunk-RDE3QE7Z.js +89 -0
  121. package/dist/chunks/chunk-RSMRR27R.js +109 -0
  122. package/dist/chunks/chunk-RZQVWFHW.js +109 -0
  123. package/dist/chunks/chunk-SRL6EWHP.js +109 -0
  124. package/dist/chunks/chunk-U4XHMZ7D.js +109 -0
  125. package/dist/chunks/chunk-V5YIUA6G.js +109 -0
  126. package/dist/chunks/chunk-XIET2VSZ.js +109 -0
  127. package/dist/chunks/chunk-XYLJ2GCB.js +205 -0
  128. package/dist/chunks/chunk-Z4JYQ2XX.js +51 -0
  129. package/dist/chunks/chunk-ZPSPKUYD.js +109 -0
  130. package/dist/chunks/css-MQ5YU77P.js +28 -0
  131. package/dist/chunks/highlight-GJHHV4KM.js +37 -0
  132. package/dist/chunks/highlight-XTHRKBA3.js +37 -0
  133. package/dist/chunks/html-ZODC4DLQ.js +20 -0
  134. package/dist/chunks/js-MNSNYTJU.js +7 -0
  135. package/dist/chunks/json-VEM2HFH3.js +17 -0
  136. package/dist/chunks/markdown-E7OPGS3S.js +19 -0
  137. package/dist/chunks/php-MLL7T3LT.js +41 -0
  138. package/dist/chunks/plain-LJJDC5TN.js +8 -0
  139. package/dist/chunks/runtime-entry.js +14 -0
  140. package/dist/chunks/shell-QTRK4U4E.js +35 -0
  141. package/dist/chunks/sql-PPWLHGJ2.js +23 -0
  142. package/dist/chunks/typescript-JT3CCYWL.js +30 -0
  143. package/dist/chunks/velin-accordion-FBIPAEXG.js +105 -0
  144. package/dist/chunks/velin-accordion-QAQO7BI7.js +117 -0
  145. package/dist/chunks/velin-announcer-PVTXXCWR.js +33 -0
  146. package/dist/chunks/velin-bottom-nav-2N2TYWOF.js +86 -0
  147. package/dist/chunks/velin-bottom-nav-7GEMPIM6.js +86 -0
  148. package/dist/chunks/velin-bottom-nav-CTU6FFZV.js +101 -0
  149. package/dist/chunks/velin-bottom-nav-N3DFQR66.js +86 -0
  150. package/dist/chunks/velin-carousel-Y5VCOABZ.js +213 -0
  151. package/dist/chunks/velin-carousel-ZTBANFQQ.js +213 -0
  152. package/dist/chunks/velin-code-block-ASUBTAQV.js +119 -0
  153. package/dist/chunks/velin-code-block-CIAURDRJ.js +132 -0
  154. package/dist/chunks/velin-code-block-TSUKPY4C.js +123 -0
  155. package/dist/chunks/velin-code-block-Y6CM726Q.js +119 -0
  156. package/dist/chunks/velin-collapse-5XYBUIID.js +80 -0
  157. package/dist/chunks/velin-combobox-2KDTCQ7N.js +155 -0
  158. package/dist/chunks/velin-combobox-NXS4QB4R.js +155 -0
  159. package/dist/chunks/velin-combobox-UGA56HP3.js +155 -0
  160. package/dist/chunks/velin-command-CKDNO4BW.js +148 -0
  161. package/dist/chunks/velin-command-M7RNHUPR.js +136 -0
  162. package/dist/chunks/velin-command-ONKO2A3J.js +136 -0
  163. package/dist/chunks/velin-command-TMLSRGIU.js +136 -0
  164. package/dist/chunks/velin-copy-G2TZ5YBB.js +81 -0
  165. package/dist/chunks/velin-copy-NYDBOKXT.js +81 -0
  166. package/dist/chunks/velin-copy-T5KDTDXP.js +81 -0
  167. package/dist/chunks/velin-countdown-4ZVDGK4R.js +101 -0
  168. package/dist/chunks/velin-countdown-5XSTN3BH.js +101 -0
  169. package/dist/chunks/velin-countdown-7AIY66CU.js +101 -0
  170. package/dist/chunks/velin-counter-ISCTEAHI.js +118 -0
  171. package/dist/chunks/velin-counter-L5FYJF7A.js +121 -0
  172. package/dist/chunks/velin-dialog-D2GUHSCD.js +144 -0
  173. package/dist/chunks/velin-dialog-ELFOZIXZ.js +152 -0
  174. package/dist/chunks/velin-dialog-GTTZI564.js +147 -0
  175. package/dist/chunks/velin-dialog-IJLFDXAJ.js +144 -0
  176. package/dist/chunks/velin-dialog-TNS3WXMB.js +144 -0
  177. package/dist/chunks/velin-drawer-BE6TYN3H.js +130 -0
  178. package/dist/chunks/velin-drawer-DLL2GLT6.js +130 -0
  179. package/dist/chunks/velin-drawer-MJS6WCF7.js +130 -0
  180. package/dist/chunks/velin-dropdown-PLUIFA5M.js +172 -0
  181. package/dist/chunks/velin-email-N5B5ZYTE.js +90 -0
  182. package/dist/chunks/velin-email-NTM6DG3V.js +90 -0
  183. package/dist/chunks/velin-email-RBX6ZR4S.js +90 -0
  184. package/dist/chunks/velin-haptic-4QQTM3RK.js +50 -0
  185. package/dist/chunks/velin-icon-7M76DOPB.js +164 -0
  186. package/dist/chunks/velin-icon-KZO3IEHG.js +164 -0
  187. package/dist/chunks/velin-icon-WTFPG2DD.js +191 -0
  188. package/dist/chunks/velin-icon-ZTSKUOVN.js +193 -0
  189. package/dist/chunks/velin-lightbox-6E7CAATP.js +149 -0
  190. package/dist/chunks/velin-lightbox-I75U6JF4.js +140 -0
  191. package/dist/chunks/velin-lightbox-QOTB4PRL.js +140 -0
  192. package/dist/chunks/velin-lightbox-UCD6N7HF.js +140 -0
  193. package/dist/chunks/velin-live-dot-RJHX2DGY.js +74 -0
  194. package/dist/chunks/velin-live-dot-XHUZCIDZ.js +67 -0
  195. package/dist/chunks/velin-menubar-I426AED2.js +86 -0
  196. package/dist/chunks/velin-menubar-J66JXRWW.js +86 -0
  197. package/dist/chunks/velin-menubar-SFEXJE56.js +86 -0
  198. package/dist/chunks/velin-modal-2HOBXE6F.js +176 -0
  199. package/dist/chunks/velin-modal-5HYNNNUI.js +176 -0
  200. package/dist/chunks/velin-modal-AEOTZ67O.js +177 -0
  201. package/dist/chunks/velin-modal-GLI2XZZB.js +176 -0
  202. package/dist/chunks/velin-persist-DFPL3QP4.js +110 -0
  203. package/dist/chunks/velin-popover-EXINRIGJ.js +145 -0
  204. package/dist/chunks/velin-popover-HQW6Q2Y6.js +172 -0
  205. package/dist/chunks/velin-popover-OXX7MRZ6.js +145 -0
  206. package/dist/chunks/velin-popover-T72HK3DF.js +151 -0
  207. package/dist/chunks/velin-popover-YKCCROJG.js +145 -0
  208. package/dist/chunks/velin-progress-ring-E4YRLZHH.js +66 -0
  209. package/dist/chunks/velin-rating-6CDBI6DG.js +91 -0
  210. package/dist/chunks/velin-rating-N4DI2XNL.js +91 -0
  211. package/dist/chunks/velin-rating-XRQJV4LO.js +91 -0
  212. package/dist/chunks/velin-scroll-top-F2FCZNYD.js +58 -0
  213. package/dist/chunks/velin-scroll-top-OHYVVHHY.js +58 -0
  214. package/dist/chunks/velin-scrollspy-OKCT4FDV.js +50 -0
  215. package/dist/chunks/velin-search-2XRQFBCS.js +556 -0
  216. package/dist/chunks/velin-search-4YHJWYSS.js +556 -0
  217. package/dist/chunks/velin-search-7B4KJ6TX.js +557 -0
  218. package/dist/chunks/velin-search-LWHY23Q5.js +474 -0
  219. package/dist/chunks/velin-secure-field-6PV7RZDJ.js +95 -0
  220. package/dist/chunks/velin-secure-field-H7FSBUJW.js +95 -0
  221. package/dist/chunks/velin-secure-field-OFNAXCDP.js +95 -0
  222. package/dist/chunks/velin-segmented-control-EKL4E3BL.js +109 -0
  223. package/dist/chunks/velin-segmented-control-GYD5BIXQ.js +109 -0
  224. package/dist/chunks/velin-segmented-control-PQX6LKOJ.js +109 -0
  225. package/dist/chunks/velin-sheet-3OGI4HD7.js +119 -0
  226. package/dist/chunks/velin-sheet-OUK6572T.js +119 -0
  227. package/dist/chunks/velin-sheet-R2Y67X6H.js +119 -0
  228. package/dist/chunks/velin-sparkline-GVEANJVW.js +184 -0
  229. package/dist/chunks/velin-sparkline-KDA3GQAN.js +162 -0
  230. package/dist/chunks/velin-sparkline-XVTFC6GE.js +181 -0
  231. package/dist/chunks/velin-stepper-2ZIHLKGM.js +141 -0
  232. package/dist/chunks/velin-stepper-7ECRUYHD.js +148 -0
  233. package/dist/chunks/velin-stepper-FYEUEWE6.js +141 -0
  234. package/dist/chunks/velin-stepper-X54WGHDG.js +141 -0
  235. package/dist/chunks/velin-stepper-wc-QU3KRVQR.js +133 -0
  236. package/dist/chunks/velin-tabs-5UQ5ZVMV.js +138 -0
  237. package/dist/chunks/velin-theme-toggle-32J2CBNC.js +291 -0
  238. package/dist/chunks/velin-theme-toggle-BNNS7WLV.js +285 -0
  239. package/dist/chunks/velin-theme-toggle-NO7V4TWI.js +285 -0
  240. package/dist/chunks/velin-theme-toggle-QELCJM2Q.js +291 -0
  241. package/dist/chunks/velin-toast-OMMOA2DK.js +109 -0
  242. package/dist/chunks/velin-toast-RCZ4CSUC.js +109 -0
  243. package/dist/chunks/velin-toast-RSF2IHT3.js +113 -0
  244. package/dist/chunks/velin-toast-WEUBTJG6.js +109 -0
  245. package/dist/chunks/velin-tooltip-664IOHZA.js +112 -0
  246. package/dist/chunks/velin-tooltip-BDHGRTQB.js +112 -0
  247. package/dist/chunks/velin-tooltip-HVUOKNVX.js +112 -0
  248. package/dist/chunks/velin-tooltip-wc-LXDVT6DE.js +103 -0
  249. package/dist/chunks/worker-client-R6DBYPFT.js +46 -0
  250. package/dist/llms.test.txt +40 -0
  251. package/dist/llms.txt +40 -0
  252. package/dist/search-index.json +1773 -0
  253. package/dist/search-index.test.json +1773 -0
  254. package/dist/themes/brutalist.min.css +1 -1
  255. package/dist/themes/midnight.min.css +1 -1
  256. package/dist/themes/pastel.min.css +1 -1
  257. package/dist/themes/retro.min.css +1 -1
  258. package/dist/themes/sunset.min.css +1 -1
  259. package/dist/velin-agent.json +684 -0
  260. package/dist/velin-agent.test.json +684 -0
  261. package/dist/velinstyle-components.iife.js +7715 -3515
  262. package/dist/velinstyle-components.js +7087 -2859
  263. package/dist/velinstyle-components.min.js +196 -109
  264. package/dist/velinstyle.css +604 -65
  265. package/dist/velinstyle.d.ts +54 -0
  266. package/dist/velinstyle.min.css +1 -1
  267. package/examples/search-index.schema.json +33 -0
  268. package/package.json +56 -10
  269. package/src/a11y/authentication.css +37 -0
  270. package/src/a11y/consistent-help.css +22 -0
  271. package/src/a11y/dragging-alternatives.css +30 -0
  272. package/src/a11y/focus-appearance.css +20 -0
  273. package/src/a11y/high-contrast-aaa.css +16 -26
  274. package/src/a11y/skip-link.css +3 -1
  275. package/src/a11y/sr-only.css +1 -1
  276. package/src/base/reset.css +0 -36
  277. package/src/base/wc-placeholder.css +102 -0
  278. package/src/components/attributes.css +42 -0
  279. package/src/components/highlight.css +182 -0
  280. package/src/components/input-group.css +1 -2
  281. package/src/components/nav.css +2 -3
  282. package/src/components/search.css +95 -0
  283. package/src/components/stepper.css +2 -4
  284. package/src/components/timeline.css +1 -2
  285. package/src/components/tooltip.css +2 -4
  286. package/src/layout/container.css +5 -5
  287. package/src/layout/grid.css +8 -8
  288. package/src/layout/patterns.css +5 -5
  289. package/src/themes/brutalist.css +2 -11
  290. package/src/themes/corporate.css +0 -9
  291. package/src/themes/earth.css +0 -9
  292. package/src/themes/forest.css +0 -9
  293. package/src/themes/midnight.css +1 -10
  294. package/src/themes/neon.css +0 -9
  295. package/src/themes/nordic.css +0 -9
  296. package/src/themes/ocean.css +0 -9
  297. package/src/themes/pastel.css +1 -10
  298. package/src/themes/retro.css +2 -11
  299. package/src/themes/sharp.css +0 -7
  300. package/src/themes/soft.css +0 -7
  301. package/src/themes/sunset.css +1 -10
  302. package/src/tokens/color.css +45 -46
  303. package/src/tokens/fonts.css +10 -0
  304. package/src/tokens/typography.css +4 -5
  305. package/src/utilities/animation.css +2 -2
  306. package/src/utilities/display.css +3 -3
  307. package/src/utilities/divide.css +2 -6
  308. package/src/utilities/responsive.css +4 -4
  309. package/src/utilities/safe-area.css +1 -1
  310. package/src/utilities/scroll-animation.css +34 -0
  311. package/src/utilities/spacing.css +17 -8
  312. package/src/utilities/text.css +4 -1
  313. package/src/velinstyle.css +9 -0
@@ -0,0 +1,89 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { banner, heading, table } from './markdown.js';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const PKG_ROOT = join(__dirname, '..', '..');
8
+
9
+ /** @type {{ id: string, level: string, name: string, owner: string, status: string, testId: string }[]} */
10
+ export const WCAG_AAA_CRITERIA = [
11
+ { id: '1.3.1', level: 'A', name: 'Info and Relationships', owner: 'Author + CSS', status: 'partial', testId: 'scan-a11y' },
12
+ { id: '1.4.3', level: 'AA', name: 'Contrast (Minimum)', owner: 'Tokens', status: 'pass', testId: 'test:contrast' },
13
+ { id: '1.4.6', level: 'AAA', name: 'Contrast (Enhanced)', owner: 'Tokens', status: 'pass', testId: 'test:contrast' },
14
+ { id: '1.4.11', level: 'AA', name: 'Non-text Contrast', owner: 'Tokens + WC', status: 'partial', testId: 'test:a11y' },
15
+ { id: '2.1.1', level: 'A', name: 'Keyboard', owner: 'Web Components', status: 'partial', testId: 'test:a11y' },
16
+ { id: '2.3.3', level: 'AAA', name: 'Animation from Interactions', owner: 'CSS + Motion', status: 'pass', testId: 'reduced-motion' },
17
+ { id: '2.4.1', level: 'A', name: 'Bypass Blocks', owner: 'CSS skip-link', status: 'pass', testId: 'scan-a11y' },
18
+ { id: '2.4.7', level: 'AA', name: 'Focus Visible', owner: 'CSS focus', status: 'pass', testId: 'test:a11y' },
19
+ { id: '2.4.11', level: 'AA', name: 'Focus Not Obscured (Minimum)', owner: 'CSS', status: 'pass', testId: 'focus-not-obscured' },
20
+ { id: '2.4.13', level: 'AAA', name: 'Focus Appearance', owner: 'CSS', status: 'pass', testId: 'focus-appearance' },
21
+ { id: '2.5.7', level: 'AA', name: 'Dragging Movements', owner: 'CSS + Sample', status: 'pass', testId: 'wcag22-dragging' },
22
+ { id: '2.5.8', level: 'AA', name: 'Target Size', owner: 'CSS + WC', status: 'pass', testId: 'target-size' },
23
+ { id: '3.2.6', level: 'A', name: 'Consistent Help', owner: 'CSS', status: 'pass', testId: 'consistent-help' },
24
+ { id: '3.3.2', level: 'A', name: 'Labels or Instructions', owner: 'Forms + WC', status: 'partial', testId: 'scan-a11y' },
25
+ { id: '3.3.7', level: 'A', name: 'Redundant Entry', owner: 'velin-persist + Author', status: 'partial', testId: 'velin-persist' },
26
+ { id: '3.3.8', level: 'AA', name: 'Accessible Authentication', owner: 'CSS + Sample', status: 'pass', testId: 'wcag22-auth' },
27
+ { id: '4.1.2', level: 'A', name: 'Name, Role, Value', owner: 'Web Components', status: 'partial', testId: 'contracts' },
28
+ { id: '4.1.3', level: 'AA', name: 'Status Messages', owner: 'Announcer + WC', status: 'pass', testId: 'live-regions' },
29
+ ];
30
+
31
+ export function loadComponentContracts(pkgRoot = PKG_ROOT) {
32
+ const path = join(pkgRoot, 'core', 'a11y', 'component-contracts.json');
33
+ if (!existsSync(path)) return { wcagLevel: 'AAA', components: {} };
34
+ return JSON.parse(readFileSync(path, 'utf-8'));
35
+ }
36
+
37
+ export function renderWcagAaaMatrix(contracts) {
38
+ let md = banner('core/a11y + src/a11y');
39
+ md += heading(1, 'WCAG 2.2 AAA framework matrix');
40
+ md += '\n> **Using VelinStyle does not certify your application.** This matrix tracks what the **framework** provides by default.\n\n';
41
+ md += `Framework target: **WCAG 2.2 Level ${contracts.wcagLevel || 'AAA'}** (token defaults + component contracts).\n\n`;
42
+
43
+ md += heading(2, 'Success criteria');
44
+ md += table(
45
+ ['Criterion', 'Level', 'Name', 'Owner', 'Status', 'Test'],
46
+ WCAG_AAA_CRITERIA.map((c) => [
47
+ c.id,
48
+ c.level,
49
+ c.name,
50
+ c.owner,
51
+ c.status,
52
+ `\`${c.testId}\``,
53
+ ]),
54
+ );
55
+
56
+ const comps = contracts.components || {};
57
+ const rows = Object.entries(comps).sort(([a], [b]) => a.localeCompare(b));
58
+ md += heading(2, 'Web component contracts');
59
+ md += table(
60
+ ['Element', 'Status', 'Keyboard', 'Live region', 'Required attrs'],
61
+ rows.map(([tag, c]) => [
62
+ `\`<${tag}>\``,
63
+ c.status || '—',
64
+ c.keyboard || '—',
65
+ c.liveRegion || '—',
66
+ (c.requiredAttributes || []).map((a) => `\`${a}\``).join(', ') || '—',
67
+ ]),
68
+ );
69
+
70
+ md += '\nRun `npm run test:a11y:coverage` for coverage score.\n';
71
+ return md;
72
+ }
73
+
74
+ export function renderComponentA11ySection(tag, contracts) {
75
+ const c = contracts.components?.[tag];
76
+ if (!c) return '';
77
+ let md = heading(2, 'Accessibility');
78
+ md += `WCAG contract status: **${c.status}** (framework target: ${contracts.wcagLevel}).\n\n`;
79
+ if (c.roles?.length) md += `- **Roles:** ${c.roles.map((r) => `\`${r}\``).join(', ')}\n`;
80
+ if (c.keyboard) md += `- **Keyboard:** ${c.keyboard}\n`;
81
+ if (c.liveRegion) md += `- **Live region:** \`${c.liveRegion}\`\n`;
82
+ if (c.reducedMotion) md += `- **Reduced motion:** honored\n`;
83
+ if (c.requiredAttributes?.length) {
84
+ md += `- **Required attributes:** ${c.requiredAttributes.map((a) => `\`${a}\``).join(', ')}\n`;
85
+ }
86
+ if (c.notes) md += `- **Notes:** ${c.notes}\n`;
87
+ md += '\n';
88
+ return md;
89
+ }
@@ -0,0 +1,95 @@
1
+ import { join } from 'path';
2
+ import { listRegisteredAttributes } from '../../core/attributes/registry.js';
3
+ import { banner, heading } from './markdown.js';
4
+
5
+ const ATTRIBUTE_DOCS = {
6
+ 'velin-modal': 'Opens a modal dialog (bridges to `<velin-modal>`).',
7
+ 'velin-tabs': 'Tab navigation (bridges to `<velin-tabs>`).',
8
+ 'velin-accordion': 'Accordion sections (bridges to `<velin-accordion>`).',
9
+ 'velin-tooltip': 'Tooltip on hover/focus (bridges to `<velin-tooltip>`).',
10
+ 'velin-copy': 'Copy text to clipboard (bridges to `<velin-copy>`).',
11
+ 'velin-counter': 'Animated number counter (bridges to `<velin-counter>`).',
12
+ 'velin-notify': 'Show a toast notification (bridges to `<velin-toast>`).',
13
+ 'velin-theme': 'Theme toggle or set `data-velin-theme`.',
14
+ 'velin-progress': 'Progress bar or ring (`ring` for `<velin-progress-ring>`).',
15
+ 'velin-search': 'Attach VelinSearch to an element.',
16
+ 'velin-reveal': 'Reveal element when scrolled into view.',
17
+ 'velin-fade': 'Fade-in on scroll.',
18
+ 'velin-slide': 'Slide-in on scroll (`up`, `down`, …).',
19
+ 'velin-scale': 'Scale-in on scroll.',
20
+ 'velin-parallax': 'Parallax background (`slow` optional).',
21
+ 'velin-hover': 'Hover lift/glow animation.',
22
+ 'velin-stagger': 'Stagger child animations (delay in ms).',
23
+ 'velin-scroll': 'Smooth scroll to anchor (`href="#id"`).',
24
+ 'velin-anchor': 'Scroll-margin for anchor targets.',
25
+ 'velin-lazy': 'Lazy-load images with optional skeleton.',
26
+ 'velin-skeleton': 'Skeleton placeholder (`text`, `avatar`, `image`, …).',
27
+ 'velin-loading': 'Loading spinner state.',
28
+ 'velin-grid': 'Auto grid layout (column count).',
29
+ 'velin-code': 'Code block with copy button and VelinHighlight syntax colors (use `language` or `velin-code="js"`).',
30
+ 'velin-quote': 'Styled blockquote.',
31
+ 'velin-highlight': 'Inline text mark (not syntax highlighting — use `velin-code` or `velinSyntax`).',
32
+ };
33
+
34
+ const A11Y_NOTES = {
35
+ 'velin-reveal': 'Honors `prefers-reduced-motion`: content shown immediately without animation.',
36
+ 'velin-fade': 'Honors `prefers-reduced-motion`.',
37
+ 'velin-slide': 'Honors `prefers-reduced-motion`.',
38
+ 'velin-scale': 'Honors `prefers-reduced-motion`.',
39
+ 'velin-parallax': 'Disabled under `prefers-reduced-motion` (CSS + motion runtime).',
40
+ 'velin-hover': 'Motion reduced globally when user prefers reduced motion.',
41
+ 'velin-stagger': 'Stagger delays collapse under reduced motion.',
42
+ 'velin-scroll': 'Uses `scroll-behavior: auto` when reduced motion is preferred.',
43
+ 'velin-skeleton': 'Sets `aria-hidden` only on empty placeholders — do not use on containers with real text.',
44
+ 'velin-loading': 'Sets `aria-busy`, `role="status"`, default `aria-label="Loading"`.',
45
+ 'velin-notify': 'Dispatches toast event; pair with `<velin-announcer>` or `initA11y()`.',
46
+ 'velin-lazy': 'Adds `loading="lazy"` on `<img>` only.',
47
+ };
48
+
49
+ export function extractAttributes() {
50
+ return listRegisteredAttributes().map((name) => ({
51
+ name,
52
+ description: ATTRIBUTE_DOCS[name] || 'Velin HTML attribute extension.',
53
+ }));
54
+ }
55
+
56
+ export function renderAttribute(attr) {
57
+ let md = banner('velinstyle docs generate');
58
+ md += heading(1, attr.name);
59
+ md += `\n${attr.description}\n\n`;
60
+ md += '## Example\n\n```html\n';
61
+ if (attr.name === 'velin-code') {
62
+ md += '<pre velin-code="js" language="js"><code>const x = 42;</code></pre>\n';
63
+ } else if (attr.name === 'velin-scroll') {
64
+ md += '<a href="#section" velin-scroll>Jump to section</a>\n';
65
+ } else {
66
+ md += `<div ${attr.name}${attr.name === 'velin-slide' ? '="up"' : ''}>…</div>\n`;
67
+ }
68
+ md += '```\n';
69
+ if (A11Y_NOTES[attr.name]) {
70
+ md += '\n## Accessibility\n\n';
71
+ md += `${A11Y_NOTES[attr.name]}\n`;
72
+ }
73
+ return md;
74
+ }
75
+
76
+ export function renderAttributesIndex(attrs) {
77
+ let md = banner('velinstyle docs generate');
78
+ md += heading(1, 'Velin HTML attributes');
79
+ md += '\nDeclarative extensions interpreted by `bootAttributes()` / `bootFromDOM({ attributes: true })`.\n\n';
80
+ md += '| Attribute | Description |\n| --- | --- |\n';
81
+ for (const a of attrs) {
82
+ md += `| [\`${a.name}\`](./${a.name}.md) | ${a.description} |\n`;
83
+ }
84
+ return md;
85
+ }
86
+
87
+ export function writeAttributesDocs(outDir) {
88
+ const attrs = extractAttributes();
89
+ const written = [];
90
+ for (const a of attrs) {
91
+ written.push({ path: join(outDir, 'attributes', `${a.name}.md`), content: renderAttribute(a) });
92
+ }
93
+ written.push({ path: join(outDir, 'attributes', 'README.md'), content: renderAttributesIndex(attrs) });
94
+ return written;
95
+ }
@@ -0,0 +1,50 @@
1
+ import { readFileSync } from 'fs';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+ import { banner, heading, table } from './markdown.js';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+
8
+ /** Commands registered in cli/index.js switch */
9
+ export const REGISTERED_COMMANDS = [
10
+ 'init', 'build', 'themes', 'add', 'icons', 'blueprint', 'tokens',
11
+ 'scan', 'prefix', 'scaffold', 'layout', 'perf', 'docs', 'meta',
12
+ ];
13
+
14
+ export function loadCliManifest() {
15
+ const path = join(__dirname, '..', 'cli-manifest.json');
16
+ return JSON.parse(readFileSync(path, 'utf-8'));
17
+ }
18
+
19
+ export function validateManifest(manifest) {
20
+ const errors = [];
21
+ const names = manifest.commands.map((c) => c.name);
22
+ for (const cmd of REGISTERED_COMMANDS) {
23
+ if (!names.includes(cmd)) {
24
+ errors.push(`Manifest missing command: ${cmd}`);
25
+ }
26
+ }
27
+ for (const name of names) {
28
+ if (!REGISTERED_COMMANDS.includes(name)) {
29
+ errors.push(`Manifest has unknown command (not in router): ${name}`);
30
+ }
31
+ }
32
+ return errors;
33
+ }
34
+
35
+ export function renderCliDocs(manifest) {
36
+ let md = banner('cli/cli-manifest.json');
37
+ md += heading(1, 'CLI commands');
38
+ md += `\nVelinStyle CLI v${manifest.version}. Run \`velinstyle --help\` for full usage.\n\n`;
39
+ md += table(
40
+ ['Command', 'Summary', 'Subcommands', 'Flags', 'Example'],
41
+ manifest.commands.map((c) => [
42
+ `\`${c.name}\``,
43
+ c.summary,
44
+ (c.subcommands || []).join(', ') || '—',
45
+ (c.flags || []).join(', ') || '—',
46
+ `\`${c.example}\``,
47
+ ]),
48
+ );
49
+ return md;
50
+ }
@@ -0,0 +1,175 @@
1
+ import { readFileSync, readdirSync, existsSync } from 'fs';
2
+ import { join, basename } from 'path';
3
+ import { banner, heading, table, bulletList, sortByKey } from './markdown.js';
4
+ import { loadComponentContracts, renderComponentA11ySection } from './extract-a11y.js';
5
+
6
+ function tagFromFile(file) {
7
+ return basename(file, '.js');
8
+ }
9
+
10
+ function parseBlockComment(source) {
11
+ const m = source.match(/^\/\*\*?([\s\S]*?)\*\//);
12
+ if (!m) return null;
13
+ return m[1]
14
+ .split('\n')
15
+ .map((l) => l.replace(/^\s*\* ?/, '').trimEnd())
16
+ .join('\n')
17
+ .trim();
18
+ }
19
+
20
+ function parseObservedAttributes(source) {
21
+ const m = source.match(/static\s+get\s+observedAttributes\s*\(\s*\)\s*\{\s*return\s*\[([^\]]*)\]/);
22
+ if (!m) return [];
23
+ return [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]);
24
+ }
25
+
26
+ function parseEvents(source) {
27
+ const events = new Set();
28
+ const re = /CustomEvent\s*\(\s*['"]([^'"]+)['"]/g;
29
+ let m;
30
+ while ((m = re.exec(source)) !== null) events.add(m[1]);
31
+ return [...events].sort();
32
+ }
33
+
34
+ function parseParts(source) {
35
+ const parts = new Set();
36
+ const re = /part\s*=\s*["']([^"']+)["']/g;
37
+ let m;
38
+ while ((m = re.exec(source)) !== null) parts.add(m[1]);
39
+ return [...parts].sort();
40
+ }
41
+
42
+ function parseSlots(source) {
43
+ const slots = [];
44
+ const re = /<slot\b([^>]*)>/g;
45
+ let m;
46
+ while ((m = re.exec(source)) !== null) {
47
+ const attrs = m[1];
48
+ const nameM = attrs.match(/name\s*=\s*["']([^"']+)["']/);
49
+ slots.push(nameM ? nameM[1] : '(default)');
50
+ }
51
+ return [...new Set(slots)].sort();
52
+ }
53
+
54
+ function parsePublicApi(source) {
55
+ const methods = new Set();
56
+ const getters = new Set();
57
+
58
+ const classM = source.match(/class\s+\w+\s+extends\s+HTMLElement\s*\{([\s\S]*)\n\}/);
59
+ if (!classM) return { methods: [], getters: [] };
60
+ const body = classM[1];
61
+
62
+ const methodRe = /^\s{2}([a-zA-Z][\w]*)\s*\([^)]*\)\s*\{/gm;
63
+ let m;
64
+ while ((m = methodRe.exec(body)) !== null) {
65
+ const name = m[1];
66
+ if (name.startsWith('_') || name === 'constructor') continue;
67
+ if (['connectedCallback', 'disconnectedCallback', 'attributeChangedCallback'].includes(name)) continue;
68
+ methods.add(name);
69
+ }
70
+
71
+ const getRe = /^\s{2}get\s+([a-zA-Z][\w]*)\s*\(/gm;
72
+ while ((m = getRe.exec(body)) !== null) getters.add(m[1]);
73
+
74
+ const setRe = /^\s{2}set\s+([a-zA-Z][\w]*)\s*\(/gm;
75
+ while ((m = setRe.exec(body)) !== null) getters.add(`${m[1]} (setter)`);
76
+
77
+ return {
78
+ methods: [...methods].sort(),
79
+ getters: [...getters].sort(),
80
+ };
81
+ }
82
+
83
+ export function extractComponentFile(filePath) {
84
+ const source = readFileSync(filePath, 'utf-8');
85
+ const tag = tagFromFile(filePath);
86
+ const { methods, getters } = parsePublicApi(source);
87
+ return {
88
+ tag,
89
+ file: `components/${basename(filePath)}`,
90
+ description: parseBlockComment(source),
91
+ observedAttributes: parseObservedAttributes(source),
92
+ events: parseEvents(source),
93
+ parts: parseParts(source),
94
+ slots: parseSlots(source),
95
+ methods,
96
+ getters,
97
+ };
98
+ }
99
+
100
+ export function extractComponents(componentsDir) {
101
+ if (!existsSync(componentsDir)) return [];
102
+ const seen = new Set();
103
+ const items = [];
104
+ for (const name of readdirSync(componentsDir)) {
105
+ if (!name.startsWith('velin-') || !name.endsWith('.js')) continue;
106
+ const key = name.toLowerCase();
107
+ if (seen.has(key)) continue;
108
+ seen.add(key);
109
+ items.push(extractComponentFile(join(componentsDir, name)));
110
+ }
111
+ return sortByKey(items, 'tag');
112
+ }
113
+
114
+ export function renderComponent(meta, contracts) {
115
+ let md = banner(meta.file);
116
+ md += heading(1, `<${meta.tag}>`);
117
+ md += `\nSource: \`${meta.file}\`\n\n`;
118
+
119
+ if (meta.description) {
120
+ md += heading(2, 'Description');
121
+ md += `${meta.description}\n\n`;
122
+ }
123
+
124
+ if (contracts) {
125
+ md += renderComponentA11ySection(meta.tag, contracts);
126
+ }
127
+
128
+ md += heading(2, 'Attributes');
129
+ if (meta.observedAttributes.length) {
130
+ md += table(
131
+ ['Attribute', 'Notes'],
132
+ meta.observedAttributes.map((a) => [`\`${a}\``, 'Observed — triggers `attributeChangedCallback` when changed']),
133
+ );
134
+ } else {
135
+ md += '_No `observedAttributes` declared._\n\n';
136
+ }
137
+
138
+ md += heading(2, 'Events');
139
+ md += bulletList(meta.events.map((e) => `\`${e}\` (bubbles)`));
140
+
141
+ md += heading(2, 'CSS parts');
142
+ md += bulletList(meta.parts.map((p) => `\`${p}\``));
143
+
144
+ md += heading(2, 'Slots');
145
+ md += bulletList(meta.slots.map((s) => `\`${s}\``));
146
+
147
+ md += heading(2, 'Public API');
148
+ const apiRows = [
149
+ ...meta.methods.map((m) => ['Method', `\`${m}()\``]),
150
+ ...meta.getters.map((g) => ['Property', `\`${g}\``]),
151
+ ];
152
+ if (apiRows.length) {
153
+ md += table(['Kind', 'Name'], apiRows);
154
+ } else {
155
+ md += '_No public methods detected._\n';
156
+ }
157
+
158
+ return md;
159
+ }
160
+
161
+ export function renderComponentsIndex(components) {
162
+ let md = banner('components/velin-*.js');
163
+ md += heading(1, 'Web Components');
164
+ md += '\nCustom elements in `components/`. Import via `@birdapi/velinstyle` or lazy-load with `./runtime`.\n\n';
165
+ md += table(
166
+ ['Element', 'Attributes', 'Events', 'Reference'],
167
+ components.map((c) => [
168
+ `\`<${c.tag}>\``,
169
+ String(c.observedAttributes.length),
170
+ String(c.events.length),
171
+ `[${c.tag}.md](./${c.tag}.md)`,
172
+ ]),
173
+ );
174
+ return md;
175
+ }
@@ -0,0 +1,32 @@
1
+ import { banner, heading, bulletList } from './markdown.js';
2
+ import { VELIN_META_MIME } from '../../core/meta/schema.js';
3
+
4
+ /**
5
+ * @param {object} bundle
6
+ */
7
+ export function renderMetaReadme(bundle) {
8
+ let md = banner('velinstyle meta');
9
+ md += heading(1, 'Velin-Meta (agent context)');
10
+ md += '\n> **Do not edit manually.** Regenerate with `velinstyle meta` or `velinstyle docs generate --scope meta`.\n\n';
11
+ md += heading(2, 'MIME and files');
12
+ md += `\n- MIME: \`${VELIN_META_MIME}\`\n`;
13
+ md += '- Bundle: `dist/velin-agent.json`\n';
14
+ md += '- Index: `dist/llms.txt`\n\n';
15
+ md += heading(2, 'CLI');
16
+ md += '\n```bash\nnpx velinstyle meta\nnpx velinstyle meta --llms --base-url https://velinstyle.info\nnpx velinstyle meta page path/to/page.html\n```\n\n';
17
+ md += heading(2, 'Page embedding');
18
+ md += '\n```html\n<script type="' + VELIN_META_MIME + '" id="velin-meta">\n{\n "version": "' + bundle.framework.version + '",\n "page": { "intent": "component-doc" },\n "allowed": { "classesPrefix": ["velin-"] }\n}\n</script>\n```\n\n';
19
+ md += heading(2, 'Snapshot');
20
+ md += `\n- Framework: ${bundle.framework.name}@${bundle.framework.version}\n`;
21
+ md += `- Components: ${bundle.components.count}\n`;
22
+ md += `- Attributes: ${bundle.attributes.count}\n`;
23
+ md += `- CLI commands: ${bundle.cli.commands.length}\n`;
24
+ md += `- Scanner rules: ${bundle.tooling.scannerRuleCount}\n`;
25
+ if (bundle.searchIndex.entryCount != null) {
26
+ md += `- Search index entries: ${bundle.searchIndex.entryCount}\n`;
27
+ }
28
+ md += '\n' + heading(2, 'Conventions');
29
+ md += '\n' + bulletList(bundle.conventions);
30
+ md += '\n';
31
+ return md;
32
+ }
@@ -0,0 +1,76 @@
1
+ import { readFileSync, readdirSync, existsSync } from 'fs';
2
+ import { join, basename } from 'path';
3
+ import { SCANNER_RULES, PERF_RULES } from '../scanner-rules-data.js';
4
+ import { banner, heading, table, sortByKey } from './markdown.js';
5
+
6
+ const VELIN_SELECTOR_RE = /\.(velin-[\w-]+)/g;
7
+
8
+ function firstComment(content) {
9
+ const m = content.match(/\/\*\s*([^*]+?)\s*\*\//);
10
+ return m ? m[1].trim() : null;
11
+ }
12
+
13
+ export function extractA11yModules(srcDir) {
14
+ const a11yDir = join(srcDir, 'a11y');
15
+ if (!existsSync(a11yDir)) return [];
16
+ const modules = [];
17
+ for (const file of readdirSync(a11yDir).filter((f) => f.endsWith('.css')).sort()) {
18
+ const content = readFileSync(join(a11yDir, file), 'utf-8');
19
+ const selectors = new Set();
20
+ let m;
21
+ const re = new RegExp(VELIN_SELECTOR_RE.source, 'g');
22
+ while ((m = re.exec(content)) !== null) selectors.add(m[1]);
23
+ modules.push({
24
+ name: basename(file, '.css'),
25
+ file: `src/a11y/${file}`,
26
+ description: firstComment(content),
27
+ selectors: [...selectors].sort(),
28
+ });
29
+ }
30
+ return modules;
31
+ }
32
+
33
+ export function renderScannerRules() {
34
+ const all = sortByKey([...SCANNER_RULES, ...PERF_RULES], 'id');
35
+ let md = banner('cli/scanner-rules-data.js');
36
+ md += heading(1, 'Scanner rules');
37
+ md += '\nRun `velinstyle scan [path]` for security, a11y, CSS, and PII checks. `velinstyle perf audit` for performance.\n\n';
38
+ md += '```bash\nvelinstyle scan . --severity warning\nvelinstyle scan . --only pii --fix-dry-run\nvelinstyle perf audit samples/\n```\n\n';
39
+
40
+ const categories = [...new Set(all.map((r) => r.category))].sort();
41
+ for (const cat of categories) {
42
+ const rules = all.filter((r) => r.category === cat);
43
+ md += heading(2, cat);
44
+ md += table(
45
+ ['Rule ID', 'Severity', 'Message', 'Fix hint'],
46
+ rules.map((r) => [`\`${r.id}\``, r.severity, r.message, r.fixHint || '—']),
47
+ );
48
+ }
49
+ return md;
50
+ }
51
+
52
+ export function renderA11yModules(modules) {
53
+ let md = banner('src/a11y/*.css');
54
+ md += heading(1, 'Accessibility CSS modules');
55
+ md += '\nImported in `@layer a11y` from `src/velinstyle.css`.\n\n';
56
+ md += table(
57
+ ['Module', 'Selectors', 'Reference'],
58
+ modules.map((m) => [m.name, String(m.selectors.length), `\`${m.file}\``]),
59
+ );
60
+ md += '\n';
61
+ for (const m of modules) {
62
+ md += heading(2, m.name);
63
+ if (m.description) md += `${m.description}\n\n`;
64
+ md += `Source: \`${m.file}\`\n\n`;
65
+ if (m.selectors.length) {
66
+ md += bulletListSelectors(m.selectors);
67
+ } else {
68
+ md += '_No `.velin-*` class selectors (may use element/attribute selectors)._\n\n';
69
+ }
70
+ }
71
+ return md;
72
+ }
73
+
74
+ function bulletListSelectors(items) {
75
+ return `${items.map((s) => `- \`.${s}\``).join('\n')}\n\n`;
76
+ }
@@ -0,0 +1,79 @@
1
+ import { readFileSync, readdirSync, existsSync } from 'fs';
2
+ import { join, basename } from 'path';
3
+ import { banner, heading, table, sortByKey } from './markdown.js';
4
+
5
+ const VAR_RE = /(--velin-[\w-]+)\s*:\s*([^;]+);/g;
6
+
7
+ export function parseTokenCss(content) {
8
+ const tokens = [];
9
+ let m;
10
+ const re = new RegExp(VAR_RE.source, 'g');
11
+ while ((m = re.exec(content)) !== null) {
12
+ tokens.push({ name: m[1], value: m[2].trim() });
13
+ }
14
+ return tokens;
15
+ }
16
+
17
+ export function extractTokens(srcDir) {
18
+ const tokensDir = join(srcDir, 'tokens');
19
+ if (!existsSync(tokensDir)) return { categories: [], byCategory: {} };
20
+
21
+ const files = readdirSync(tokensDir).filter((f) => f.endsWith('.css')).sort();
22
+ const byCategory = {};
23
+
24
+ for (const file of files) {
25
+ const content = readFileSync(join(tokensDir, file), 'utf-8');
26
+ const category = basename(file, '.css');
27
+ const tokens = sortByKey(parseTokenCss(content), 'name');
28
+ byCategory[category] = { file: `src/tokens/${file}`, tokens };
29
+ }
30
+
31
+ return { categories: Object.keys(byCategory).sort(), byCategory };
32
+ }
33
+
34
+ export function renderTokenCategory(category, data) {
35
+ let md = banner(`src/tokens/${category}.css`);
36
+ md += heading(1, `Tokens: ${category}`);
37
+ md += `\nSource: \`${data.file}\`\n\n`;
38
+ if (!data.tokens.length) {
39
+ md += '_No \`--velin-*\` variables found._\n';
40
+ return md;
41
+ }
42
+ md += table(
43
+ ['Token', 'Value'],
44
+ data.tokens.map((t) => [`\`${t.name}\``, `\`${t.value}\``]),
45
+ );
46
+ if (category === 'color') {
47
+ md += '\n## Contrast (WCAG 2.2 AAA)\n\n';
48
+ md += 'VelinStyle defaults target **7:1** for body text on `--velin-color-surface-bright` and semantic text tokens. ';
49
+ md += 'CI runs `npm run test:contrast` across `:root`, dark mode, and all 13 theme presets.\n\n';
50
+ md += 'For **AA (4.5:1)** body text (migration from pre-0.9 palettes), set `data-velin-contrast="aa"` on `<html>` ';
51
+ md += '(see `src/a11y/high-contrast-aaa.css`). AAA remains the default when the attribute is omitted.\n\n';
52
+ md += 'See [WCAG 2.2 AAA matrix](../a11y/wcag22-aaa-matrix.md) and [Accessibility guide](../../../docs/a11y.html).\n';
53
+ }
54
+ return md;
55
+ }
56
+
57
+ export function renderTokensIndex(extracted) {
58
+ let md = banner('src/tokens/*.css');
59
+ md += heading(1, 'Design tokens');
60
+ md += '\nCSS custom properties from `src/tokens/`. Use with `var(--velin-*, fallback)`.\n\n';
61
+ md += 'See also: [`examples/tokens.full.json`](../../examples/tokens.full.json) for JSON `tokens build` input.\n\n';
62
+ md += table(
63
+ ['Category', 'Variables', 'Reference'],
64
+ extracted.categories.map((cat) => {
65
+ const n = extracted.byCategory[cat].tokens.length;
66
+ return [cat, String(n), `[${cat}.md](./${cat}.md)`];
67
+ }),
68
+ );
69
+ return md;
70
+ }
71
+
72
+ export function renderJsonSchema(schemaPath, schemaContent) {
73
+ let md = banner(schemaPath);
74
+ md += heading(1, 'Tokens JSON schema');
75
+ md += `\nSchema file: \`${schemaPath}\`\n\n`;
76
+ md += 'Supported top-level blocks: `tokens`, `themes`, `fonts`, `motion`, `zIndex`, `displayP3`.\n\n';
77
+ md += '```json\n' + JSON.stringify(JSON.parse(schemaContent), null, 2) + '\n```\n';
78
+ return md;
79
+ }
@@ -0,0 +1,75 @@
1
+ import { readFileSync, readdirSync, existsSync } from 'fs';
2
+ import { join, basename } from 'path';
3
+ import { banner, heading, table, sortByKey } from './markdown.js';
4
+
5
+ const CLASS_RULE_RE = /\.(velin-[\w\\\/:-]+)\s*\{([^}]*)\}/g;
6
+ const NESTED_CLASS_RE = /[\s,>+~](\.velin-[\w\\\/:-]+)\s*\{([^}]*)\}/g;
7
+
8
+ function truncateCss(body, max = 120) {
9
+ const one = body.replace(/\s+/g, ' ').trim();
10
+ if (one.length <= max) return one;
11
+ return `${one.slice(0, max - 1)}…`;
12
+ }
13
+
14
+ export function parseUtilityCss(content) {
15
+ const classes = [];
16
+ let m;
17
+ const re = new RegExp(CLASS_RULE_RE.source, 'g');
18
+ while ((m = re.exec(content)) !== null) {
19
+ classes.push({ selector: m[1], output: truncateCss(m[2]) });
20
+ }
21
+ const nested = new RegExp(NESTED_CLASS_RE.source, 'g');
22
+ while ((m = nested.exec(content)) !== null) {
23
+ const sel = m[1].replace(/^\./, '');
24
+ if (!classes.some((c) => c.selector === sel)) {
25
+ classes.push({ selector: sel, output: truncateCss(m[2]) });
26
+ }
27
+ }
28
+ return classes;
29
+ }
30
+
31
+ export function extractUtilities(srcDir) {
32
+ const utilDir = join(srcDir, 'utilities');
33
+ if (!existsSync(utilDir)) return { files: [], byFile: {} };
34
+
35
+ const files = readdirSync(utilDir).filter((f) => f.endsWith('.css')).sort();
36
+ const byFile = {};
37
+
38
+ for (const file of files) {
39
+ const content = readFileSync(join(utilDir, file), 'utf-8');
40
+ const category = basename(file, '.css');
41
+ const classes = sortByKey(parseUtilityCss(content), 'selector');
42
+ byFile[category] = { file: `src/utilities/${file}`, classes };
43
+ }
44
+
45
+ return { files: Object.keys(byFile).sort(), byFile };
46
+ }
47
+
48
+ export function renderUtilityFile(category, data) {
49
+ let md = banner(data.file);
50
+ md += heading(1, `Utilities: ${category}`);
51
+ md += `\nSource: \`${data.file}\`\n\n`;
52
+ if (!data.classes.length) {
53
+ md += '_No `.velin-*` utility classes found._\n';
54
+ return md;
55
+ }
56
+ md += table(
57
+ ['Class', 'CSS output'],
58
+ data.classes.map((c) => [`\`.${c.selector}\``, `\`${c.output}\``]),
59
+ );
60
+ return md;
61
+ }
62
+
63
+ export function renderUtilitiesIndex(extracted) {
64
+ let md = banner('src/utilities/*.css');
65
+ md += heading(1, 'Utility classes');
66
+ md += '\nClasses in `@layer utilities` from `src/utilities/`.\n\n';
67
+ md += table(
68
+ ['Category', 'Classes', 'Reference'],
69
+ extracted.files.map((cat) => {
70
+ const n = extracted.byFile[cat].classes.length;
71
+ return [cat, String(n), `[${cat}.md](./${cat}.md)`];
72
+ }),
73
+ );
74
+ return md;
75
+ }