@krizaka/ui 3.22.0 → 3.24.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 (453) hide show
  1. package/README.md +6 -1
  2. package/dist/achievement.js +1 -1
  3. package/dist/action-bar.js +4 -4
  4. package/dist/app-header.d.ts +79 -0
  5. package/dist/app-header.js +32 -0
  6. package/dist/app-header.js.map +1 -0
  7. package/dist/app-shell.d.ts +20 -2
  8. package/dist/app-shell.js +34 -19
  9. package/dist/app-shell.js.map +1 -1
  10. package/dist/argument-card.d.ts +4 -1
  11. package/dist/argument-card.js +10 -7
  12. package/dist/argument-card.js.map +1 -1
  13. package/dist/audio-player.d.ts +3 -2
  14. package/dist/audio-player.js +14 -10
  15. package/dist/audio-player.js.map +1 -1
  16. package/dist/blocks.css +28 -0
  17. package/dist/button.js +2 -1
  18. package/dist/captcha.d.ts +81 -0
  19. package/dist/captcha.js +162 -0
  20. package/dist/captcha.js.map +1 -0
  21. package/dist/card-grid.d.ts +10 -1
  22. package/dist/card-grid.js +3 -2
  23. package/dist/card-grid.js.map +1 -1
  24. package/dist/card.js +3 -3
  25. package/dist/changelog-page.d.ts +57 -3
  26. package/dist/changelog-page.js +117 -1
  27. package/dist/changelog-page.js.map +1 -1
  28. package/dist/choice-grid.js +1 -1
  29. package/dist/{chunk-YCIBJ6OQ.js → chunk-4DH2HW3D.js} +3 -1
  30. package/dist/{chunk-3IN6TOPM.js → chunk-6ZYRUOTA.js} +6 -3
  31. package/dist/chunk-6ZYRUOTA.js.map +1 -0
  32. package/dist/chunk-APNIVNIX.js +54 -0
  33. package/dist/chunk-APNIVNIX.js.map +1 -0
  34. package/dist/{chunk-FKBHWS4N.js → chunk-CEJDH6SA.js} +2 -2
  35. package/dist/{chunk-OWC2LZD2.js → chunk-CHOJUCVE.js} +6 -3
  36. package/dist/chunk-CHOJUCVE.js.map +1 -0
  37. package/dist/{chunk-3BC4XQ6C.js → chunk-DZU6RMYD.js} +6 -3
  38. package/dist/chunk-DZU6RMYD.js.map +1 -0
  39. package/dist/{chunk-R6NK6ZNY.js → chunk-F7FVPPBB.js} +4 -4
  40. package/dist/{chunk-2L67YTNV.js → chunk-FTX66XZY.js} +2 -2
  41. package/dist/{chunk-5Z7FWCV2.js → chunk-HBZPJ3EP.js} +2 -2
  42. package/dist/{chunk-C2BRIFJG.js → chunk-HJFRHYCI.js} +2 -2
  43. package/dist/{chunk-ZNMFG62E.js → chunk-KH3JOEPY.js} +3 -2
  44. package/dist/chunk-KH3JOEPY.js.map +1 -0
  45. package/dist/{chunk-BTMFZ66D.js → chunk-KM6VAKIE.js} +2 -2
  46. package/dist/{chunk-DGQMDJXK.js → chunk-LZNZLCOE.js} +2 -2
  47. package/dist/{chunk-AQSZJJAJ.js → chunk-MQPC3P4T.js} +2 -2
  48. package/dist/{chunk-2FRWNSRO.js → chunk-NSLXEDIS.js} +3 -2
  49. package/dist/chunk-NSLXEDIS.js.map +1 -0
  50. package/dist/{chunk-4LOMIQVX.js → chunk-W675RFOA.js} +3 -2
  51. package/dist/chunk-W675RFOA.js.map +1 -0
  52. package/dist/{chunk-G3ETON2A.js → chunk-WU4VLSEA.js} +3 -2
  53. package/dist/chunk-WU4VLSEA.js.map +1 -0
  54. package/dist/{chunk-IZ7MV6HM.js → chunk-XVFQZ3ZG.js} +3 -2
  55. package/dist/chunk-XVFQZ3ZG.js.map +1 -0
  56. package/dist/cn.js +1 -1
  57. package/dist/code-block.js +2 -2
  58. package/dist/code-block.js.map +1 -1
  59. package/dist/command.js +3 -3
  60. package/dist/comparison-table.js +1 -1
  61. package/dist/confirm-button.js +2 -2
  62. package/dist/continue-prompt.js +3 -3
  63. package/dist/continue-prompt.js.map +1 -1
  64. package/dist/cta-panel.js +1 -1
  65. package/dist/data-table.js +4 -4
  66. package/dist/date-picker.js +2 -2
  67. package/dist/decision-list.js +1 -1
  68. package/dist/dialog.js +3 -2
  69. package/dist/dialog.js.map +1 -1
  70. package/dist/docs-nav.d.ts +36 -0
  71. package/dist/docs-nav.js +44 -0
  72. package/dist/docs-nav.js.map +1 -0
  73. package/dist/dropdown-menu.js +1 -1
  74. package/dist/error-page.js +1 -1
  75. package/dist/field.js +1 -1
  76. package/dist/filter-bar.js +2 -2
  77. package/dist/force-update-screen.js +2 -1
  78. package/dist/force-update-screen.js.map +1 -1
  79. package/dist/gallery-page.js +1 -1
  80. package/dist/genui.d.ts +3 -1
  81. package/dist/genui.js +33 -6
  82. package/dist/genui.js.map +1 -1
  83. package/dist/glow-card.js +1 -1
  84. package/dist/goal-gauge.js +1 -1
  85. package/dist/how-it-works-page.js +1 -1
  86. package/dist/image-cropper.js +3 -3
  87. package/dist/index.d.ts +15 -1
  88. package/dist/index.js +65 -5
  89. package/dist/index.js.map +1 -1
  90. package/dist/landscape.js +1 -1
  91. package/dist/link-card.d.ts +9 -2
  92. package/dist/link-card.js +5 -44
  93. package/dist/link-card.js.map +1 -1
  94. package/dist/longform-page.js +1 -1
  95. package/dist/maintenance-screen.js +2 -1
  96. package/dist/maintenance-screen.js.map +1 -1
  97. package/dist/map.js +3 -3
  98. package/dist/map.js.map +1 -1
  99. package/dist/markdown.js +2 -1
  100. package/dist/markdown.js.map +1 -1
  101. package/dist/marquee.js +1 -1
  102. package/dist/mascot-quote.js +1 -1
  103. package/dist/match-dialog.js +2 -1
  104. package/dist/match-dialog.js.map +1 -1
  105. package/dist/media-grid.d.ts +26 -4
  106. package/dist/media-grid.js +144 -77
  107. package/dist/media-grid.js.map +1 -1
  108. package/dist/native/map.js +3 -3
  109. package/dist/native/map.js.map +1 -1
  110. package/dist/native.d.ts +18 -1
  111. package/dist/native.js +105 -24
  112. package/dist/native.js.map +1 -1
  113. package/dist/nav-bar.js +3 -3
  114. package/dist/page-hero.js +6 -6
  115. package/dist/page.js +2 -2
  116. package/dist/popover.js +1 -1
  117. package/dist/pricing-page.js +1 -1
  118. package/dist/product-page.js +1 -1
  119. package/dist/profile-card.js +1 -1
  120. package/dist/promise-card.js +1 -1
  121. package/dist/radio-group.js +2 -2
  122. package/dist/rail-nav.d.ts +149 -0
  123. package/dist/rail-nav.js +177 -0
  124. package/dist/rail-nav.js.map +1 -0
  125. package/dist/scroll-stage.js +1 -1
  126. package/dist/settings-layout.d.ts +75 -62
  127. package/dist/settings-layout.js +39 -6
  128. package/dist/settings-layout.js.map +1 -1
  129. package/dist/showcase.js +1 -1
  130. package/dist/site-footer.d.ts +48 -67
  131. package/dist/site-footer.js +36 -12
  132. package/dist/site-footer.js.map +1 -1
  133. package/dist/slider.js +2 -2
  134. package/dist/stage-banner.js +1 -1
  135. package/dist/step-showcase.js +1 -1
  136. package/dist/tabs.js +1 -1
  137. package/dist/tabs.js.map +1 -1
  138. package/dist/terminal-replay.js +1 -1
  139. package/dist/theme.js +2 -1
  140. package/dist/theme.js.map +1 -1
  141. package/dist/toast.js +1 -1
  142. package/dist/toc.js +1 -1
  143. package/dist/tooltip.js +1 -1
  144. package/dist/update-prompt.js +2 -1
  145. package/dist/update-prompt.js.map +1 -1
  146. package/dist/vertical-video.js +2 -2
  147. package/dist/video-player.js +3 -3
  148. package/dist/waveform.d.ts +93 -0
  149. package/dist/waveform.js +95 -0
  150. package/dist/waveform.js.map +1 -0
  151. package/package.json +81 -10
  152. package/registry/achievement.json +1 -1
  153. package/registry/action-bar.json +1 -1
  154. package/registry/activity-feed.json +1 -1
  155. package/registry/alert.json +1 -1
  156. package/registry/app-header.json +202 -0
  157. package/registry/app-shell.json +33 -9
  158. package/registry/argument-card.json +37 -5
  159. package/registry/audio-player.json +5 -3
  160. package/registry/auth-layout.json +1 -1
  161. package/registry/avatar.json +1 -1
  162. package/registry/badge.json +1 -1
  163. package/registry/button.json +3 -2
  164. package/registry/captcha.json +300 -0
  165. package/registry/card-grid.json +27 -3
  166. package/registry/card.json +1 -1
  167. package/registry/catalogue-page.json +1 -1
  168. package/registry/changelog-page.json +11 -7
  169. package/registry/chat.json +1 -1
  170. package/registry/checkbox.json +1 -1
  171. package/registry/chip-link.json +1 -1
  172. package/registry/chip.json +1 -1
  173. package/registry/choice-grid.json +1 -1
  174. package/registry/cn.json +1 -1
  175. package/registry/code-block.json +1 -1
  176. package/registry/combobox.json +1 -1
  177. package/registry/command.json +1 -1
  178. package/registry/comparison-table.json +1 -1
  179. package/registry/component-page.json +1 -1
  180. package/registry/confirm-button.json +1 -1
  181. package/registry/contact-page.json +1 -1
  182. package/registry/continue-prompt.json +1 -1
  183. package/registry/countdown.json +1 -1
  184. package/registry/cta-panel.json +1 -1
  185. package/registry/dashboard-layout.json +1 -1
  186. package/registry/data-table.json +1 -1
  187. package/registry/date-picker.json +1 -1
  188. package/registry/decision-list.json +1 -1
  189. package/registry/description-list.json +1 -1
  190. package/registry/detail-layout.json +1 -1
  191. package/registry/dialog.json +1 -1
  192. package/registry/docs-article-page.json +1 -1
  193. package/registry/docs-hub-page.json +1 -1
  194. package/registry/docs-layout.json +1 -1
  195. package/registry/docs-nav.json +177 -0
  196. package/registry/document-viewer.json +1 -1
  197. package/registry/dropdown-menu.json +1 -1
  198. package/registry/empty-state.json +1 -1
  199. package/registry/error-page.json +1 -1
  200. package/registry/examples/activity-feed/native/notifications.light.png +0 -0
  201. package/registry/examples/app-header/basic.d.ts +4 -0
  202. package/registry/examples/app-header/basic.js +23 -0
  203. package/registry/examples/app-header/basic.tsx +17 -0
  204. package/registry/examples/app-shell/desktop.d.ts +4 -0
  205. package/registry/examples/app-shell/desktop.js +41 -0
  206. package/registry/examples/app-shell/desktop.tsx +37 -0
  207. package/registry/examples/argument-card/hover-light.d.ts +4 -0
  208. package/registry/examples/argument-card/hover-light.js +17 -0
  209. package/registry/examples/argument-card/hover-light.tsx +19 -0
  210. package/registry/examples/argument-card/hover-quiet.d.ts +4 -0
  211. package/registry/examples/argument-card/hover-quiet.js +17 -0
  212. package/registry/examples/argument-card/hover-quiet.tsx +19 -0
  213. package/registry/examples/avatar/native/group.light.png +0 -0
  214. package/registry/examples/avatar/native/presence.light.png +0 -0
  215. package/registry/examples/avatar/native/sizes.light.png +0 -0
  216. package/registry/examples/badge/native/tones.light.png +0 -0
  217. package/registry/examples/button/native/ghost.light.png +0 -0
  218. package/registry/examples/captcha/disclosure.d.ts +4 -0
  219. package/registry/examples/captcha/disclosure.js +14 -0
  220. package/registry/examples/captcha/disclosure.tsx +11 -0
  221. package/registry/examples/captcha/form.d.ts +4 -0
  222. package/registry/examples/captcha/form.js +18 -0
  223. package/registry/examples/captcha/form.tsx +17 -0
  224. package/registry/examples/card/native/body.light.png +0 -0
  225. package/registry/examples/card/native/default.light.png +0 -0
  226. package/registry/examples/card/native/elevated.light.png +0 -0
  227. package/registry/examples/card/native/pressable.light.png +0 -0
  228. package/registry/examples/card-grid/hover-quiet.d.ts +4 -0
  229. package/registry/examples/card-grid/hover-quiet.js +18 -0
  230. package/registry/examples/card-grid/hover-quiet.tsx +20 -0
  231. package/registry/examples/changelog-page/releases.js +1 -1
  232. package/registry/examples/changelog-page/releases.tsx +1 -1
  233. package/registry/examples/chat/native/conversation.light.png +0 -0
  234. package/registry/examples/chat/native/inbox.light.png +0 -0
  235. package/registry/examples/chip/native/multiple.light.png +0 -0
  236. package/registry/examples/chip/native/removable.light.png +0 -0
  237. package/registry/examples/chip/native/single.light.png +0 -0
  238. package/registry/examples/chip/native/toggle.light.png +0 -0
  239. package/registry/examples/combobox/native/multiple.light.png +0 -0
  240. package/registry/examples/combobox/native/search.light.png +0 -0
  241. package/registry/examples/countdown/native/days.light.png +0 -0
  242. package/registry/examples/countdown/native/hours.light.png +0 -0
  243. package/registry/examples/countdown/native/sizes.light.png +0 -0
  244. package/registry/examples/countdown/native/urgent.light.png +0 -0
  245. package/registry/examples/countdown/native/with-label.light.png +0 -0
  246. package/registry/examples/date-picker/native/birth-date-wheel.light.png +0 -0
  247. package/registry/examples/date-picker/native/birth-date.light.png +0 -0
  248. package/registry/examples/date-picker/native/calendar.dark.png +0 -0
  249. package/registry/examples/date-picker/native/calendar.light.png +0 -0
  250. package/registry/examples/date-picker/native/day-strip.dark.png +0 -0
  251. package/registry/examples/date-picker/native/day-strip.light.png +0 -0
  252. package/registry/examples/date-picker/native/time-slots.dark.png +0 -0
  253. package/registry/examples/date-picker/native/time-slots.light.png +0 -0
  254. package/registry/examples/date-picker/native/time-wheel.light.png +0 -0
  255. package/registry/examples/description-list/native/job-details.light.png +0 -0
  256. package/registry/examples/docs-nav/groups.d.ts +4 -0
  257. package/registry/examples/docs-nav/groups.js +21 -0
  258. package/registry/examples/docs-nav/groups.tsx +15 -0
  259. package/registry/examples/empty-state/native/default.light.png +0 -0
  260. package/registry/examples/field/native/default.light.png +0 -0
  261. package/registry/examples/field/native/select.light.png +0 -0
  262. package/registry/examples/force-update-screen/native/default.light.png +0 -0
  263. package/registry/examples/form/native/amount.light.png +0 -0
  264. package/registry/examples/form/native/audience.light.png +0 -0
  265. package/registry/examples/form/native/profile.light.png +0 -0
  266. package/registry/examples/form/native/settings.light.png +0 -0
  267. package/registry/examples/form/native/sign-up.light.png +0 -0
  268. package/registry/examples/form/native/upload.dark.png +0 -0
  269. package/registry/examples/form/native/upload.light.png +0 -0
  270. package/registry/examples/goal-gauge/native/default.light.png +0 -0
  271. package/registry/examples/header/native/large-title.light.png +0 -0
  272. package/registry/examples/link-card/hover-light.d.ts +4 -0
  273. package/registry/examples/link-card/hover-light.js +18 -0
  274. package/registry/examples/link-card/hover-light.tsx +20 -0
  275. package/registry/examples/link-card/hover-quiet.d.ts +4 -0
  276. package/registry/examples/link-card/hover-quiet.js +18 -0
  277. package/registry/examples/link-card/hover-quiet.tsx +20 -0
  278. package/registry/examples/loader/native/job-wait.light.png +0 -0
  279. package/registry/examples/maintenance-screen/native/default.light.png +0 -0
  280. package/registry/examples/map/native/places.light.png +0 -0
  281. package/registry/examples/markdown/native/basic.light.png +0 -0
  282. package/registry/examples/mascot-state/native/default.light.png +0 -0
  283. package/registry/examples/media-picker/native/story.light.png +0 -0
  284. package/registry/examples/meta-row/native/video.light.png +0 -0
  285. package/registry/examples/meter/native/credits.light.png +0 -0
  286. package/registry/examples/meter/native/split-ring.light.png +0 -0
  287. package/registry/examples/number-ticker/native/balance.light.png +0 -0
  288. package/registry/examples/onboarding/native/first-launch.light.png +0 -0
  289. package/registry/examples/otp-input/native/verify.light.png +0 -0
  290. package/registry/examples/pagination/native/cursor.light.png +0 -0
  291. package/registry/examples/pagination/native/pages.light.png +0 -0
  292. package/registry/examples/permission-prompt/native/blocked.light.png +0 -0
  293. package/registry/examples/permission-prompt/native/camera.light.png +0 -0
  294. package/registry/examples/profile-card/native/default.light.png +0 -0
  295. package/registry/examples/radio-group/native/buttons.light.png +0 -0
  296. package/registry/examples/radio-group/native/cards.light.png +0 -0
  297. package/registry/examples/radio-group/native/list.light.png +0 -0
  298. package/registry/examples/radio-group/native/segmented.light.png +0 -0
  299. package/registry/examples/rail-nav/expandable.d.ts +4 -0
  300. package/registry/examples/rail-nav/expandable.js +34 -0
  301. package/registry/examples/rail-nav/expandable.tsx +28 -0
  302. package/registry/examples/rail-nav/studio.d.ts +4 -0
  303. package/registry/examples/rail-nav/studio.js +43 -0
  304. package/registry/examples/rail-nav/studio.tsx +38 -0
  305. package/registry/examples/screen/native/app-shell.light.png +0 -0
  306. package/registry/examples/screen/native/auth.light.png +0 -0
  307. package/registry/examples/screen/native/map.light.png +0 -0
  308. package/registry/examples/screen/native/notifications.light.png +0 -0
  309. package/registry/examples/screen/native/profile.light.png +0 -0
  310. package/registry/examples/segmented/native/default.light.png +0 -0
  311. package/registry/examples/segmented/native/small.light.png +0 -0
  312. package/registry/examples/settings-layout/flat.d.ts +4 -0
  313. package/registry/examples/settings-layout/flat.js +26 -0
  314. package/registry/examples/settings-layout/flat.tsx +30 -0
  315. package/registry/examples/showcase/native/slides.light.png +0 -0
  316. package/registry/examples/site-footer/app.d.ts +4 -0
  317. package/registry/examples/site-footer/app.js +25 -0
  318. package/registry/examples/site-footer/app.tsx +22 -0
  319. package/registry/examples/site-footer/docs.d.ts +4 -0
  320. package/registry/examples/site-footer/docs.js +31 -0
  321. package/registry/examples/site-footer/docs.tsx +24 -0
  322. package/registry/examples/slider/native/default.light.png +0 -0
  323. package/registry/examples/slider/native/stepped.light.png +0 -0
  324. package/registry/examples/step-progress/native/sign-up.light.png +0 -0
  325. package/registry/examples/stepper/native/run.light.png +0 -0
  326. package/registry/examples/swipeable-row/native/conversation.light.png +0 -0
  327. package/registry/examples/switch/native/settings.light.png +0 -0
  328. package/registry/examples/tab-bar/native/main-sections.light.png +0 -0
  329. package/registry/examples/tabs/native/sections.light.png +0 -0
  330. package/registry/examples/tabs/native/segmented.light.png +0 -0
  331. package/registry/examples/tag-selector/native/interests.light.png +0 -0
  332. package/registry/examples/toast/native/tones.light.png +0 -0
  333. package/registry/examples/txt/native/tones.dark.png +0 -0
  334. package/registry/examples/txt/native/tones.light.png +0 -0
  335. package/registry/examples/update-prompt/native/default.light.png +0 -0
  336. package/registry/examples/waveform/card.d.ts +4 -0
  337. package/registry/examples/waveform/card.js +18 -0
  338. package/registry/examples/waveform/card.tsx +16 -0
  339. package/registry/examples/waveform/native/card.d.ts +4 -0
  340. package/registry/examples/waveform/native/card.dark.png +0 -0
  341. package/registry/examples/waveform/native/card.js +19 -0
  342. package/registry/examples/waveform/native/card.light.png +0 -0
  343. package/registry/examples/waveform/native/card.tsx +19 -0
  344. package/registry/examples/waveform/sizes.d.ts +4 -0
  345. package/registry/examples/waveform/sizes.js +16 -0
  346. package/registry/examples/waveform/sizes.tsx +14 -0
  347. package/registry/field.json +1 -1
  348. package/registry/file-upload.json +1 -1
  349. package/registry/filter-bar.json +1 -1
  350. package/registry/force-update-screen.json +1 -1
  351. package/registry/form.json +1 -1
  352. package/registry/gallery-page.json +1 -1
  353. package/registry/genui.json +3 -3
  354. package/registry/glow-card.json +1 -1
  355. package/registry/goal-gauge.json +1 -1
  356. package/registry/how-it-works-page.json +1 -1
  357. package/registry/image-cropper.json +1 -1
  358. package/registry/index.json +197 -14
  359. package/registry/iso.json +1 -1
  360. package/registry/kanban.json +1 -1
  361. package/registry/kbd.json +1 -1
  362. package/registry/landing-page.json +1 -1
  363. package/registry/landscape.json +1 -1
  364. package/registry/legal-page.json +1 -1
  365. package/registry/link-card.json +38 -6
  366. package/registry/list.json +1 -1
  367. package/registry/loader.json +1 -1
  368. package/registry/longform-page.json +1 -1
  369. package/registry/maintenance-screen.json +1 -1
  370. package/registry/map.json +2 -2
  371. package/registry/markdown.json +1 -1
  372. package/registry/marketing-layout.json +1 -1
  373. package/registry/marquee.json +1 -1
  374. package/registry/mascot-quote.json +1 -1
  375. package/registry/mascot-state.json +1 -1
  376. package/registry/mascots.json +1 -1
  377. package/registry/match-dialog.json +1 -1
  378. package/registry/media-capture.json +1 -1
  379. package/registry/media-grid.json +92 -6
  380. package/registry/meta-row.json +1 -1
  381. package/registry/meter.json +1 -1
  382. package/registry/nav-bar.json +2 -2
  383. package/registry/number-ticker.json +1 -1
  384. package/registry/page-header.json +1 -1
  385. package/registry/page-hero.json +1 -1
  386. package/registry/page.json +1 -1
  387. package/registry/pagination.json +1 -1
  388. package/registry/photo-grid.json +1 -1
  389. package/registry/pointer-light.json +1 -1
  390. package/registry/popover.json +1 -1
  391. package/registry/pricing-page.json +1 -1
  392. package/registry/product-page.json +1 -1
  393. package/registry/profile-card.json +1 -1
  394. package/registry/progress.json +1 -1
  395. package/registry/promise-card.json +1 -1
  396. package/registry/radio-group.json +1 -1
  397. package/registry/rail-nav.json +493 -0
  398. package/registry/recipes.json +1 -1
  399. package/registry/scroll-stage.json +1 -1
  400. package/registry/search-field.json +1 -1
  401. package/registry/section-backdrop.json +1 -1
  402. package/registry/separator.json +1 -1
  403. package/registry/settings-layout.json +104 -9
  404. package/registry/showcase.json +1 -1
  405. package/registry/site-footer.json +38 -6
  406. package/registry/sites.json +1 -1
  407. package/registry/skeleton.json +1 -1
  408. package/registry/skip-link.json +1 -1
  409. package/registry/slider.json +1 -1
  410. package/registry/slot.json +1 -1
  411. package/registry/spinner.json +1 -1
  412. package/registry/split-view.json +1 -1
  413. package/registry/stage-banner.json +1 -1
  414. package/registry/stat.json +1 -1
  415. package/registry/status-page.json +1 -1
  416. package/registry/step-progress.json +1 -1
  417. package/registry/step-showcase.json +1 -1
  418. package/registry/stepper.json +1 -1
  419. package/registry/swipe-deck.json +1 -1
  420. package/registry/switch.json +1 -1
  421. package/registry/tabs.json +2 -2
  422. package/registry/tag-input.json +1 -1
  423. package/registry/tag-selector.json +1 -1
  424. package/registry/terminal-replay.json +1 -1
  425. package/registry/theme.json +1 -1
  426. package/registry/toast.json +1 -1
  427. package/registry/toc.json +1 -1
  428. package/registry/tooltip.json +1 -1
  429. package/registry/tree-view.json +1 -1
  430. package/registry/update-prompt.json +1 -1
  431. package/registry/verified-badge.json +1 -1
  432. package/registry/vertical-video.json +1 -1
  433. package/registry/video-player.json +1 -1
  434. package/registry/video-trimmer.json +1 -1
  435. package/registry/virtual.json +1 -1
  436. package/registry/waveform.json +296 -0
  437. package/dist/chunk-2FRWNSRO.js.map +0 -1
  438. package/dist/chunk-3BC4XQ6C.js.map +0 -1
  439. package/dist/chunk-3IN6TOPM.js.map +0 -1
  440. package/dist/chunk-4LOMIQVX.js.map +0 -1
  441. package/dist/chunk-G3ETON2A.js.map +0 -1
  442. package/dist/chunk-IZ7MV6HM.js.map +0 -1
  443. package/dist/chunk-OWC2LZD2.js.map +0 -1
  444. package/dist/chunk-ZNMFG62E.js.map +0 -1
  445. /package/dist/{chunk-YCIBJ6OQ.js.map → chunk-4DH2HW3D.js.map} +0 -0
  446. /package/dist/{chunk-FKBHWS4N.js.map → chunk-CEJDH6SA.js.map} +0 -0
  447. /package/dist/{chunk-R6NK6ZNY.js.map → chunk-F7FVPPBB.js.map} +0 -0
  448. /package/dist/{chunk-2L67YTNV.js.map → chunk-FTX66XZY.js.map} +0 -0
  449. /package/dist/{chunk-5Z7FWCV2.js.map → chunk-HBZPJ3EP.js.map} +0 -0
  450. /package/dist/{chunk-C2BRIFJG.js.map → chunk-HJFRHYCI.js.map} +0 -0
  451. /package/dist/{chunk-BTMFZ66D.js.map → chunk-KM6VAKIE.js.map} +0 -0
  452. /package/dist/{chunk-DGQMDJXK.js.map → chunk-LZNZLCOE.js.map} +0 -0
  453. /package/dist/{chunk-AQSZJJAJ.js.map → chunk-MQPC3P4T.js.map} +0 -0
