@krizaka/ui 3.22.0 → 3.23.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 (447) hide show
  1. package/README.md +4 -0
  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-3IN6TOPM.js → chunk-6ZYRUOTA.js} +6 -3
  30. package/dist/chunk-6ZYRUOTA.js.map +1 -0
  31. package/dist/chunk-APNIVNIX.js +54 -0
  32. package/dist/chunk-APNIVNIX.js.map +1 -0
  33. package/dist/{chunk-FKBHWS4N.js → chunk-CEJDH6SA.js} +2 -2
  34. package/dist/{chunk-OWC2LZD2.js → chunk-CHOJUCVE.js} +6 -3
  35. package/dist/chunk-CHOJUCVE.js.map +1 -0
  36. package/dist/{chunk-3BC4XQ6C.js → chunk-DZU6RMYD.js} +6 -3
  37. package/dist/chunk-DZU6RMYD.js.map +1 -0
  38. package/dist/{chunk-R6NK6ZNY.js → chunk-F7FVPPBB.js} +4 -4
  39. package/dist/{chunk-5Z7FWCV2.js → chunk-HBZPJ3EP.js} +2 -2
  40. package/dist/{chunk-C2BRIFJG.js → chunk-HJFRHYCI.js} +2 -2
  41. package/dist/{chunk-ZNMFG62E.js → chunk-KH3JOEPY.js} +3 -2
  42. package/dist/chunk-KH3JOEPY.js.map +1 -0
  43. package/dist/{chunk-BTMFZ66D.js → chunk-KM6VAKIE.js} +2 -2
  44. package/dist/{chunk-DGQMDJXK.js → chunk-LZNZLCOE.js} +2 -2
  45. package/dist/{chunk-AQSZJJAJ.js → chunk-MQPC3P4T.js} +2 -2
  46. package/dist/{chunk-2FRWNSRO.js → chunk-NSLXEDIS.js} +3 -2
  47. package/dist/chunk-NSLXEDIS.js.map +1 -0
  48. package/dist/{chunk-4LOMIQVX.js → chunk-W675RFOA.js} +3 -2
  49. package/dist/chunk-W675RFOA.js.map +1 -0
  50. package/dist/{chunk-G3ETON2A.js → chunk-WU4VLSEA.js} +3 -2
  51. package/dist/chunk-WU4VLSEA.js.map +1 -0
  52. package/dist/{chunk-IZ7MV6HM.js → chunk-XVFQZ3ZG.js} +3 -2
  53. package/dist/chunk-XVFQZ3ZG.js.map +1 -0
  54. package/dist/cn.js +1 -1
  55. package/dist/code-block.js +2 -2
  56. package/dist/code-block.js.map +1 -1
  57. package/dist/command.js +3 -3
  58. package/dist/comparison-table.js +1 -1
  59. package/dist/confirm-button.js +2 -2
  60. package/dist/continue-prompt.js +3 -3
  61. package/dist/continue-prompt.js.map +1 -1
  62. package/dist/cta-panel.js +1 -1
  63. package/dist/data-table.js +4 -4
  64. package/dist/date-picker.js +2 -2
  65. package/dist/decision-list.js +1 -1
  66. package/dist/dialog.js +3 -2
  67. package/dist/dialog.js.map +1 -1
  68. package/dist/docs-nav.d.ts +36 -0
  69. package/dist/docs-nav.js +44 -0
  70. package/dist/docs-nav.js.map +1 -0
  71. package/dist/dropdown-menu.js +1 -1
  72. package/dist/error-page.js +1 -1
  73. package/dist/field.js +1 -1
  74. package/dist/filter-bar.js +2 -2
  75. package/dist/force-update-screen.js +2 -1
  76. package/dist/force-update-screen.js.map +1 -1
  77. package/dist/gallery-page.js +1 -1
  78. package/dist/genui.d.ts +3 -1
  79. package/dist/genui.js +33 -6
  80. package/dist/genui.js.map +1 -1
  81. package/dist/glow-card.js +1 -1
  82. package/dist/goal-gauge.js +1 -1
  83. package/dist/how-it-works-page.js +1 -1
  84. package/dist/image-cropper.js +3 -3
  85. package/dist/index.js +3 -3
  86. package/dist/landscape.js +1 -1
  87. package/dist/link-card.d.ts +9 -2
  88. package/dist/link-card.js +5 -44
  89. package/dist/link-card.js.map +1 -1
  90. package/dist/longform-page.js +1 -1
  91. package/dist/maintenance-screen.js +2 -1
  92. package/dist/maintenance-screen.js.map +1 -1
  93. package/dist/map.js +3 -3
  94. package/dist/map.js.map +1 -1
  95. package/dist/markdown.js +2 -1
  96. package/dist/markdown.js.map +1 -1
  97. package/dist/marquee.js +1 -1
  98. package/dist/mascot-quote.js +1 -1
  99. package/dist/match-dialog.js +2 -1
  100. package/dist/match-dialog.js.map +1 -1
  101. package/dist/media-grid.d.ts +26 -4
  102. package/dist/media-grid.js +144 -77
  103. package/dist/media-grid.js.map +1 -1
  104. package/dist/native/map.js +3 -3
  105. package/dist/native/map.js.map +1 -1
  106. package/dist/native.d.ts +18 -1
  107. package/dist/native.js +105 -24
  108. package/dist/native.js.map +1 -1
  109. package/dist/nav-bar.js +1 -1
  110. package/dist/page-hero.js +6 -6
  111. package/dist/page.js +2 -2
  112. package/dist/popover.js +1 -1
  113. package/dist/pricing-page.js +1 -1
  114. package/dist/product-page.js +1 -1
  115. package/dist/profile-card.js +1 -1
  116. package/dist/promise-card.js +1 -1
  117. package/dist/radio-group.js +2 -2
  118. package/dist/rail-nav.d.ts +149 -0
  119. package/dist/rail-nav.js +177 -0
  120. package/dist/rail-nav.js.map +1 -0
  121. package/dist/scroll-stage.js +1 -1
  122. package/dist/settings-layout.d.ts +75 -62
  123. package/dist/settings-layout.js +39 -6
  124. package/dist/settings-layout.js.map +1 -1
  125. package/dist/showcase.js +1 -1
  126. package/dist/site-footer.d.ts +48 -67
  127. package/dist/site-footer.js +34 -10
  128. package/dist/site-footer.js.map +1 -1
  129. package/dist/slider.js +2 -2
  130. package/dist/stage-banner.js +1 -1
  131. package/dist/step-showcase.js +1 -1
  132. package/dist/tabs.js +1 -1
  133. package/dist/tabs.js.map +1 -1
  134. package/dist/terminal-replay.js +1 -1
  135. package/dist/theme.js +2 -1
  136. package/dist/theme.js.map +1 -1
  137. package/dist/toast.js +1 -1
  138. package/dist/toc.js +1 -1
  139. package/dist/tooltip.js +1 -1
  140. package/dist/update-prompt.js +2 -1
  141. package/dist/update-prompt.js.map +1 -1
  142. package/dist/vertical-video.js +2 -2
  143. package/dist/video-player.js +3 -3
  144. package/dist/waveform.d.ts +93 -0
  145. package/dist/waveform.js +95 -0
  146. package/dist/waveform.js.map +1 -0
  147. package/package.json +81 -10
  148. package/registry/achievement.json +1 -1
  149. package/registry/action-bar.json +1 -1
  150. package/registry/activity-feed.json +1 -1
  151. package/registry/alert.json +1 -1
  152. package/registry/app-header.json +202 -0
  153. package/registry/app-shell.json +33 -9
  154. package/registry/argument-card.json +37 -5
  155. package/registry/audio-player.json +5 -3
  156. package/registry/auth-layout.json +1 -1
  157. package/registry/avatar.json +1 -1
  158. package/registry/badge.json +1 -1
  159. package/registry/button.json +3 -2
  160. package/registry/captcha.json +300 -0
  161. package/registry/card-grid.json +27 -3
  162. package/registry/card.json +1 -1
  163. package/registry/catalogue-page.json +1 -1
  164. package/registry/changelog-page.json +11 -7
  165. package/registry/chat.json +1 -1
  166. package/registry/checkbox.json +1 -1
  167. package/registry/chip-link.json +1 -1
  168. package/registry/chip.json +1 -1
  169. package/registry/choice-grid.json +1 -1
  170. package/registry/cn.json +1 -1
  171. package/registry/code-block.json +1 -1
  172. package/registry/combobox.json +1 -1
  173. package/registry/command.json +1 -1
  174. package/registry/comparison-table.json +1 -1
  175. package/registry/component-page.json +1 -1
  176. package/registry/confirm-button.json +1 -1
  177. package/registry/contact-page.json +1 -1
  178. package/registry/continue-prompt.json +1 -1
  179. package/registry/countdown.json +1 -1
  180. package/registry/cta-panel.json +1 -1
  181. package/registry/dashboard-layout.json +1 -1
  182. package/registry/data-table.json +1 -1
  183. package/registry/date-picker.json +1 -1
  184. package/registry/decision-list.json +1 -1
  185. package/registry/description-list.json +1 -1
  186. package/registry/detail-layout.json +1 -1
  187. package/registry/dialog.json +1 -1
  188. package/registry/docs-article-page.json +1 -1
  189. package/registry/docs-hub-page.json +1 -1
  190. package/registry/docs-layout.json +1 -1
  191. package/registry/docs-nav.json +177 -0
  192. package/registry/document-viewer.json +1 -1
  193. package/registry/dropdown-menu.json +1 -1
  194. package/registry/empty-state.json +1 -1
  195. package/registry/error-page.json +1 -1
  196. package/registry/examples/activity-feed/native/notifications.light.png +0 -0
  197. package/registry/examples/app-header/basic.d.ts +4 -0
  198. package/registry/examples/app-header/basic.js +23 -0
  199. package/registry/examples/app-header/basic.tsx +17 -0
  200. package/registry/examples/app-shell/desktop.d.ts +4 -0
  201. package/registry/examples/app-shell/desktop.js +41 -0
  202. package/registry/examples/app-shell/desktop.tsx +37 -0
  203. package/registry/examples/argument-card/hover-light.d.ts +4 -0
  204. package/registry/examples/argument-card/hover-light.js +17 -0
  205. package/registry/examples/argument-card/hover-light.tsx +19 -0
  206. package/registry/examples/argument-card/hover-quiet.d.ts +4 -0
  207. package/registry/examples/argument-card/hover-quiet.js +17 -0
  208. package/registry/examples/argument-card/hover-quiet.tsx +19 -0
  209. package/registry/examples/avatar/native/group.light.png +0 -0
  210. package/registry/examples/avatar/native/presence.light.png +0 -0
  211. package/registry/examples/avatar/native/sizes.light.png +0 -0
  212. package/registry/examples/badge/native/tones.light.png +0 -0
  213. package/registry/examples/button/native/ghost.light.png +0 -0
  214. package/registry/examples/captcha/disclosure.d.ts +4 -0
  215. package/registry/examples/captcha/disclosure.js +14 -0
  216. package/registry/examples/captcha/disclosure.tsx +11 -0
  217. package/registry/examples/captcha/form.d.ts +4 -0
  218. package/registry/examples/captcha/form.js +18 -0
  219. package/registry/examples/captcha/form.tsx +17 -0
  220. package/registry/examples/card/native/body.light.png +0 -0
  221. package/registry/examples/card/native/default.light.png +0 -0
  222. package/registry/examples/card/native/elevated.light.png +0 -0
  223. package/registry/examples/card/native/pressable.light.png +0 -0
  224. package/registry/examples/card-grid/hover-quiet.d.ts +4 -0
  225. package/registry/examples/card-grid/hover-quiet.js +18 -0
  226. package/registry/examples/card-grid/hover-quiet.tsx +20 -0
  227. package/registry/examples/changelog-page/releases.js +1 -1
  228. package/registry/examples/changelog-page/releases.tsx +1 -1
  229. package/registry/examples/chat/native/conversation.light.png +0 -0
  230. package/registry/examples/chat/native/inbox.light.png +0 -0
  231. package/registry/examples/chip/native/multiple.light.png +0 -0
  232. package/registry/examples/chip/native/removable.light.png +0 -0
  233. package/registry/examples/chip/native/single.light.png +0 -0
  234. package/registry/examples/chip/native/toggle.light.png +0 -0
  235. package/registry/examples/combobox/native/multiple.light.png +0 -0
  236. package/registry/examples/combobox/native/search.light.png +0 -0
  237. package/registry/examples/countdown/native/days.light.png +0 -0
  238. package/registry/examples/countdown/native/hours.light.png +0 -0
  239. package/registry/examples/countdown/native/sizes.light.png +0 -0
  240. package/registry/examples/countdown/native/urgent.light.png +0 -0
  241. package/registry/examples/countdown/native/with-label.light.png +0 -0
  242. package/registry/examples/date-picker/native/birth-date-wheel.light.png +0 -0
  243. package/registry/examples/date-picker/native/birth-date.light.png +0 -0
  244. package/registry/examples/date-picker/native/calendar.dark.png +0 -0
  245. package/registry/examples/date-picker/native/calendar.light.png +0 -0
  246. package/registry/examples/date-picker/native/day-strip.dark.png +0 -0
  247. package/registry/examples/date-picker/native/day-strip.light.png +0 -0
  248. package/registry/examples/date-picker/native/time-slots.dark.png +0 -0
  249. package/registry/examples/date-picker/native/time-slots.light.png +0 -0
  250. package/registry/examples/date-picker/native/time-wheel.light.png +0 -0
  251. package/registry/examples/description-list/native/job-details.light.png +0 -0
  252. package/registry/examples/docs-nav/groups.d.ts +4 -0
  253. package/registry/examples/docs-nav/groups.js +21 -0
  254. package/registry/examples/docs-nav/groups.tsx +15 -0
  255. package/registry/examples/empty-state/native/default.light.png +0 -0
  256. package/registry/examples/field/native/default.light.png +0 -0
  257. package/registry/examples/field/native/select.light.png +0 -0
  258. package/registry/examples/force-update-screen/native/default.light.png +0 -0
  259. package/registry/examples/form/native/amount.light.png +0 -0
  260. package/registry/examples/form/native/audience.light.png +0 -0
  261. package/registry/examples/form/native/profile.light.png +0 -0
  262. package/registry/examples/form/native/settings.light.png +0 -0
  263. package/registry/examples/form/native/sign-up.light.png +0 -0
  264. package/registry/examples/form/native/upload.dark.png +0 -0
  265. package/registry/examples/form/native/upload.light.png +0 -0
  266. package/registry/examples/goal-gauge/native/default.light.png +0 -0
  267. package/registry/examples/header/native/large-title.light.png +0 -0
  268. package/registry/examples/link-card/hover-light.d.ts +4 -0
  269. package/registry/examples/link-card/hover-light.js +18 -0
  270. package/registry/examples/link-card/hover-light.tsx +20 -0
  271. package/registry/examples/link-card/hover-quiet.d.ts +4 -0
  272. package/registry/examples/link-card/hover-quiet.js +18 -0
  273. package/registry/examples/link-card/hover-quiet.tsx +20 -0
  274. package/registry/examples/loader/native/job-wait.light.png +0 -0
  275. package/registry/examples/maintenance-screen/native/default.light.png +0 -0
  276. package/registry/examples/map/native/places.light.png +0 -0
  277. package/registry/examples/markdown/native/basic.light.png +0 -0
  278. package/registry/examples/mascot-state/native/default.light.png +0 -0
  279. package/registry/examples/media-picker/native/story.light.png +0 -0
  280. package/registry/examples/meta-row/native/video.light.png +0 -0
  281. package/registry/examples/meter/native/credits.light.png +0 -0
  282. package/registry/examples/meter/native/split-ring.light.png +0 -0
  283. package/registry/examples/number-ticker/native/balance.light.png +0 -0
  284. package/registry/examples/onboarding/native/first-launch.light.png +0 -0
  285. package/registry/examples/otp-input/native/verify.light.png +0 -0
  286. package/registry/examples/pagination/native/cursor.light.png +0 -0
  287. package/registry/examples/pagination/native/pages.light.png +0 -0
  288. package/registry/examples/permission-prompt/native/blocked.light.png +0 -0
  289. package/registry/examples/permission-prompt/native/camera.light.png +0 -0
  290. package/registry/examples/profile-card/native/default.light.png +0 -0
  291. package/registry/examples/radio-group/native/buttons.light.png +0 -0
  292. package/registry/examples/radio-group/native/cards.light.png +0 -0
  293. package/registry/examples/radio-group/native/list.light.png +0 -0
  294. package/registry/examples/radio-group/native/segmented.light.png +0 -0
  295. package/registry/examples/rail-nav/expandable.d.ts +4 -0
  296. package/registry/examples/rail-nav/expandable.js +34 -0
  297. package/registry/examples/rail-nav/expandable.tsx +28 -0
  298. package/registry/examples/rail-nav/studio.d.ts +4 -0
  299. package/registry/examples/rail-nav/studio.js +43 -0
  300. package/registry/examples/rail-nav/studio.tsx +38 -0
  301. package/registry/examples/screen/native/app-shell.light.png +0 -0
  302. package/registry/examples/screen/native/auth.light.png +0 -0
  303. package/registry/examples/screen/native/map.light.png +0 -0
  304. package/registry/examples/screen/native/notifications.light.png +0 -0
  305. package/registry/examples/screen/native/profile.light.png +0 -0
  306. package/registry/examples/segmented/native/default.light.png +0 -0
  307. package/registry/examples/segmented/native/small.light.png +0 -0
  308. package/registry/examples/settings-layout/flat.d.ts +4 -0
  309. package/registry/examples/settings-layout/flat.js +26 -0
  310. package/registry/examples/settings-layout/flat.tsx +30 -0
  311. package/registry/examples/showcase/native/slides.light.png +0 -0
  312. package/registry/examples/site-footer/app.d.ts +4 -0
  313. package/registry/examples/site-footer/app.js +25 -0
  314. package/registry/examples/site-footer/app.tsx +22 -0
  315. package/registry/examples/site-footer/docs.d.ts +4 -0
  316. package/registry/examples/site-footer/docs.js +31 -0
  317. package/registry/examples/site-footer/docs.tsx +24 -0
  318. package/registry/examples/slider/native/default.light.png +0 -0
  319. package/registry/examples/slider/native/stepped.light.png +0 -0
  320. package/registry/examples/step-progress/native/sign-up.light.png +0 -0
  321. package/registry/examples/stepper/native/run.light.png +0 -0
  322. package/registry/examples/swipeable-row/native/conversation.light.png +0 -0
  323. package/registry/examples/switch/native/settings.light.png +0 -0
  324. package/registry/examples/tab-bar/native/main-sections.light.png +0 -0
  325. package/registry/examples/tabs/native/sections.light.png +0 -0
  326. package/registry/examples/tabs/native/segmented.light.png +0 -0
  327. package/registry/examples/tag-selector/native/interests.light.png +0 -0
  328. package/registry/examples/toast/native/tones.light.png +0 -0
  329. package/registry/examples/txt/native/tones.dark.png +0 -0
  330. package/registry/examples/txt/native/tones.light.png +0 -0
  331. package/registry/examples/update-prompt/native/default.light.png +0 -0
  332. package/registry/examples/waveform/card.d.ts +4 -0
  333. package/registry/examples/waveform/card.js +18 -0
  334. package/registry/examples/waveform/card.tsx +16 -0
  335. package/registry/examples/waveform/native/card.d.ts +4 -0
  336. package/registry/examples/waveform/native/card.dark.png +0 -0
  337. package/registry/examples/waveform/native/card.js +19 -0
  338. package/registry/examples/waveform/native/card.light.png +0 -0
  339. package/registry/examples/waveform/native/card.tsx +19 -0
  340. package/registry/examples/waveform/sizes.d.ts +4 -0
  341. package/registry/examples/waveform/sizes.js +16 -0
  342. package/registry/examples/waveform/sizes.tsx +14 -0
  343. package/registry/field.json +1 -1
  344. package/registry/file-upload.json +1 -1
  345. package/registry/filter-bar.json +1 -1
  346. package/registry/force-update-screen.json +1 -1
  347. package/registry/form.json +1 -1
  348. package/registry/gallery-page.json +1 -1
  349. package/registry/genui.json +3 -3
  350. package/registry/glow-card.json +1 -1
  351. package/registry/goal-gauge.json +1 -1
  352. package/registry/how-it-works-page.json +1 -1
  353. package/registry/image-cropper.json +1 -1
  354. package/registry/index.json +197 -14
  355. package/registry/iso.json +1 -1
  356. package/registry/kanban.json +1 -1
  357. package/registry/kbd.json +1 -1
  358. package/registry/landing-page.json +1 -1
  359. package/registry/landscape.json +1 -1
  360. package/registry/legal-page.json +1 -1
  361. package/registry/link-card.json +38 -6
  362. package/registry/list.json +1 -1
  363. package/registry/loader.json +1 -1
  364. package/registry/longform-page.json +1 -1
  365. package/registry/maintenance-screen.json +1 -1
  366. package/registry/map.json +2 -2
  367. package/registry/markdown.json +1 -1
  368. package/registry/marketing-layout.json +1 -1
  369. package/registry/marquee.json +1 -1
  370. package/registry/mascot-quote.json +1 -1
  371. package/registry/mascot-state.json +1 -1
  372. package/registry/mascots.json +1 -1
  373. package/registry/match-dialog.json +1 -1
  374. package/registry/media-capture.json +1 -1
  375. package/registry/media-grid.json +92 -6
  376. package/registry/meta-row.json +1 -1
  377. package/registry/meter.json +1 -1
  378. package/registry/nav-bar.json +2 -2
  379. package/registry/number-ticker.json +1 -1
  380. package/registry/page-header.json +1 -1
  381. package/registry/page-hero.json +1 -1
  382. package/registry/page.json +1 -1
  383. package/registry/pagination.json +1 -1
  384. package/registry/photo-grid.json +1 -1
  385. package/registry/pointer-light.json +1 -1
  386. package/registry/popover.json +1 -1
  387. package/registry/pricing-page.json +1 -1
  388. package/registry/product-page.json +1 -1
  389. package/registry/profile-card.json +1 -1
  390. package/registry/progress.json +1 -1
  391. package/registry/promise-card.json +1 -1
  392. package/registry/radio-group.json +1 -1
  393. package/registry/rail-nav.json +493 -0
  394. package/registry/recipes.json +1 -1
  395. package/registry/scroll-stage.json +1 -1
  396. package/registry/search-field.json +1 -1
  397. package/registry/section-backdrop.json +1 -1
  398. package/registry/separator.json +1 -1
  399. package/registry/settings-layout.json +104 -9
  400. package/registry/showcase.json +1 -1
  401. package/registry/site-footer.json +38 -6
  402. package/registry/sites.json +1 -1
  403. package/registry/skeleton.json +1 -1
  404. package/registry/skip-link.json +1 -1
  405. package/registry/slider.json +1 -1
  406. package/registry/slot.json +1 -1
  407. package/registry/spinner.json +1 -1
  408. package/registry/split-view.json +1 -1
  409. package/registry/stage-banner.json +1 -1
  410. package/registry/stat.json +1 -1
  411. package/registry/status-page.json +1 -1
  412. package/registry/step-progress.json +1 -1
  413. package/registry/step-showcase.json +1 -1
  414. package/registry/stepper.json +1 -1
  415. package/registry/swipe-deck.json +1 -1
  416. package/registry/switch.json +1 -1
  417. package/registry/tabs.json +2 -2
  418. package/registry/tag-input.json +1 -1
  419. package/registry/tag-selector.json +1 -1
  420. package/registry/terminal-replay.json +1 -1
  421. package/registry/theme.json +1 -1
  422. package/registry/toast.json +1 -1
  423. package/registry/toc.json +1 -1
  424. package/registry/tooltip.json +1 -1
  425. package/registry/tree-view.json +1 -1
  426. package/registry/update-prompt.json +1 -1
  427. package/registry/verified-badge.json +1 -1
  428. package/registry/vertical-video.json +1 -1
  429. package/registry/video-player.json +1 -1
  430. package/registry/video-trimmer.json +1 -1
  431. package/registry/virtual.json +1 -1
  432. package/registry/waveform.json +296 -0
  433. package/dist/chunk-2FRWNSRO.js.map +0 -1
  434. package/dist/chunk-3BC4XQ6C.js.map +0 -1
  435. package/dist/chunk-3IN6TOPM.js.map +0 -1
  436. package/dist/chunk-4LOMIQVX.js.map +0 -1
  437. package/dist/chunk-G3ETON2A.js.map +0 -1
  438. package/dist/chunk-IZ7MV6HM.js.map +0 -1
  439. package/dist/chunk-OWC2LZD2.js.map +0 -1
  440. package/dist/chunk-ZNMFG62E.js.map +0 -1
  441. /package/dist/{chunk-FKBHWS4N.js.map → chunk-CEJDH6SA.js.map} +0 -0
  442. /package/dist/{chunk-R6NK6ZNY.js.map → chunk-F7FVPPBB.js.map} +0 -0
  443. /package/dist/{chunk-5Z7FWCV2.js.map → chunk-HBZPJ3EP.js.map} +0 -0
  444. /package/dist/{chunk-C2BRIFJG.js.map → chunk-HJFRHYCI.js.map} +0 -0
  445. /package/dist/{chunk-BTMFZ66D.js.map → chunk-KM6VAKIE.js.map} +0 -0
  446. /package/dist/{chunk-DGQMDJXK.js.map → chunk-LZNZLCOE.js.map} +0 -0
  447. /package/dist/{chunk-AQSZJJAJ.js.map → chunk-MQPC3P4T.js.map} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { Fact } from './page.js';