@@ -41,7 +41,8 @@
41
41
  "What it does, the bird's quote and the long version are never on the face: a detail page (`more`), or `panelLabel`.",
42
42
  "One idea per card; one bird per idea: small in the corner (`mark`), its quote (`rule`) where the card unfolds.",
43
43
  "Give each subject its hue with `--kz-hue` on the card (or a wrapper); the brand's accent otherwise.",
44
- "Secondary actions as an icon `ActionBar` in `actions`; the way in as `more`."
44
+ "Secondary actions as an icon `ActionBar` in `actions`; the way in as `more`.",
45
+ "`hover=\"quiet\"` for reasons read as prose (a product's argument, a policy): no glow, a lift and a ring on focus. The default `light` keeps the pointer's light."
45
46
  ],
46
47
  "accessibility": {
47
48
  "keyboard": [
@@ -53,7 +54,7 @@
53
54
  "notes": [
54
55
  "An `article` named by its title; the hook's label is read before the line (visually hidden); the fold is a native `details` element.",
55
56
  "The way in is described by the card's title (`aria-describedby`), so several `Read more` links stay distinct.",
56
- "The lift, the light and the panel's chevron stop under `prefers-reduced-motion`."
57
+ "The lift, the light and the panel's chevron stop under `prefers-reduced-motion`; the quiet hover keeps its surface and ring, and never lifts."
57
58
  ]
58
59
  },
59
60
  "related": [
@@ -104,6 +105,13 @@
104
105
  "description": "Compact facts under the line: registry and version, a status said honestly.",
105
106
  "required": false
106
107
  },
108
+ {
109
+ "name": "hover",
110
+ "type": "CardHover",
111
+ "default": null,
112
+ "description": "The hover of the card: `light` (the pointer's light, the default) or `quiet` (no glow, a lift, a ring on focus).",
113
+ "required": false
114
+ },
107
115
  {
108
116
  "name": "icon",
109
117
  "type": "ReactNode",
@@ -194,6 +202,22 @@
194
202
  "path": "examples/argument-card/grid.tsx",
195
203
  "module": "@krizaka/ui/registry/examples/argument-card/grid",
196
204
  "code": "import { ArgumentCard } from \"@krizaka/ui/argument-card\";\nimport { CardGrid } from \"@krizaka/ui/card-grid\";\n\nconst reasons = [\n { id: \"local\", title: \"Local inference\", hook: \"Elsewhere, every prompt crosses the ocean.\" },\n { id: \"pipeline\", title: \"One pipeline\", hook: \"Elsewhere, each feature guards itself — or forgets.\" },\n { id: \"telemetry\", title: \"Zero telemetry\", hook: \"Elsewhere, usage data is the price of the free tier.\" },\n];\n\n// A product page's reasons, side by side: each card the same height, the hook at the same place on every card.\nexport default function ArgumentCardGrid() {\n return (\n <CardGrid className=\"w-full max-w-5xl\" min={260} reveal={false}>\n {reasons.map((r) => (\n <ArgumentCard key={r.id} id={`why-${r.id}`} title={r.title} hook={{ label: \"The trap elsewhere\", text: r.hook }} more={{ href: `#${r.id}`, label: \"Details\" }} />\n ))}\n </CardGrid>\n );\n}\n"
205
+ },
206
+ {
207
+ "name": "hover-light",
208
+ "title": "The light hover",
209
+ "description": "`hover=\"light\"`, the default: the pointer's light and the coloured lift, said explicitly.",
210
+ "path": "examples/argument-card/hover-light.tsx",
211
+ "module": "@krizaka/ui/registry/examples/argument-card/hover-light",
212
+ "code": "import { ArgumentCard } from \"@krizaka/ui/argument-card\";\nimport { CardGrid } from \"@krizaka/ui/card-grid\";\n\nconst reasons = [\n { id: \"local\", title: \"Local inference\", hook: \"Elsewhere, every prompt crosses the ocean.\" },\n { id: \"pipeline\", title: \"One pipeline\", hook: \"Elsewhere, each feature guards itself — or forgets.\" },\n { id: \"telemetry\", title: \"Zero telemetry\", hook: \"Elsewhere, usage data is the price of the free tier.\" },\n];\n\n// The default hover, said explicitly: the pointer's light on the reasons.\nexport default function ArgumentCardHoverLight() {\n return (\n <CardGrid className=\"w-full max-w-5xl\" min={260} reveal={false}>\n {reasons.map((r) => (\n <ArgumentCard key={r.id} hover=\"light\" id={`light-${r.id}`} title={r.title} hook={{ label: \"The trap elsewhere\", text: r.hook }} more={{ href: `#${r.id}`, label: \"Details\" }} />\n ))}\n </CardGrid>\n );\n}\n"
213
+ },
214
+ {
215
+ "name": "hover-quiet",
216
+ "title": "The quiet hover",
217
+ "description": "`hover=\"quiet\"` on a grid of reasons: no glow, a lift, a ring on focus.",
218
+ "path": "examples/argument-card/hover-quiet.tsx",
219
+ "module": "@krizaka/ui/registry/examples/argument-card/hover-quiet",
220
+ "code": "import { ArgumentCard } from \"@krizaka/ui/argument-card\";\nimport { CardGrid } from \"@krizaka/ui/card-grid\";\n\nconst reasons = [\n { id: \"local\", title: \"Local inference\", hook: \"Elsewhere, every prompt crosses the ocean.\" },\n { id: \"pipeline\", title: \"One pipeline\", hook: \"Elsewhere, each feature guards itself — or forgets.\" },\n { id: \"telemetry\", title: \"Zero telemetry\", hook: \"Elsewhere, usage data is the price of the free tier.\" },\n];\n\n// The quiet hover on the reasons: no glow, a lift, a ring on focus. The grid is quiet, so every card is.\nexport default function ArgumentCardHoverQuiet() {\n return (\n <CardGrid className=\"w-full max-w-5xl\" min={260} reveal={false} hover=\"quiet\">\n {reasons.map((r) => (\n <ArgumentCard key={r.id} id={`quiet-${r.id}`} title={r.title} hook={{ label: \"The trap elsewhere\", text: r.hook }} more={{ href: `#${r.id}`, label: \"Details\" }} />\n ))}\n </CardGrid>\n );\n}\n"
197
221
  }
198
222
  ]
199
223
  },
@@ -201,7 +225,7 @@
201
225
  "files": [
202
226
  {
203
227
  "path": "argument-card/argument-card.tsx",
204
- "content": "// Server-safe. A card that argues (blocks.css), compact: who (icon, name, facts) → the trap in one line → the way in.\n// The long version — what it does, the rule a bird stands for, the reasons — waits in a panel the reader opens (a\n// native <details>: keyboard, screen readers, no script). Cards of a row share their height; the panel and the foot\n// sit at the bottom, at the same place on every card.\nimport type * as React from \"react\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\nimport { ArrowGlyph, ChevronGlyph } from \"../glyphs\";\nimport { type Fact, Facts } from \"../page/page\";\n\nexport type ArgumentCardProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** The id of the title: the card's accessible name. */\n id: string;\n /** The title: the subject (a building block, a feature, a plan). */\n title: React.ReactNode;\n /** The icon tile (a signature icon): decorative. */\n icon?: React.ReactNode;\n /** A mono line under the title (a repository, a package). */\n meta?: React.ReactNode;\n /** Small, in the top corner: the bird whose rule it is (a `Mascot` of 40 px, still). Decorative. */\n mark?: React.ReactNode;\n /** Compact facts under the line: registry and version, a status said honestly. */\n facts?: readonly Fact[];\n /**\n * The problem in ONE line (≤ 90 characters): what a reader keeps if they read nothing else. `label` (`The trap`) is\n * read by assistive technology before it, not shown.\n */\n hook: { label: React.ReactNode; text: React.ReactNode };\n /** Unfolded only: what it does, as a few short points, with their label (`What it does`). */\n points?: { label: React.ReactNode; items: readonly React.ReactNode[] };\n /** Unfolded only: the rule told by a bird (a `MascotQuote`, `variant=\"inline\"`). */\n rule?: React.ReactNode;\n /** Unfolded only: the long version, as labelled paragraphs. */\n details?: readonly { label: React.ReactNode; text: React.ReactNode }[];\n /**\n * Without a detail page: the words that unfold the card in place (`Details`) — a native `<details>` holding the\n * points, the rule and the long version. Ignored with `more`: the detail page holds them.\n */\n panelLabel?: React.ReactNode;\n /** The way in: a detail page — one short action at the foot (`Details`). With it, the card is only its face. */\n more?: { href: string; label: React.ReactNode };\n /** Discreet secondary actions at the foot, on the right (an `ActionBar` with `variant=\"icons\"`). */\n actions?: React.ReactNode;\n /**\n * The title's level.\n * @default 3\n */\n level?: 2 | 3 | 4;\n /** The app's link component for internal addresses (Next's `Link`); an `<a>` otherwise. */\n linkAs?: LinkComponent;\n};\n\n/**\n * A card that argues, compact (about a third of a full card): who (icon, name, meta; the bird small in the corner) →\n * the problem in one line → compact facts → one short action. What it does, the bird's rule and the long version are\n * never on the face: they live on the detail page (`more`) or in the card unfolded in place (`panelLabel`).\n */\nexport function ArgumentCard({ id, title, icon, meta, mark, facts, hook, points, rule, details, panelLabel, more, actions, level = 3, linkAs, className, ...props }: ArgumentCardProps) {\n const H = `h${level}` as \"h2\" | \"h3\" | \"h4\";\n const fold = !more && panelLabel && (points || rule || (details && details.length > 0));\n return (\n <article className={cn(\"kz-block-card kz-arg\", className)} data-kz-light=\"\" aria-labelledby={id} {...props}>\n <div className=\"kz-arg-head\">\n {icon && (\n <span className=\"kz-block-icon\" aria-hidden>\n {icon}\n </span>\n )}\n <div className=\"kz-arg-id\">\n <H id={id} className=\"kz-arg-title\">\n {title}\n </H>\n {meta && <p className=\"kz-arg-meta\">{meta}</p>}\n </div>\n {mark && (\n <span className=\"kz-arg-mark\" aria-hidden>\n {mark}\n </span>\n )}\n </div>\n <p className=\"kz-arg-line\">\n <span className=\"kz-sr-only\">{hook.label} — </span>\n {hook.text}\n </p>\n {facts && facts.length > 0 && <Facts items={facts} />}\n {fold && (\n <details className=\"kz-arg-panel\">\n <summary>\n {panelLabel} <ChevronGlyph className=\"h-3.5 w-3.5\" />\n </summary>\n <div className=\"kz-arg-panel-body\">\n {points && (\n <div>\n <p className=\"kz-label\">{points.label}</p>\n <ul className=\"kz-list\">\n {points.items.map((item, i) => (\n <li key={i}>{item}</li>\n ))}\n </ul>\n </div>\n )}\n {details && details.length > 0 && (\n <dl>\n {details.map((d, i) => (\n <div key={i}>\n <dt className=\"kz-label\">{d.label}</dt>\n <dd>{d.text}</dd>\n </div>\n ))}\n </dl>\n )}\n {rule}\n </div>\n </details>\n )}\n {(more || actions) && (\n <div className=\"kz-arg-foot\">\n {more ? (\n <BlockLink href={more.href} linkAs={linkAs} className=\"kz-arg-more\" aria-describedby={id}>\n {more.label} <ArrowGlyph className=\"h-3.5 w-3.5\" />\n </BlockLink>\n ) : (\n <span />\n )}\n {actions}\n </div>\n )}\n </article>\n );\n}\n"
228
+ "content": "// Server-safe. A card that argues (blocks.css), compact: who (icon, name, facts) → the trap in one line → the way in.\n// The long version — what it does, the rule a bird stands for, the reasons — waits in a panel the reader opens (a\n// native <details>: keyboard, screen readers, no script). Cards of a row share their height; the panel and the foot\n// sit at the bottom, at the same place on every card.\nimport type * as React from \"react\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\nimport { ArrowGlyph, ChevronGlyph } from \"../glyphs\";\nimport { type CardHover, cardHoverClass } from \"../link-card/link-card\";\nimport { type Fact, Facts } from \"../page/page\";\n\nexport type ArgumentCardProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** The id of the title: the card's accessible name. */\n id: string;\n /** The title: the subject (a building block, a feature, a plan). */\n title: React.ReactNode;\n /** The icon tile (a signature icon): decorative. */\n icon?: React.ReactNode;\n /** A mono line under the title (a repository, a package). */\n meta?: React.ReactNode;\n /** Small, in the top corner: the bird whose rule it is (a `Mascot` of 40 px, still). Decorative. */\n mark?: React.ReactNode;\n /** Compact facts under the line: registry and version, a status said honestly. */\n facts?: readonly Fact[];\n /**\n * The problem in ONE line (≤ 90 characters): what a reader keeps if they read nothing else. `label` (`The trap`) is\n * read by assistive technology before it, not shown.\n */\n hook: { label: React.ReactNode; text: React.ReactNode };\n /** Unfolded only: what it does, as a few short points, with their label (`What it does`). */\n points?: { label: React.ReactNode; items: readonly React.ReactNode[] };\n /** Unfolded only: the rule told by a bird (a `MascotQuote`, `variant=\"inline\"`). */\n rule?: React.ReactNode;\n /** Unfolded only: the long version, as labelled paragraphs. */\n details?: readonly { label: React.ReactNode; text: React.ReactNode }[];\n /**\n * Without a detail page: the words that unfold the card in place (`Details`) — a native `<details>` holding the\n * points, the rule and the long version. Ignored with `more`: the detail page holds them.\n */\n panelLabel?: React.ReactNode;\n /** The way in: a detail page — one short action at the foot (`Details`). With it, the card is only its face. */\n more?: { href: string; label: React.ReactNode };\n /** Discreet secondary actions at the foot, on the right (an `ActionBar` with `variant=\"icons\"`). */\n actions?: React.ReactNode;\n /**\n * The title's level.\n * @default 3\n */\n level?: 2 | 3 | 4;\n /** The app's link component for internal addresses (Next's `Link`); an `<a>` otherwise. */\n linkAs?: LinkComponent;\n /** The hover of the card: `light` (the pointer's light, the default) or `quiet` (no glow, a lift, a ring on focus). */\n hover?: CardHover;\n};\n\n/**\n * A card that argues, compact (about a third of a full card): who (icon, name, meta; the bird small in the corner) →\n * the problem in one line → compact facts → one short action. What it does, the bird's rule and the long version are\n * never on the face: they live on the detail page (`more`) or in the card unfolded in place (`panelLabel`).\n */\nexport function ArgumentCard({ id, title, icon, meta, mark, facts, hook, points, rule, details, panelLabel, more, actions, level = 3, linkAs, hover, className, ...props }: ArgumentCardProps) {\n const H = `h${level}` as \"h2\" | \"h3\" | \"h4\";\n const fold = !more && panelLabel && (points || rule || (details && details.length > 0));\n return (\n <article className={cn(\"kz-block-card kz-arg\", cardHoverClass(hover), className)} data-kz-light=\"\" aria-labelledby={id} {...props}>\n <div className=\"kz-arg-head\">\n {icon && (\n <span className=\"kz-block-icon\" aria-hidden>\n {icon}\n </span>\n )}\n <div className=\"kz-arg-id\">\n <H id={id} className=\"kz-arg-title\">\n {title}\n </H>\n {meta && <p className=\"kz-arg-meta\">{meta}</p>}\n </div>\n {mark && (\n <span className=\"kz-arg-mark\" aria-hidden>\n {mark}\n </span>\n )}\n </div>\n <p className=\"kz-arg-line\">\n <span className=\"kz-sr-only\">{hook.label} — </span>\n {hook.text}\n </p>\n {facts && facts.length > 0 && <Facts items={facts} />}\n {fold && (\n <details className=\"kz-arg-panel\">\n <summary>\n {panelLabel} <ChevronGlyph className=\"h-3.5 w-3.5\" />\n </summary>\n <div className=\"kz-arg-panel-body\">\n {points && (\n <div>\n <p className=\"kz-label\">{points.label}</p>\n <ul className=\"kz-list\">\n {points.items.map((item, i) => (\n <li key={i}>{item}</li>\n ))}\n </ul>\n </div>\n )}\n {details && details.length > 0 && (\n <dl>\n {details.map((d, i) => (\n <div key={i}>\n <dt className=\"kz-label\">{d.label}</dt>\n <dd>{d.text}</dd>\n </div>\n ))}\n </dl>\n )}\n {rule}\n </div>\n </details>\n )}\n {(more || actions) && (\n <div className=\"kz-arg-foot\">\n {more ? (\n <BlockLink href={more.href} linkAs={linkAs} className=\"kz-arg-more\" aria-describedby={id}>\n {more.label} <ArrowGlyph className=\"h-3.5 w-3.5\" />\n </BlockLink>\n ) : (\n <span />\n )}\n {actions}\n </div>\n )}\n </article>\n );\n}\n"
205
229
  },
206
230
  {
207
231
  "path": "argument-card/index.ts",
@@ -209,7 +233,7 @@
209
233
  },
210
234
  {
211
235
  "path": "argument-card/meta.ts",
212
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Argument card\",\n summary:\n \"A compact card that argues — about a third of a full card: who (icon, name, the bird in the corner), the trap in one line, facts and one short action; the long version lives on a detail page or unfolds in place.\",\n status: \"beta\",\n category: \"blocks\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"A block of a public site's page; an app screen on a phone is built from Screen, Header and Card.\", use: \"card\" }, desktop: { status: \"n/a\", reason: \"A public site's page, not a window of the Mac app.\" } },\n whenToUse: [\n \"The reasons of a product page, the building blocks of an open-source page, the options of a decision.\",\n \"When each subject has a problem to state before its solution, and a reader who scans before reading.\",\n ],\n whenNotToUse: [\n { when: \"When the card only leads somewhere.\", use: \"link-card\" },\n { when: \"For the decisions behind a choice, compared side by side.\", use: \"decision-list\" },\n ],\n bestPractices: [\n \"The hook is ONE line (≤ 90 characters, regular weight) a reader keeps in five seconds — never three bold lines.\",\n \"What it does, the bird's quote and the long version are never on the face: a detail page (`more`), or `panelLabel`.\",\n \"One idea per card; one bird per idea: small in the corner (`mark`), its quote (`rule`) where the card unfolds.\",\n \"Give each subject its hue with `--kz-hue` on the card (or a wrapper); the brand's accent otherwise.\",\n \"Secondary actions as an icon `ActionBar` in `actions`; the way in as `more`.\",\n ],\n accessibility: {\n keyboard: [{ keys: \"Enter / Space\", action: \"On the panel's summary, opens or closes the long version.\" }],\n notes: [\n \"An `article` named by its title; the hook's label is read before the line (visually hidden); the fold is a native `details` element.\",\n \"The way in is described by the card's title (`aria-describedby`), so several `Read more` links stay distinct.\",\n \"The lift, the light and the panel's chevron stop under `prefers-reduced-motion`.\",\n ],\n },\n related: [\"card-grid\", \"mascot-quote\", \"action-bar\"],\n web: {\n imports: [\"ArgumentCard\"],\n examples: [\n { name: \"building-block\", title: \"A building block\", description: \"Compact: the duck in the corner, the trap in one line, facts, one short action and an icon action.\" },\n { name: \"unfold\", title: \"Unfolded in place\", description: \"No detail page: `panelLabel` unfolds what it does, the cost and the pigeon's rule.\" },\n { name: \"grid\", title: \"Three reasons\", description: \"A product's reasons side by side in a grid: the hook and the way in, no panel.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
236
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Argument card\",\n summary:\n \"A compact card that argues — about a third of a full card: who (icon, name, the bird in the corner), the trap in one line, facts and one short action; the long version lives on a detail page or unfolds in place.\",\n status: \"beta\",\n category: \"blocks\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"A block of a public site's page; an app screen on a phone is built from Screen, Header and Card.\", use: \"card\" }, desktop: { status: \"n/a\", reason: \"A public site's page, not a window of the Mac app.\" } },\n whenToUse: [\n \"The reasons of a product page, the building blocks of an open-source page, the options of a decision.\",\n \"When each subject has a problem to state before its solution, and a reader who scans before reading.\",\n ],\n whenNotToUse: [\n { when: \"When the card only leads somewhere.\", use: \"link-card\" },\n { when: \"For the decisions behind a choice, compared side by side.\", use: \"decision-list\" },\n ],\n bestPractices: [\n \"The hook is ONE line (≤ 90 characters, regular weight) a reader keeps in five seconds — never three bold lines.\",\n \"What it does, the bird's quote and the long version are never on the face: a detail page (`more`), or `panelLabel`.\",\n \"One idea per card; one bird per idea: small in the corner (`mark`), its quote (`rule`) where the card unfolds.\",\n \"Give each subject its hue with `--kz-hue` on the card (or a wrapper); the brand's accent otherwise.\",\n \"Secondary actions as an icon `ActionBar` in `actions`; the way in as `more`.\",\n \"`hover=\\\"quiet\\\"` for reasons read as prose (a product's argument, a policy): no glow, a lift and a ring on focus. The default `light` keeps the pointer's light.\",\n ],\n accessibility: {\n keyboard: [{ keys: \"Enter / Space\", action: \"On the panel's summary, opens or closes the long version.\" }],\n notes: [\n \"An `article` named by its title; the hook's label is read before the line (visually hidden); the fold is a native `details` element.\",\n \"The way in is described by the card's title (`aria-describedby`), so several `Read more` links stay distinct.\",\n \"The lift, the light and the panel's chevron stop under `prefers-reduced-motion`; the quiet hover keeps its surface and ring, and never lifts.\",\n ],\n },\n related: [\"card-grid\", \"mascot-quote\", \"action-bar\"],\n web: {\n imports: [\"ArgumentCard\"],\n examples: [\n { name: \"building-block\", title: \"A building block\", description: \"Compact: the duck in the corner, the trap in one line, facts, one short action and an icon action.\" },\n { name: \"unfold\", title: \"Unfolded in place\", description: \"No detail page: `panelLabel` unfolds what it does, the cost and the pigeon's rule.\" },\n { name: \"grid\", title: \"Three reasons\", description: \"A product's reasons side by side in a grid: the hook and the way in, no panel.\" },\n { name: \"hover-light\", title: \"The light hover\", description: \"`hover=\\\"light\\\"`, the default: the pointer's light and the coloured lift, said explicitly.\" },\n { name: \"hover-quiet\", title: \"The quiet hover\", description: \"`hover=\\\"quiet\\\"` on a grid of reasons: no glow, a lift, a ring on focus.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
213
237
  },
214
238
  {
215
239
  "path": "block-link.tsx",
@@ -221,12 +245,13 @@
221
245
  },
222
246
  {
223
247
  "path": "meta.ts",
224
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
248
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
225
249
  }
226
250
  ],