2
2
  import * as React from 'react';
3
3
  import { L as LinkComponent } from './block-link-BAg4eoZk.js';
4
+ import { CardHover } from './link-card.js';
4
5
  import './section-backdrop.js';
5
6
  import 'tailwind-variants';
6
7
 
@@ -56,12 +57,14 @@ type ArgumentCardProps = Omit<React.HTMLAttributes<HTMLElement>, "title"> & {
56
57
  level?: 2 | 3 | 4;
57
58
  /** The app's link component for internal addresses (Next's `Link`); an `<a>` otherwise. */
58
59
  linkAs?: LinkComponent;
60
+ /** The hover of the card: `light` (the pointer's light, the default) or `quiet` (no glow, a lift, a ring on focus). */
61
+ hover?: CardHover;
59
62
  };
60
63
  /**
61
64
  * A card that argues, compact (about a third of a full card): who (icon, name, meta; the bird small in the corner) →
62
65
  * the problem in one line → compact facts → one short action. What it does, the bird's rule and the long version are
63
66
  * never on the face: they live on the detail page (`more`) or in the card unfolded in place (`panelLabel`).
64
67
  */
65
- declare function ArgumentCard({ id, title, icon, meta, mark, facts, hook, points, rule, details, panelLabel, more, actions, level, linkAs, className, ...props }: ArgumentCardProps): React.JSX.Element;
68
+ declare function ArgumentCard({ id, title, icon, meta, mark, facts, hook, points, rule, details, panelLabel, more, actions, level, linkAs, hover, className, ...props }: ArgumentCardProps): React.JSX.Element;
66
69
 
67
70
  export { ArgumentCard, type ArgumentCardProps, Fact };
@@ -1,24 +1,27 @@
1
+ import {
2
+ Facts
3
+ } from "./chunk-LZNZLCOE.js";
4
+ import {
5
+ cardHoverClass
6
+ } from "./chunk-APNIVNIX.js";
1
7
  import {
2
8
  BlockLink
3
9
  } from "./chunk-2AGSO3WH.js";
4
- import {
5
- Facts
6
- } from "./chunk-DGQMDJXK.js";
7
- import "./chunk-XVLWVTQA.js";
8
10
  import {
9
11
  ArrowGlyph,
10
12
  ChevronGlyph
11
13
  } from "./chunk-XCBVFMNV.js";
14
+ import "./chunk-XVLWVTQA.js";
12
15
  import {
13
16
  cn
14
- } from "./chunk-4LOMIQVX.js";
17
+ } from "./chunk-W675RFOA.js";
15
18
 
16
19
  // src/argument-card/argument-card.tsx
17
20
  import { jsx, jsxs } from "react/jsx-runtime";
18
- function ArgumentCard({ id, title, icon, meta, mark, facts, hook, points, rule, details, panelLabel, more, actions, level = 3, linkAs, className, ...props }) {
21
+ function ArgumentCard({ id, title, icon, meta, mark, facts, hook, points, rule, details, panelLabel, more, actions, level = 3, linkAs, hover, className, ...props }) {
19
22
  const H = `h${level}`;
20
23
  const fold = !more && panelLabel && (points || rule || details && details.length > 0);
21
- return /* @__PURE__ */ jsxs("article", { className: cn("kz-block-card kz-arg", className), "data-kz-light": "", "aria-labelledby": id, ...props, children: [
24
+ return /* @__PURE__ */ jsxs("article", { className: cn("kz-block-card kz-arg", cardHoverClass(hover), className), "data-kz-light": "", "aria-labelledby": id, ...props, children: [
22
25
  /* @__PURE__ */ jsxs("div", { className: "kz-arg-head", children: [
23
26
  icon && /* @__PURE__ */ jsx("span", { className: "kz-block-icon", "aria-hidden": true, children: icon }),
24
27
  /* @__PURE__ */ jsxs("div", { className: "kz-arg-id", children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/argument-card/argument-card.tsx"],"sourcesContent":["// 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"],"mappings":";;;;;;;;;;;;;;;;AAiEU,cAIF,YAJE;AAPH,SAAS,aAAa,EAAE,IAAI,OAAO,MAAM,MAAM,MAAM,OAAO,MAAM,QAAQ,MAAM,SAAS,YAAY,MAAM,SAAS,QAAQ,GAAG,QAAQ,WAAW,GAAG,MAAM,GAAsB;AACtL,QAAM,IAAI,IAAI,KAAK;AACnB,QAAM,OAAO,CAAC,QAAQ,eAAe,UAAU,QAAS,WAAW,QAAQ,SAAS;AACpF,SACE,qBAAC,aAAQ,WAAW,GAAG,wBAAwB,SAAS,GAAG,iBAAc,IAAG,mBAAiB,IAAK,GAAG,OACnG;AAAA,yBAAC,SAAI,WAAU,eACZ;AAAA,cACC,oBAAC,UAAK,WAAU,iBAAgB,eAAW,MACxC,gBACH;AAAA,MAEF,qBAAC,SAAI,WAAU,aACb;AAAA,4BAAC,KAAE,IAAQ,WAAU,gBAClB,iBACH;AAAA,QACC,QAAQ,oBAAC,OAAE,WAAU,eAAe,gBAAK;AAAA,SAC5C;AAAA,MACC,QACC,oBAAC,UAAK,WAAU,eAAc,eAAW,MACtC,gBACH;AAAA,OAEJ;AAAA,IACA,qBAAC,OAAE,WAAU,eACX;AAAA,2BAAC,UAAK,WAAU,cAAc;AAAA,aAAK;AAAA,QAAM;AAAA,SAAG;AAAA,MAC3C,KAAK;AAAA,OACR;AAAA,IACC,SAAS,MAAM,SAAS,KAAK,oBAAC,SAAM,OAAO,OAAO;AAAA,IAClD,QACC,qBAAC,aAAQ,WAAU,gBACjB;AAAA,2BAAC,aACE;AAAA;AAAA,QAAW;AAAA,QAAC,oBAAC,gBAAa,WAAU,eAAc;AAAA,SACrD;AAAA,MACA,qBAAC,SAAI,WAAU,qBACZ;AAAA,kBACC,qBAAC,SACC;AAAA,8BAAC,OAAE,WAAU,YAAY,iBAAO,OAAM;AAAA,UACtC,oBAAC,QAAG,WAAU,WACX,iBAAO,MAAM,IAAI,CAAC,MAAM,MACvB,oBAAC,QAAY,kBAAJ,CAAS,CACnB,GACH;AAAA,WACF;AAAA,QAED,WAAW,QAAQ,SAAS,KAC3B,oBAAC,QACE,kBAAQ,IAAI,CAAC,GAAG,MACf,qBAAC,SACC;AAAA,8BAAC,QAAG,WAAU,YAAY,YAAE,OAAM;AAAA,UAClC,oBAAC,QAAI,YAAE,MAAK;AAAA,aAFJ,CAGV,CACD,GACH;AAAA,QAED;AAAA,SACH;AAAA,OACF;AAAA,KAEA,QAAQ,YACR,qBAAC,SAAI,WAAU,eACZ;AAAA,aACC,qBAAC,aAAU,MAAM,KAAK,MAAM,QAAgB,WAAU,eAAc,oBAAkB,IACnF;AAAA,aAAK;AAAA,QAAM;AAAA,QAAC,oBAAC,cAAW,WAAU,eAAc;AAAA,SACnD,IAEA,oBAAC,UAAK;AAAA,MAEP;AAAA,OACH;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/argument-card/argument-card.tsx"],"sourcesContent":["// 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"],"mappings":";;;;;;;;;;;;;;;;;;;AAoEU,cAIF,YAJE;AAPH,SAAS,aAAa,EAAE,IAAI,OAAO,MAAM,MAAM,MAAM,OAAO,MAAM,QAAQ,MAAM,SAAS,YAAY,MAAM,SAAS,QAAQ,GAAG,QAAQ,OAAO,WAAW,GAAG,MAAM,GAAsB;AAC7L,QAAM,IAAI,IAAI,KAAK;AACnB,QAAM,OAAO,CAAC,QAAQ,eAAe,UAAU,QAAS,WAAW,QAAQ,SAAS;AACpF,SACE,qBAAC,aAAQ,WAAW,GAAG,wBAAwB,eAAe,KAAK,GAAG,SAAS,GAAG,iBAAc,IAAG,mBAAiB,IAAK,GAAG,OAC1H;AAAA,yBAAC,SAAI,WAAU,eACZ;AAAA,cACC,oBAAC,UAAK,WAAU,iBAAgB,eAAW,MACxC,gBACH;AAAA,MAEF,qBAAC,SAAI,WAAU,aACb;AAAA,4BAAC,KAAE,IAAQ,WAAU,gBAClB,iBACH;AAAA,QACC,QAAQ,oBAAC,OAAE,WAAU,eAAe,gBAAK;AAAA,SAC5C;AAAA,MACC,QACC,oBAAC,UAAK,WAAU,eAAc,eAAW,MACtC,gBACH;AAAA,OAEJ;AAAA,IACA,qBAAC,OAAE,WAAU,eACX;AAAA,2BAAC,UAAK,WAAU,cAAc;AAAA,aAAK;AAAA,QAAM;AAAA,SAAG;AAAA,MAC3C,KAAK;AAAA,OACR;AAAA,IACC,SAAS,MAAM,SAAS,KAAK,oBAAC,SAAM,OAAO,OAAO;AAAA,IAClD,QACC,qBAAC,aAAQ,WAAU,gBACjB;AAAA,2BAAC,aACE;AAAA;AAAA,QAAW;AAAA,QAAC,oBAAC,gBAAa,WAAU,eAAc;AAAA,SACrD;AAAA,MACA,qBAAC,SAAI,WAAU,qBACZ;AAAA,kBACC,qBAAC,SACC;AAAA,8BAAC,OAAE,WAAU,YAAY,iBAAO,OAAM;AAAA,UACtC,oBAAC,QAAG,WAAU,WACX,iBAAO,MAAM,IAAI,CAAC,MAAM,MACvB,oBAAC,QAAY,kBAAJ,CAAS,CACnB,GACH;AAAA,WACF;AAAA,QAED,WAAW,QAAQ,SAAS,KAC3B,oBAAC,QACE,kBAAQ,IAAI,CAAC,GAAG,MACf,qBAAC,SACC;AAAA,8BAAC,QAAG,WAAU,YAAY,YAAE,OAAM;AAAA,UAClC,oBAAC,QAAI,YAAE,MAAK;AAAA,aAFJ,CAGV,CACD,GACH;AAAA,QAED;AAAA,SACH;AAAA,OACF;AAAA,KAEA,QAAQ,YACR,qBAAC,SAAI,WAAU,eACZ;AAAA,aACC,qBAAC,aAAU,MAAM,KAAK,MAAM,QAAgB,WAAU,eAAc,oBAAkB,IACnF;AAAA,aAAK;AAAA,QAAM;AAAA,QAAC,oBAAC,cAAW,WAAU,eAAc;AAAA,SACnD,IAEA,oBAAC,UAAK;AAAA,MAEP;AAAA,OACH;AAAA,KAEJ;AAEJ;","names":[]}
@@ -1,6 +1,9 @@
1
1
  import * as tailwind_variants from 'tailwind-variants';
2
2
  import * as React from 'react';
3
3
 
4
+ /** Resamples peaks to `count` bars (the maximum of each slice), normalised to the loudest. No peaks: a flat line at 15 %. */
5
+ declare function resamplePeaks(peaks: readonly number[], count: number): number[];
6
+
4
7
  declare const audioPlayer: tailwind_variants.TVReturnType<{
5
8
  [key: string]: {
6
9
  [key: string]: tailwind_variants.ClassValue | {
@@ -103,8 +106,6 @@ type AudioPlayerProps = {
103
106
  /** Classes merged on the root. */
104
107
  className?: string;
105
108
  };
106
- /** Resamples peaks to `count` bars (the maximum of each slice), normalised to the loudest. */
107
- declare function resamplePeaks(peaks: readonly number[], count: number): number[];
108
109
  /** The peaks of a sound file, computed with Web Audio (`count` of them). */
109
110
  declare function computeAudioPeaks(src: string, count?: number, signal?: AbortSignal): Promise<number[]>;
110
111
  /** A sound with a seekable waveform: play / pause, time, speed — a voice note, a podcast clip, a track. */
@@ -13,6 +13,20 @@ import {
13
13
  // src/audio-player/audio-player.tsx
14
14
  import * as React from "react";
15
15
  import { tv } from "tailwind-variants";
16
+
17
+ // src/waveform/bars.ts
18
+ function resamplePeaks(peaks, count) {
19
+ if (peaks.length === 0) return Array.from({ length: count }, () => 0.15);
20
+ const out = Array.from({ length: count }, (_, i) => {
21
+ const from = Math.floor(i / count * peaks.length);
22
+ const to = Math.max(from + 1, Math.floor((i + 1) / count * peaks.length));
23
+ return Math.max(...peaks.slice(from, to).map(Math.abs));
24
+ });
25
+ const loudest = Math.max(...out, 1e-4);
26
+ return out.map((v) => Math.max(0.08, v / loudest));
27
+ }
28
+
29
+ // src/audio-player/audio-player.tsx
16
30
  import { jsx, jsxs } from "react/jsx-runtime";
17
31
  var audioPlayer = tv({
18
32
  slots: {
@@ -33,16 +47,6 @@ var audioPlayerLabels = {
33
47
  speed: (rate) => `Speed ${rate}\xD7`,
34
48
  time: (time, duration) => `${time} of ${duration}`
35
49
  };
36
- function resamplePeaks(peaks, count) {
37
- if (peaks.length === 0) return Array.from({ length: count }, () => 0.15);
38
- const out = Array.from({ length: count }, (_, i) => {
39
- const from = Math.floor(i / count * peaks.length);
40
- const to = Math.max(from + 1, Math.floor((i + 1) / count * peaks.length));
41
- return Math.max(...peaks.slice(from, to).map(Math.abs));
42
- });
43
- const loudest = Math.max(...out, 1e-4);
44
- return out.map((v) => Math.max(0.08, v / loudest));
45
- }
46
50
  async function computeAudioPeaks(src, count = 200, signal) {
47
51
  const response = await fetch(src, { signal });
48
52
  const data = await response.arrayBuffer();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/audio-player/audio-player.tsx"],"sourcesContent":["// 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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/audio-player/audio-player.tsx","../src/waveform/bars.ts"],"sourcesContent":["// 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","// The bars of a waveform, shared by AudioPlayer (the seekable one) and Waveform (the decorative one). Pure: no React.\n\n/** Resamples peaks to `count` bars (the maximum of each slice), normalised to the loudest. No peaks: a flat line at 15 %. */\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"],"mappings":"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","names":[]}
package/dist/blocks.css CHANGED
@@ -248,6 +248,31 @@ a.kz-fact:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); o
248
248
  .kz-block-card:hover .kz-block-icon { transform: translateY(-2px) rotate(-3deg); box-shadow: 0 10px 22px -12px var(--kz-hue, var(--kz-accent)); }
249
249
  }
250
250
 
251
+ /* The quiet hover (`hover="quiet"` on a card, or on CardGrid for the whole grid; a card's own `hover="light"` wins over
252
+ its grid): no glow, no halo, no accent border or title. The card lifts 2 px, its surface and hairline go one step up,
253
+ the title leans towards the accent text, and the focus is a ring on the whole card. Reduced motion: no lift. */
254
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light))::after { display: none; }
255
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)) {
256
+ transition: transform 0.3s var(--kz-ease, ease), background-color 0.3s var(--kz-ease, ease), border-color 0.3s var(--kz-ease, ease);
257
+ }
258
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)):is(:hover, :focus-within, :focus-visible) .kz-block-icon svg [data-node] { transform: none; filter: none; }
259
+ @media (hover: hover) {
260
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)):hover {
261
+ transform: translateY(-2px);
262
+ background: var(--kz-surface-2);
263
+ border-color: var(--kz-border-strong);
264
+ box-shadow: none;
265
+ }
266
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)):hover .kz-block-icon { transform: none; box-shadow: none; }
267
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)):hover :is(.kz-link-card-title, .kz-arg-title) { color: color-mix(in srgb, var(--kz-text-primary) 72%, var(--kz-hue-text, var(--kz-accent-text))); }
268
+ }
269
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)):is(:focus-within, :focus-visible) :is(.kz-link-card-title, .kz-arg-title) { color: color-mix(in srgb, var(--kz-text-primary) 72%, var(--kz-hue-text, var(--kz-accent-text))); }
270
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)):is(:focus-visible, :has(:focus-visible)) {
271
+ outline: 2px solid var(--kz-ring, var(--kz-accent));
272
+ outline-offset: 2px;
273
+ }
274
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)):is(:focus-within, :focus-visible) { border-color: var(--kz-border-strong); }
275
+
251
276
  /* ─── LinkCard: the whole card is the link — icon, title, one sentence, the way in ─── */
252
277
  .kz-link-card {
253
278
  display: grid;
@@ -595,6 +620,7 @@ a.kz-fact:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); o
595
620
  @media (prefers-reduced-motion: reduce) {
596
621
  .kz-block-card, .kz-block-card::after, .kz-block-icon, .kz-block-icon svg [data-node], .kz-link-card-cta svg, .kz-arg-more svg, .kz-arg-panel > summary svg, .kz-actions [data-tip]::after { transition: none !important; }
597
622
  .kz-block-card:hover, .kz-block-card:hover .kz-block-icon, .kz-link-card:hover .kz-link-card-cta svg, .kz-arg-more:hover svg { transform: none !important; }
623
+ :is(.kz-block-card.kz-hover-quiet, .kz-cards.kz-hover-quiet .kz-block-card:not(.kz-hover-light)):hover { transform: none !important; }
598
624
  .kz-quote-bird { animation: none !important; }
599
625
  }
600
626
 
@@ -772,6 +798,8 @@ a.kz-fact:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); o
772
798
  .kz-tpl-log-kind[data-kind="changed"] { color: var(--kz-accent-text); }