227
251
  "dependencies": [],
228
252
  "registryDependencies": [
229
253
  "cn",
254
+ "link-card",
230
255
  "page"
231
256
  ],
232
257
  "demo": {
@@ -273,6 +298,13 @@
273
298
  "description": "Compact facts under the line: registry and version, a status said honestly.",
274
299
  "required": false
275
300
  },
301
+ {
302
+ "name": "hover",
303
+ "type": "CardHover",
304
+ "default": null,
305
+ "description": "The hover of the card: `light` (the pointer's light, the default) or `quiet` (no glow, a lift, a ring on focus).",
306
+ "required": false
307
+ },
276
308
  {
277
309
  "name": "icon",
278
310
  "type": "ReactNode",
@@ -177,7 +177,7 @@
177
177
  "files": [
178
178
  {
179
179
  "path": "audio-player/audio-player.tsx",
180
- "content": "// Client. An `<audio>` with a waveform you can seek on: the bars are the sound's peaks (given by the server, or\n// computed here from the file with Web Audio when `computePeaks` — once, on demand), the played part in the accent.\n// The waveform is a `slider` (arrows ± 5 s, Page keys ± 30 s, Home / End) whose value is read as a time; the play\n// button and the speed are named by props. No animation but the played part's colour, and it pauses off screen.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { PauseGlyph, PlayGlyph } from \"../glyphs\";\nimport { clamp, formatClock, playSafely, useInView } from \"../media\";\n\nexport const audioPlayer = tv({\n slots: {\n root: \"flex w-full items-center gap-3 rounded-xl border border-border-default bg-surface-1 p-3\",\n play:\n \"inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-accent text-on-accent transition-colors hover:bg-accent-hover \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-1\",\n body: \"flex min-w-0 flex-1 flex-col gap-1\",\n title: \"truncate text-sm font-semibold text-fg\",\n wave:\n \"relative flex h-10 w-full cursor-pointer touch-none items-center gap-[2px] rounded-md \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n bar: \"min-w-[2px] flex-1 rounded-full bg-border-strong data-[played]:bg-accent\",\n meta: \"flex items-center justify-between font-mono text-[11px] tabular-nums text-fg-secondary\",\n speed:\n \"shrink-0 rounded-md px-2 py-1 text-xs font-semibold tabular-nums text-fg-secondary hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n});\n\nexport type AudioPlayerLabels = {\n play: string;\n pause: string;\n /** The waveform slider (\"Seek\"). */\n seek: string;\n /** The speed button: `(rate) => \"Speed \" + rate + \"×\"`. */\n speed: (rate: number) => string;\n /** The slider's value: `(time, duration) => \"0:42 of 3:10\"`. */\n time: (time: string, duration: string) => string;\n};\n\nexport const audioPlayerLabels: AudioPlayerLabels = {\n play: \"Play\",\n pause: \"Pause\",\n seek: \"Seek\",\n speed: (rate) => `Speed ${rate}×`,\n time: (time, duration) => `${time} of ${duration}`,\n};\n\nexport type AudioPlayerProps = {\n /** The address of the sound. */\n src: string;\n /** What it is (\"Voice note — Eric, 0:42\"): shown, and the accessible name of the player. */\n title: string;\n /** The peaks, between 0 and 1, as many as you like (they are resampled to `bars`). */\n peaks?: readonly number[];\n /** Without `peaks`: fetches the file and computes them with Web Audio at the first play (same-origin or CORS). */\n computePeaks?: boolean;\n /** How many bars. Default 48. */\n bars?: number;\n /** The speeds of the speed button. Default `[1, 1.5, 2]`; `[]` hides it. */\n playbackRates?: readonly number[];\n /** Pauses when it leaves the screen. Default `true`. */\n pauseWhenHidden?: boolean;\n /** The length, in seconds, before the metadata is loaded (from the server). */\n duration?: number;\n /** Called when playback starts, pauses, ends. */\n onPlayingChange?: (playing: boolean) => void;\n /** The words, passed translated; English by default. */\n labels?: Partial<AudioPlayerLabels>;\n /** Classes merged on the root. */\n className?: string;\n};\n\n/** Resamples peaks to `count` bars (the maximum of each slice), normalised to the loudest. */\nexport function resamplePeaks(peaks: readonly number[], count: number): number[] {\n if (peaks.length === 0) return Array.from({ length: count }, () => 0.15);\n const out = Array.from({ length: count }, (_, i) => {\n const from = Math.floor((i / count) * peaks.length);\n const to = Math.max(from + 1, Math.floor(((i + 1) / count) * peaks.length));\n return Math.max(...peaks.slice(from, to).map(Math.abs));\n });\n const loudest = Math.max(...out, 0.0001);\n return out.map((v) => Math.max(0.08, v / loudest));\n}\n\n/** The peaks of a sound file, computed with Web Audio (`count` of them). */\nexport async function computeAudioPeaks(src: string, count = 200, signal?: AbortSignal): Promise<number[]> {\n const response = await fetch(src, { signal });\n const data = await response.arrayBuffer();\n const Context = window.AudioContext ?? (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;\n const context = new Context();\n try {\n const buffer = await context.decodeAudioData(data);\n const channel = buffer.getChannelData(0);\n const size = Math.max(1, Math.floor(channel.length / count));\n return Array.from({ length: count }, (_, i) => {\n let peak = 0;\n for (let j = i * size; j < Math.min(channel.length, (i + 1) * size); j += 1) peak = Math.max(peak, Math.abs(channel[j]!));\n return peak;\n });\n } finally {\n void context.close();\n }\n}\n\n/** A sound with a seekable waveform: play / pause, time, speed — a voice note, a podcast clip, a track. */\nexport function AudioPlayer({\n src,\n title,\n peaks: given,\n computePeaks,\n bars = 48,\n playbackRates = [1, 1.5, 2],\n pauseWhenHidden = true,\n duration: knownDuration = 0,\n onPlayingChange,\n labels: words,\n className,\n}: AudioPlayerProps) {\n const labels = { ...audioPlayerLabels, ...words };\n const s = audioPlayer();\n const audio = React.useRef<HTMLAudioElement>(null);\n const root = React.useRef<HTMLDivElement>(null);\n const wave = React.useRef<HTMLDivElement>(null);\n const dragging = React.useRef(false);\n const [playing, setPlaying] = React.useState(false);\n const [time, setTime] = React.useState(0);\n const [duration, setDuration] = React.useState(knownDuration);\n const [rate, setRate] = React.useState(playbackRates[0] ?? 1);\n const [computed, setComputed] = React.useState<number[] | null>(null);\n const [wantPeaks, setWantPeaks] = React.useState(false);\n\n React.useEffect(() => {\n if (given || !computePeaks || !wantPeaks) return;\n const controller = new AbortController();\n computeAudioPeaks(src, 200, controller.signal).then(setComputed, () => undefined);\n return () => controller.abort();\n }, [given, computePeaks, wantPeaks, src]);\n\n useInView(root, (visible) => !visible && audio.current && !audio.current.paused && audio.current.pause(), pauseWhenHidden);\n\n const shape = React.useMemo(() => resamplePeaks(given ?? computed ?? [], bars), [given, computed, bars]);\n const progress = duration > 0 ? time / duration : 0;\n\n const seek = (to: number) => {\n if (!audio.current) return;\n const next = clamp(to, 0, duration || audio.current.duration || 0);\n audio.current.currentTime = next;\n setTime(next);\n };\n const at = (clientX: number) => {\n const box = wave.current?.getBoundingClientRect();\n return box && box.width ? ((clientX - box.left) / box.width) * duration : 0;\n };\n const toggle = () => {\n setWantPeaks(true);\n if (!audio.current) return;\n if (audio.current.paused) playSafely(audio.current);\n else audio.current.pause();\n };\n\n return (\n <div ref={root} role=\"group\" aria-label={title} className={s.root({ className })}>\n <audio\n ref={audio}\n src={src}\n preload=\"metadata\"\n onPlay={() => {\n setPlaying(true);\n onPlayingChange?.(true);\n }}\n onPause={() => {\n setPlaying(false);\n onPlayingChange?.(false);\n }}\n onTimeUpdate={(event) => setTime(event.currentTarget.currentTime)}\n onDurationChange={(event) => Number.isFinite(event.currentTarget.duration) && setDuration(event.currentTarget.duration)}\n />\n <button type=\"button\" className={s.play()} aria-label={playing ? labels.pause : labels.play} onClick={toggle}>\n {playing ? <PauseGlyph className=\"h-5 w-5\" /> : <PlayGlyph className=\"ml-0.5 h-5 w-5\" />}\n </button>\n <div className={s.body()}>\n <p className={s.title()} aria-hidden>\n {title}\n </p>\n <div\n ref={wave}\n role=\"slider\"\n tabIndex={0}\n aria-label={labels.seek}\n aria-valuemin={0}\n aria-valuemax={Math.round(duration)}\n aria-valuenow={Math.round(time)}\n aria-valuetext={labels.time(formatClock(time), formatClock(duration))}\n className={s.wave()}\n onPointerDown={(event) => {\n dragging.current = true;\n wave.current?.setPointerCapture?.(event.pointerId);\n seek(at(event.clientX));\n }}\n onPointerMove={(event) => dragging.current && seek(at(event.clientX))}\n onPointerUp={() => {\n dragging.current = false;\n }}\n onKeyDown={(event) => {\n const moves: Record<string, number> = {\n ArrowLeft: time - 5,\n ArrowDown: time - 5,\n ArrowRight: time + 5,\n ArrowUp: time + 5,\n PageDown: time - 30,\n PageUp: time + 30,\n Home: 0,\n End: duration,\n };\n const to = moves[event.key];\n if (to === undefined) return;\n event.preventDefault();\n seek(to);\n }}\n >\n {shape.map((peak, i) => (\n <span key={i} aria-hidden data-played={(i + 0.5) / bars <= progress ? \"\" : undefined} className={s.bar()} style={{ height: `${Math.round(peak * 100)}%` }} />\n ))}\n </div>\n <div className={s.meta()} aria-hidden>\n <span>{formatClock(time)}</span>\n <span>{formatClock(duration)}</span>\n </div>\n </div>\n {playbackRates.length > 1 && (\n <button\n type=\"button\"\n className={s.speed()}\n aria-label={labels.speed(rate)}\n onClick={() => {\n const next = playbackRates[(playbackRates.indexOf(rate) + 1) % playbackRates.length] ?? 1;\n if (audio.current) audio.current.playbackRate = next;\n setRate(next);\n }}\n >\n {rate}×\n </button>\n )}\n </div>\n );\n}\n"
180
+ "content": "// Client. An `<audio>` with a waveform you can seek on: the bars are the sound's peaks (given by the server, or\n// computed here from the file with Web Audio when `computePeaks` — once, on demand), the played part in the accent.\n// The waveform is a `slider` (arrows ± 5 s, Page keys ± 30 s, Home / End) whose value is read as a time; the play\n// button and the speed are named by props. No animation but the played part's colour, and it pauses off screen.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { PauseGlyph, PlayGlyph } from \"../glyphs\";\nimport { clamp, formatClock, playSafely, useInView } from \"../media\";\nimport { resamplePeaks } from \"../waveform/bars\";\n\nexport { resamplePeaks };\n\nexport const audioPlayer = tv({\n slots: {\n root: \"flex w-full items-center gap-3 rounded-xl border border-border-default bg-surface-1 p-3\",\n play:\n \"inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-accent text-on-accent transition-colors hover:bg-accent-hover \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-1\",\n body: \"flex min-w-0 flex-1 flex-col gap-1\",\n title: \"truncate text-sm font-semibold text-fg\",\n wave:\n \"relative flex h-10 w-full cursor-pointer touch-none items-center gap-[2px] rounded-md \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n bar: \"min-w-[2px] flex-1 rounded-full bg-border-strong data-[played]:bg-accent\",\n meta: \"flex items-center justify-between font-mono text-[11px] tabular-nums text-fg-secondary\",\n speed:\n \"shrink-0 rounded-md px-2 py-1 text-xs font-semibold tabular-nums text-fg-secondary hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n});\n\nexport type AudioPlayerLabels = {\n play: string;\n pause: string;\n /** The waveform slider (\"Seek\"). */\n seek: string;\n /** The speed button: `(rate) => \"Speed \" + rate + \"×\"`. */\n speed: (rate: number) => string;\n /** The slider's value: `(time, duration) => \"0:42 of 3:10\"`. */\n time: (time: string, duration: string) => string;\n};\n\nexport const audioPlayerLabels: AudioPlayerLabels = {\n play: \"Play\",\n pause: \"Pause\",\n seek: \"Seek\",\n speed: (rate) => `Speed ${rate}×`,\n time: (time, duration) => `${time} of ${duration}`,\n};\n\nexport type AudioPlayerProps = {\n /** The address of the sound. */\n src: string;\n /** What it is (\"Voice note — Eric, 0:42\"): shown, and the accessible name of the player. */\n title: string;\n /** The peaks, between 0 and 1, as many as you like (they are resampled to `bars`). */\n peaks?: readonly number[];\n /** Without `peaks`: fetches the file and computes them with Web Audio at the first play (same-origin or CORS). */\n computePeaks?: boolean;\n /** How many bars. Default 48. */\n bars?: number;\n /** The speeds of the speed button. Default `[1, 1.5, 2]`; `[]` hides it. */\n playbackRates?: readonly number[];\n /** Pauses when it leaves the screen. Default `true`. */\n pauseWhenHidden?: boolean;\n /** The length, in seconds, before the metadata is loaded (from the server). */\n duration?: number;\n /** Called when playback starts, pauses, ends. */\n onPlayingChange?: (playing: boolean) => void;\n /** The words, passed translated; English by default. */\n labels?: Partial<AudioPlayerLabels>;\n /** Classes merged on the root. */\n className?: string;\n};\n\n/** The peaks of a sound file, computed with Web Audio (`count` of them). */\nexport async function computeAudioPeaks(src: string, count = 200, signal?: AbortSignal): Promise<number[]> {\n const response = await fetch(src, { signal });\n const data = await response.arrayBuffer();\n const Context = window.AudioContext ?? (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;\n const context = new Context();\n try {\n const buffer = await context.decodeAudioData(data);\n const channel = buffer.getChannelData(0);\n const size = Math.max(1, Math.floor(channel.length / count));\n return Array.from({ length: count }, (_, i) => {\n let peak = 0;\n for (let j = i * size; j < Math.min(channel.length, (i + 1) * size); j += 1) peak = Math.max(peak, Math.abs(channel[j]!));\n return peak;\n });\n } finally {\n void context.close();\n }\n}\n\n/** A sound with a seekable waveform: play / pause, time, speed — a voice note, a podcast clip, a track. */\nexport function AudioPlayer({\n src,\n title,\n peaks: given,\n computePeaks,\n bars = 48,\n playbackRates = [1, 1.5, 2],\n pauseWhenHidden = true,\n duration: knownDuration = 0,\n onPlayingChange,\n labels: words,\n className,\n}: AudioPlayerProps) {\n const labels = { ...audioPlayerLabels, ...words };\n const s = audioPlayer();\n const audio = React.useRef<HTMLAudioElement>(null);\n const root = React.useRef<HTMLDivElement>(null);\n const wave = React.useRef<HTMLDivElement>(null);\n const dragging = React.useRef(false);\n const [playing, setPlaying] = React.useState(false);\n const [time, setTime] = React.useState(0);\n const [duration, setDuration] = React.useState(knownDuration);\n const [rate, setRate] = React.useState(playbackRates[0] ?? 1);\n const [computed, setComputed] = React.useState<number[] | null>(null);\n const [wantPeaks, setWantPeaks] = React.useState(false);\n\n React.useEffect(() => {\n if (given || !computePeaks || !wantPeaks) return;\n const controller = new AbortController();\n computeAudioPeaks(src, 200, controller.signal).then(setComputed, () => undefined);\n return () => controller.abort();\n }, [given, computePeaks, wantPeaks, src]);\n\n useInView(root, (visible) => !visible && audio.current && !audio.current.paused && audio.current.pause(), pauseWhenHidden);\n\n const shape = React.useMemo(() => resamplePeaks(given ?? computed ?? [], bars), [given, computed, bars]);\n const progress = duration > 0 ? time / duration : 0;\n\n const seek = (to: number) => {\n if (!audio.current) return;\n const next = clamp(to, 0, duration || audio.current.duration || 0);\n audio.current.currentTime = next;\n setTime(next);\n };\n const at = (clientX: number) => {\n const box = wave.current?.getBoundingClientRect();\n return box && box.width ? ((clientX - box.left) / box.width) * duration : 0;\n };\n const toggle = () => {\n setWantPeaks(true);\n if (!audio.current) return;\n if (audio.current.paused) playSafely(audio.current);\n else audio.current.pause();\n };\n\n return (\n <div ref={root} role=\"group\" aria-label={title} className={s.root({ className })}>\n <audio\n ref={audio}\n src={src}\n preload=\"metadata\"\n onPlay={() => {\n setPlaying(true);\n onPlayingChange?.(true);\n }}\n onPause={() => {\n setPlaying(false);\n onPlayingChange?.(false);\n }}\n onTimeUpdate={(event) => setTime(event.currentTarget.currentTime)}\n onDurationChange={(event) => Number.isFinite(event.currentTarget.duration) && setDuration(event.currentTarget.duration)}\n />\n <button type=\"button\" className={s.play()} aria-label={playing ? labels.pause : labels.play} onClick={toggle}>\n {playing ? <PauseGlyph className=\"h-5 w-5\" /> : <PlayGlyph className=\"ml-0.5 h-5 w-5\" />}\n </button>\n <div className={s.body()}>\n <p className={s.title()} aria-hidden>\n {title}\n </p>\n <div\n ref={wave}\n role=\"slider\"\n tabIndex={0}\n aria-label={labels.seek}\n aria-valuemin={0}\n aria-valuemax={Math.round(duration)}\n aria-valuenow={Math.round(time)}\n aria-valuetext={labels.time(formatClock(time), formatClock(duration))}\n className={s.wave()}\n onPointerDown={(event) => {\n dragging.current = true;\n wave.current?.setPointerCapture?.(event.pointerId);\n seek(at(event.clientX));\n }}\n onPointerMove={(event) => dragging.current && seek(at(event.clientX))}\n onPointerUp={() => {\n dragging.current = false;\n }}\n onKeyDown={(event) => {\n const moves: Record<string, number> = {\n ArrowLeft: time - 5,\n ArrowDown: time - 5,\n ArrowRight: time + 5,\n ArrowUp: time + 5,\n PageDown: time - 30,\n PageUp: time + 30,\n Home: 0,\n End: duration,\n };\n const to = moves[event.key];\n if (to === undefined) return;\n event.preventDefault();\n seek(to);\n }}\n >\n {shape.map((peak, i) => (\n <span key={i} aria-hidden data-played={(i + 0.5) / bars <= progress ? \"\" : undefined} className={s.bar()} style={{ height: `${Math.round(peak * 100)}%` }} />\n ))}\n </div>\n <div className={s.meta()} aria-hidden>\n <span>{formatClock(time)}</span>\n <span>{formatClock(duration)}</span>\n </div>\n </div>\n {playbackRates.length > 1 && (\n <button\n type=\"button\"\n className={s.speed()}\n aria-label={labels.speed(rate)}\n onClick={() => {\n const next = playbackRates[(playbackRates.indexOf(rate) + 1) % playbackRates.length] ?? 1;\n if (audio.current) audio.current.playbackRate = next;\n setRate(next);\n }}\n >\n {rate}×\n </button>\n )}\n </div>\n );\n}\n"
181
181
  },
182
182
  {
183
183
  "path": "audio-player/index.ts",
@@ -197,13 +197,15 @@
197
197
  },
198
198
  {
199
199
  "path": "meta.ts",
200
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
200
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
201
201
  }
202
202
  ],
203
203
  "dependencies": [
204
204
  "tailwind-variants@^3.3.1"
205
205
  ],
206
- "registryDependencies": [],
206
+ "registryDependencies": [
207
+ "waveform"
208
+ ],
207
209
  "demo": {
208
210
  "path": "examples/audio-player/voice-note.tsx",
209
211
  "content": "import { AudioPlayer } from \"@krizaka/ui/audio-player\";\n\n// Peaks computed once on the server: 64 values between 0 and 1.\nconst PEAKS = Array.from({ length: 64 }, (_, i) => 0.2 + Math.abs(Math.sin(i * 0.45) * Math.cos(i * 0.13)) * 0.8);\n\nexport default function AudioPlayerVoiceNote() {\n return (\n <div className=\"w-full max-w-md\">\n <AudioPlayer title=\"Voice note from Eric\" src=\"https://www.krizaka.com/assets/orochia/tour/elena-live.mp4\" peaks={PEAKS} duration={42} />\n </div>\n );\n}\n"
@@ -168,7 +168,7 @@
168
168
  },
169
169
  {
170
170
  "path": "meta.ts",
171
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
171
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
172
172
  }
173
173
  ],
174
174
  "dependencies": [
@@ -472,7 +472,7 @@
472
472
  },
473
473
  {
474
474
  "path": "meta.ts",
475
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
475
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
476
476
  }
477
477
  ],
478
478
  "dependencies": [