773
799
  .kz-tpl-log-kind[data-kind="fixed"] { color: var(--kz-warning-text); }
774
800
  .kz-tpl-log-kind[data-kind="removed"] { color: var(--kz-danger-text); }
801
+ .kz-tpl-log-kind[data-kind="breaking"] { color: var(--kz-danger-text); }
802
+ .kz-tpl-log-kind[data-kind="security"] { color: var(--kz-warning-text); }
775
803
  @media (max-width: 720px) {
776
804
  .kz-tpl-log-entry { grid-template-columns: minmax(0, 1fr); gap: 10px; }
777
805
  .kz-tpl-log-when { position: static; grid-auto-flow: column; justify-content: start; align-items: center; gap: 10px; }
package/dist/button.js CHANGED
@@ -3,9 +3,10 @@ import {
3
3
  Button,
4
4
  IconButton,
5
5
  buttonVariants
6
- } from "./chunk-3IN6TOPM.js";
6
+ } from "./chunk-6ZYRUOTA.js";
7
7
  import "./chunk-43PTCDTL.js";
8
8
  import "./chunk-2VNYHG4E.js";
9
+ import "./chunk-W675RFOA.js";
9
10
  export {
10
11
  Button,
11
12
  IconButton,
@@ -0,0 +1,81 @@
1
+ import * as React from 'react';
2
+
3
+ /** The words of `Captcha` and `CaptchaDisclosure`, English and French. Pass `labels` to say them otherwise. */
4
+ interface CaptchaLabels {
5
+ /** The accessible name of the check (the group around the widget). */
6
+ readonly group: string;
7
+ /** Said when Cloudflare could not run the check. */
8
+ readonly error: string;
9
+ /** The retry button. */
10
+ readonly retry: string;
11
+ /** « Protected by Cloudflare Turnstile ». */
12
+ readonly protectedBy: string;
13
+ readonly privacy: string;
14
+ readonly terms: string;
15
+ }
16
+ declare const captchaMessages: {
17
+ readonly en: {
18
+ readonly group: "Security check";
19
+ readonly error: "The security check could not run. Check your connection and try again.";
20
+ readonly retry: "Try again";
21
+ readonly protectedBy: "Protected by Cloudflare Turnstile";
22
+ readonly privacy: "Privacy";
23
+ readonly terms: "Terms";
24
+ };
25
+ readonly fr: {
26
+ readonly group: "Vérification de sécurité";
27
+ readonly error: "La vérification de sécurité n'a pas pu s'exécuter. Vérifiez votre connexion et réessayez.";
28
+ readonly retry: "Réessayer";
29
+ readonly protectedBy: "Protégé par Cloudflare Turnstile";
30
+ readonly privacy: "Confidentialité";
31
+ readonly terms: "Conditions";
32
+ };
33
+ };
34
+ type CaptchaLocale = keyof typeof captchaMessages;
35
+
36
+ type CaptchaProps = {
37
+ /** The widget's public site key. Empty → nothing is rendered and no token is asked (local dev, tests). */
38
+ siteKey: string;
39
+ /** The token once the check passes; `null` when it expires, fails or the theme redraws the widget. */
40
+ onToken: (token: string | null) => void;
41
+ /**
42
+ * The language of the widget and of the words: `"en"`, `"fr"`…
43
+ * @default "en"
44
+ */
45
+ locale?: string;
46
+ /**
47
+ * `auto` follows the page (`html.light` / `data-theme`, redrawn when it flips).
48
+ * @default "auto"
49
+ */
50
+ theme?: "auto" | "light" | "dark";
51
+ /** Words to say otherwise; the rest comes from the locale. */
52
+ labels?: Partial<CaptchaLabels>;
53
+ /** Classes added to the root. */
54
+ className?: string;
55
+ };
56
+ /**
57
+ * The bot check of a public form, Cloudflare Turnstile that stays out of the way: managed mode with
58
+ * `appearance: "interaction-only"`, so a normal visitor sees nothing — the check runs in the background and `onToken`
59
+ * fires. Only when Cloudflare needs the visitor does a framed box appear, on the tokens (radius, border, theme).
60
+ * If the check cannot run, an alert with a retry button replaces it. Pair it with `CaptchaDisclosure` under the submit.
61
+ */
62
+ declare function Captcha({ siteKey, onToken, locale, theme, labels, className }: CaptchaProps): React.JSX.Element | null;
63
+ type CaptchaDisclosureProps = {
64
+ /**
65
+ * The language of the line: `"en"`, `"fr"`…
66
+ * @default "en"
67
+ */
68
+ locale?: string;
69
+ /** Words to say otherwise; the rest comes from the locale. */
70
+ labels?: Partial<CaptchaLabels>;
71
+ /** The privacy link; Cloudflare's privacy policy by default. */
72
+ privacyHref?: string;
73
+ /** The terms link; Cloudflare's website terms by default. */
74
+ termsHref?: string;
75
+ /** Classes added to the root. */
76
+ className?: string;
77
+ };
78
+ /** « Protected by Cloudflare Turnstile · Privacy · Terms »: the line Cloudflare's terms ask for under the submit button. */
79
+ declare function CaptchaDisclosure({ locale, labels, privacyHref, termsHref, className }: CaptchaDisclosureProps): React.JSX.Element;
80
+
81
+ export { Captcha, CaptchaDisclosure, type CaptchaDisclosureProps, type CaptchaLabels, type CaptchaLocale, type CaptchaProps, captchaMessages };
@@ -0,0 +1,162 @@
1
+ "use client";
2
+ import {
3
+ Button
4
+ } from "./chunk-CHOJUCVE.js";
5
+ import "./chunk-D3T54RSL.js";
6
+ import "./chunk-WEVYKZIF.js";
7
+ import "./chunk-NSLXEDIS.js";
8
+
9
+ // src/captcha/captcha.tsx
10
+ import { useCallback, useEffect, useRef, useState } from "react";
11
+ import { tv } from "tailwind-variants";
12
+
13
+ // src/captcha/messages.ts
14
+ var captchaMessages = {
15
+ en: {
16
+ group: "Security check",
17
+ error: "The security check could not run. Check your connection and try again.",
18
+ retry: "Try again",
19
+ protectedBy: "Protected by Cloudflare Turnstile",
20
+ privacy: "Privacy",
21
+ terms: "Terms"
22
+ },
23
+ fr: {
24
+ group: "V\xE9rification de s\xE9curit\xE9",
25
+ error: "La v\xE9rification de s\xE9curit\xE9 n'a pas pu s'ex\xE9cuter. V\xE9rifiez votre connexion et r\xE9essayez.",
26
+ retry: "R\xE9essayer",
27
+ protectedBy: "Prot\xE9g\xE9 par Cloudflare Turnstile",
28
+ privacy: "Confidentialit\xE9",
29
+ terms: "Conditions"
30
+ }
31
+ };
32
+ var labelsFor = (locale, labels) => ({
33
+ ...captchaMessages[locale?.toLowerCase().startsWith("fr") ? "fr" : "en"],
34
+ ...labels
35
+ });
36
+
37
+ // src/captcha/turnstile.ts
38
+ var TURNSTILE_SCRIPT = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
39
+ var TURNSTILE_PRIVACY = "https://www.cloudflare.com/privacypolicy/";
40
+ var TURNSTILE_TERMS = "https://www.cloudflare.com/website-terms/";
41
+ var loading = null;
42
+ function loadTurnstile() {
43
+ if (window.turnstile) return Promise.resolve(window.turnstile);
44
+ loading ??= new Promise((resolve, reject) => {
45
+ const s = document.createElement("script");
46
+ s.src = TURNSTILE_SCRIPT;
47
+ s.async = true;
48
+ s.onload = () => window.turnstile ? resolve(window.turnstile) : reject(new Error("turnstile"));
49
+ s.onerror = () => {
50
+ loading = null;
51
+ reject(new Error("turnstile"));
52
+ };
53
+ try {
54
+ document.head.appendChild(s);
55
+ } catch {
56
+ loading = null;
57
+ reject(new Error("turnstile"));
58
+ }
59
+ });
60
+ return loading;
61
+ }
62
+ function pageTheme() {
63
+ const root = document.documentElement;
64
+ return root.classList.contains("light") || root.dataset.theme === "light" ? "light" : "dark";
65
+ }
66
+
67
+ // src/captcha/captcha.tsx
68
+ import { jsx, jsxs } from "react/jsx-runtime";
69
+ var captcha = tv({
70
+ slots: {
71
+ root: "w-full",
72
+ frame: "overflow-hidden rounded-lg border border-border-default bg-surface-2 p-2",
73
+ error: "flex flex-wrap items-center gap-3 rounded-lg border border-border-default bg-surface-2 p-3 text-sm text-fg-danger",
74
+ disclosure: "text-xs text-fg-muted",
75
+ link: "underline underline-offset-2 hover:text-fg-secondary focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2"
76
+ }
77
+ });
78
+ function Captcha({ siteKey, onToken, locale = "en", theme = "auto", labels, className }) {
79
+ const host = useRef(null);
80
+ const widget = useRef(null);
81
+ const onTokenRef = useRef(onToken);
82
+ useEffect(() => {
83
+ onTokenRef.current = onToken;
84
+ });
85
+ const [interactive, setInteractive] = useState(false);
86
+ const [failed, setFailed] = useState(false);
87
+ const [attempt, setAttempt] = useState(0);
88
+ const words = labelsFor(locale, labels);
89
+ const { root, frame, error } = captcha();
90
+ useEffect(() => {
91
+ if (!siteKey || !host.current) return;
92
+ const el = host.current;
93
+ let cancelled = false;
94
+ const fail = () => {
95
+ onTokenRef.current(null);
96
+ setInteractive(false);
97
+ setFailed(true);
98
+ };
99
+ const mount = () => {
100
+ loadTurnstile().then((api) => {
101
+ if (cancelled) return;
102
+ if (widget.current) api.remove(widget.current);
103
+ widget.current = api.render(el, {
104
+ sitekey: siteKey,
105
+ appearance: "interaction-only",
106
+ size: "flexible",
107
+ theme: theme === "auto" ? pageTheme() : theme,
108
+ language: locale,
109
+ callback: (token) => {
110
+ setFailed(false);
111
+ onTokenRef.current(token);
112
+ },
113
+ "expired-callback": () => onTokenRef.current(null),
114
+ "before-interactive-callback": () => setInteractive(true),
115
+ "after-interactive-callback": () => setInteractive(false),
116
+ "error-callback": fail,
117
+ "unsupported-callback": fail
118
+ });
119
+ }, fail);
120
+ };
121
+ setFailed(false);
122
+ mount();
123
+ const watcher = theme === "auto" ? new MutationObserver(() => {
124
+ onTokenRef.current(null);
125
+ mount();
126
+ }) : null;
127
+ watcher?.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "data-theme"] });
128
+ return () => {
129
+ cancelled = true;
130
+ watcher?.disconnect();
131
+ if (widget.current && window.turnstile) window.turnstile.remove(widget.current);
132
+ widget.current = null;
133
+ };
134
+ }, [siteKey, locale, theme, attempt]);
135
+ const retry = useCallback(() => setAttempt((n) => n + 1), []);
136
+ if (!siteKey) return null;
137
+ return /* @__PURE__ */ jsxs("div", { className: root({ className }), role: "group", "aria-label": words.group, "data-captcha": failed ? "error" : interactive ? "interactive" : "idle", children: [
138
+ failed ? /* @__PURE__ */ jsxs("div", { role: "alert", className: error(), children: [
139
+ /* @__PURE__ */ jsx("span", { children: words.error }),
140
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "secondary", onClick: retry, children: words.retry })
141
+ ] }) : null,
142
+ /* @__PURE__ */ jsx("div", { className: interactive && !failed ? frame() : void 0, hidden: failed, style: interactive ? void 0 : { height: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { ref: host }) })
143
+ ] });
144
+ }
145
+ function CaptchaDisclosure({ locale = "en", labels, privacyHref = TURNSTILE_PRIVACY, termsHref = TURNSTILE_TERMS, className }) {
146
+ const words = labelsFor(locale, labels);
147
+ const { disclosure, link } = captcha();
148
+ return /* @__PURE__ */ jsxs("p", { className: disclosure({ className }), children: [
149
+ words.protectedBy,
150
+ " \xB7 ",
151
+ /* @__PURE__ */ jsx("a", { className: link(), href: privacyHref, target: "_blank", rel: "noopener noreferrer", children: words.privacy }),
152
+ " \xB7",
153
+ " ",
154
+ /* @__PURE__ */ jsx("a", { className: link(), href: termsHref, target: "_blank", rel: "noopener noreferrer", children: words.terms })
155
+ ] });
156
+ }
157
+ export {
158
+ Captcha,
159
+ CaptchaDisclosure,
160
+ captchaMessages
161
+ };
162
+ //# sourceMappingURL=captcha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/captcha/captcha.tsx","../src/captcha/messages.ts","../src/captcha/turnstile.ts"],"sourcesContent":["// Client: loads the Cloudflare script and draws the widget in an effect.\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { Button } from \"../button\";\nimport { type CaptchaLabels, labelsFor } from \"./messages\";\nimport { loadTurnstile, pageTheme, TURNSTILE_PRIVACY, TURNSTILE_TERMS } from \"./turnstile\";\n\nconst captcha = tv({\n slots: {\n root: \"w-full\",\n frame: \"overflow-hidden rounded-lg border border-border-default bg-surface-2 p-2\",\n error: \"flex flex-wrap items-center gap-3 rounded-lg border border-border-default bg-surface-2 p-3 text-sm text-fg-danger\",\n disclosure: \"text-xs text-fg-muted\",\n link: \"underline underline-offset-2 hover:text-fg-secondary focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2\",\n },\n});\n\nexport type CaptchaProps = {\n /** The widget's public site key. Empty → nothing is rendered and no token is asked (local dev, tests). */\n siteKey: string;\n /** The token once the check passes; `null` when it expires, fails or the theme redraws the widget. */\n onToken: (token: string | null) => void;\n /**\n * The language of the widget and of the words: `\"en\"`, `\"fr\"`…\n * @default \"en\"\n */\n locale?: string;\n /**\n * `auto` follows the page (`html.light` / `data-theme`, redrawn when it flips).\n * @default \"auto\"\n */\n theme?: \"auto\" | \"light\" | \"dark\";\n /** Words to say otherwise; the rest comes from the locale. */\n labels?: Partial<CaptchaLabels>;\n /** Classes added to the root. */\n className?: string;\n};\n\n/**\n * The bot check of a public form, Cloudflare Turnstile that stays out of the way: managed mode with\n * `appearance: \"interaction-only\"`, so a normal visitor sees nothing — the check runs in the background and `onToken`\n * fires. Only when Cloudflare needs the visitor does a framed box appear, on the tokens (radius, border, theme).\n * If the check cannot run, an alert with a retry button replaces it. Pair it with `CaptchaDisclosure` under the submit.\n */\nexport function Captcha({ siteKey, onToken, locale = \"en\", theme = \"auto\", labels, className }: CaptchaProps) {\n const host = useRef<HTMLDivElement>(null);\n const widget = useRef<string | null>(null);\n const onTokenRef = useRef(onToken);\n useEffect(() => {\n onTokenRef.current = onToken;\n });\n const [interactive, setInteractive] = useState(false);\n const [failed, setFailed] = useState(false);\n const [attempt, setAttempt] = useState(0);\n const words = labelsFor(locale, labels);\n const { root, frame, error } = captcha();\n\n useEffect(() => {\n if (!siteKey || !host.current) return;\n const el = host.current;\n let cancelled = false;\n const fail = () => {\n onTokenRef.current(null);\n setInteractive(false);\n setFailed(true);\n };\n const mount = () => {\n loadTurnstile().then((api) => {\n if (cancelled) return;\n if (widget.current) api.remove(widget.current);\n widget.current = api.render(el, {\n sitekey: siteKey,\n appearance: \"interaction-only\",\n size: \"flexible\",\n theme: theme === \"auto\" ? pageTheme() : theme,\n language: locale,\n callback: (token: string) => {\n setFailed(false);\n onTokenRef.current(token);\n },\n \"expired-callback\": () => onTokenRef.current(null),\n \"before-interactive-callback\": () => setInteractive(true),\n \"after-interactive-callback\": () => setInteractive(false),\n \"error-callback\": fail,\n \"unsupported-callback\": fail,\n });\n }, fail);\n };\n setFailed(false);\n mount();\n const watcher = theme === \"auto\" ? new MutationObserver(() => { onTokenRef.current(null); mount(); }) : null;\n watcher?.observe(document.documentElement, { attributes: true, attributeFilter: [\"class\", \"data-theme\"] });\n return () => {\n cancelled = true;\n watcher?.disconnect();\n if (widget.current && window.turnstile) window.turnstile.remove(widget.current);\n widget.current = null;\n };\n }, [siteKey, locale, theme, attempt]);\n\n const retry = useCallback(() => setAttempt((n) => n + 1), []);\n\n if (!siteKey) return null;\n return (\n <div className={root({ className })} role=\"group\" aria-label={words.group} data-captcha={failed ? \"error\" : interactive ? \"interactive\" : \"idle\"}>\n {failed ? (\n <div role=\"alert\" className={error()}>\n <span>{words.error}</span>\n <Button type=\"button\" size=\"sm\" variant=\"secondary\" onClick={retry}>\n {words.retry}\n </Button>\n </div>\n ) : null}\n {/* Always mounted (Cloudflare draws into it); framed and visible only when a challenge needs the visitor. */}\n <div className={interactive && !failed ? frame() : undefined} hidden={failed} style={interactive ? undefined : { height: 0, overflow: \"hidden\" }}>\n <div ref={host} />\n </div>\n </div>\n );\n}\n\nexport type CaptchaDisclosureProps = {\n /**\n * The language of the line: `\"en\"`, `\"fr\"`…\n * @default \"en\"\n */\n locale?: string;\n /** Words to say otherwise; the rest comes from the locale. */\n labels?: Partial<CaptchaLabels>;\n /** The privacy link; Cloudflare's privacy policy by default. */\n privacyHref?: string;\n /** The terms link; Cloudflare's website terms by default. */\n termsHref?: string;\n /** Classes added to the root. */\n className?: string;\n};\n\n/** « Protected by Cloudflare Turnstile · Privacy · Terms »: the line Cloudflare's terms ask for under the submit button. */\nexport function CaptchaDisclosure({ locale = \"en\", labels, privacyHref = TURNSTILE_PRIVACY, termsHref = TURNSTILE_TERMS, className }: CaptchaDisclosureProps) {\n const words = labelsFor(locale, labels);\n const { disclosure, link } = captcha();\n return (\n <p className={disclosure({ className })}>\n {words.protectedBy} · <a className={link()} href={privacyHref} target=\"_blank\" rel=\"noopener noreferrer\">{words.privacy}</a> ·{\" \"}\n <a className={link()} href={termsHref} target=\"_blank\" rel=\"noopener noreferrer\">{words.terms}</a>\n </p>\n );\n}\n","/** The words of `Captcha` and `CaptchaDisclosure`, English and French. Pass `labels` to say them otherwise. */\nexport interface CaptchaLabels {\n /** The accessible name of the check (the group around the widget). */\n readonly group: string;\n /** Said when Cloudflare could not run the check. */\n readonly error: string;\n /** The retry button. */\n readonly retry: string;\n /** « Protected by Cloudflare Turnstile ». */\n readonly protectedBy: string;\n readonly privacy: string;\n readonly terms: string;\n}\n\nexport const captchaMessages = {\n en: {\n group: \"Security check\",\n error: \"The security check could not run. Check your connection and try again.\",\n retry: \"Try again\",\n protectedBy: \"Protected by Cloudflare Turnstile\",\n privacy: \"Privacy\",\n terms: \"Terms\",\n },\n fr: {\n group: \"Vérification de sécurité\",\n error: \"La vérification de sécurité n'a pas pu s'exécuter. Vérifiez votre connexion et réessayez.\",\n retry: \"Réessayer\",\n protectedBy: \"Protégé par Cloudflare Turnstile\",\n privacy: \"Confidentialité\",\n terms: \"Conditions\",\n },\n} as const satisfies Record<string, CaptchaLabels>;\n\nexport type CaptchaLocale = keyof typeof captchaMessages;\n\nexport const labelsFor = (locale: string | undefined, labels?: Partial<CaptchaLabels>): CaptchaLabels => ({\n ...captchaMessages[locale?.toLowerCase().startsWith(\"fr\") ? \"fr\" : \"en\"],\n ...labels,\n});\n","/** The Cloudflare Turnstile browser API, loaded once, on demand. */\nexport interface TurnstileApi {\n render(el: HTMLElement, options: Record<string, unknown>): string;\n remove(id: string): void;\n reset(id: string): void;\n}\ndeclare global {\n interface Window {\n turnstile?: TurnstileApi;\n }\n}\n\nexport const TURNSTILE_SCRIPT = \"https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit\";\nexport const TURNSTILE_PRIVACY = \"https://www.cloudflare.com/privacypolicy/\";\nexport const TURNSTILE_TERMS = \"https://www.cloudflare.com/website-terms/\";\n\nlet loading: Promise<TurnstileApi> | null = null;\n\nexport function loadTurnstile(): Promise<TurnstileApi> {\n if (window.turnstile) return Promise.resolve(window.turnstile);\n loading ??= new Promise<TurnstileApi>((resolve, reject) => {\n const s = document.createElement(\"script\");\n s.src = TURNSTILE_SCRIPT;\n s.async = true;\n s.onload = () => (window.turnstile ? resolve(window.turnstile) : reject(new Error(\"turnstile\")));\n s.onerror = () => {\n loading = null;\n reject(new Error(\"turnstile\"));\n };\n try {\n document.head.appendChild(s);\n } catch {\n loading = null;\n reject(new Error(\"turnstile\"));\n }\n });\n return loading;\n}\n\n/** The page's theme: `html.light` or `data-theme=\"light\"` is light, anything else dark. */\nexport function pageTheme(): \"light\" | \"dark\" {\n const root = document.documentElement;\n return root.classList.contains(\"light\") || root.dataset.theme === \"light\" ? \"light\" : \"dark\";\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,8 @@
1
1
  import * as React from 'react';
2
2
 
3
+ /** The hover of a card (blocks.css): the pointer's light (`light`) or the quiet hover (`quiet`). */
4
+ type CardHover = "light" | "quiet";
5
+
3
6
  type CardGridProps = Omit<React.HTMLAttributes<HTMLElement>, "children"> & {
4
7
  /** The cards: one cell each. */
5
8
  children: React.ReactNode;
@@ -48,8 +51,14 @@ type CardGridProps = Omit<React.HTMLAttributes<HTMLElement>, "children"> & {
48
51
  foundationPosition?: "start" | "end";
49
52
  /** A short mono label on the frame's edge, naming the relation (`Powered by Orazaka`). Only with `foundation`. */
50
53
  foundationLabel?: React.ReactNode;
54
+ /**
55
+ * The hover of every card of the grid: `light` (the pointer's light, the default) or `quiet` (no glow, a 2 px lift, a
56
+ * ring on focus). A card that sets its own `hover` keeps it.
57
+ * @default "light"
58
+ */
59
+ hover?: CardHover;
51
60
  };
52
61
  /** A responsive grid of cards: equal rows, a staggered reveal, the pointer's light. */
53
- declare function CardGrid({ children, min, fit, align, reveal, columns, light, as: Tag, foundation, foundationPosition, foundationLabel, className, style, ...props }: CardGridProps): React.JSX.Element;
62
+ declare function CardGrid({ children, min, fit, align, reveal, columns, light, as: Tag, foundation, foundationPosition, foundationLabel, hover, className, style, ...props }: CardGridProps): React.JSX.Element;
54
63
 
55
64
  export { CardGrid, type CardGridProps };
package/dist/card-grid.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-AB5D3KBG.js";
6
6
  import {
7
7
  cn
8
- } from "./chunk-G3ETON2A.js";
8
+ } from "./chunk-WU4VLSEA.js";
9
9
 
10
10
  // src/card-grid/card-grid.tsx
11
11
  import * as React from "react";
@@ -23,6 +23,7 @@ function CardGrid({
23
23
  foundation,
24
24
  foundationPosition = "start",
25
25
  foundationLabel,
26
+ hover = "light",
26
27
  className,
27
28
  style,
28
29
  ...props
@@ -34,7 +35,7 @@ function CardGrid({
34
35
  Tag,
35
36
  {
36
37
  ref: foundation ? void 0 : ref,
37
- className: cn("kz-cards", !foundation && className),
38
+ className: cn("kz-cards", hover === "quiet" && "kz-hover-quiet", !foundation && className),
38
39
  "data-fit": fit === "fit" ? "" : void 0,
39
40
  "data-align": align === "start" ? "start" : void 0,
40
41
  style: { "--kz-cards-min": `${min}px`, ...foundation ? null : style },