@krizaka/ui 3.30.0 → 3.31.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 (394) hide show
  1. package/README.md +4 -8
  2. package/dist/action-bar.js +3 -3
  3. package/dist/action-bar.js.map +1 -1
  4. package/dist/argument-card.d.ts +1 -1
  5. package/dist/argument-card.js +2 -2
  6. package/dist/argument-card.js.map +1 -1
  7. package/dist/blocks.css +19 -60
  8. package/dist/{button-BmCrLCq7.d.ts → button-Cm9pFH3P.d.ts} +10 -9
  9. package/dist/button.d.ts +11 -10
  10. package/dist/button.js +1 -1
  11. package/dist/captcha.js +1 -1
  12. package/dist/card-grid.d.ts +5 -12
  13. package/dist/card-grid.js +4 -12
  14. package/dist/card-grid.js.map +1 -1
  15. package/dist/card.d.ts +7 -7
  16. package/dist/card.js +2 -2
  17. package/dist/card.js.map +1 -1
  18. package/dist/{chunk-GYKQ52HX.js → chunk-2AJ4NJUF.js} +2 -2
  19. package/dist/{chunk-MQPC3P4T.js → chunk-2DGLWTPS.js} +3 -3
  20. package/dist/chunk-2DGLWTPS.js.map +1 -0
  21. package/dist/{chunk-DZU6RMYD.js → chunk-3DHNN5V7.js} +15 -9
  22. package/dist/chunk-3DHNN5V7.js.map +1 -0
  23. package/dist/{chunk-TFJDIOFK.js → chunk-4E5IUZHR.js} +3 -3
  24. package/dist/chunk-4E5IUZHR.js.map +1 -0
  25. package/dist/{chunk-HAQWKORZ.js → chunk-4JO5GSSD.js} +1 -37
  26. package/dist/chunk-4JO5GSSD.js.map +1 -0
  27. package/dist/{chunk-APNIVNIX.js → chunk-IBDTAMPF.js} +3 -4
  28. package/dist/chunk-IBDTAMPF.js.map +1 -0
  29. package/dist/{chunk-LYOPPBMC.js → chunk-KIBICUZK.js} +2 -2
  30. package/dist/{chunk-HBZPJ3EP.js → chunk-NJFCSVPT.js} +2 -2
  31. package/dist/{chunk-KM6VAKIE.js → chunk-RE5WJ4QP.js} +2 -2
  32. package/dist/chunk-RE5WJ4QP.js.map +1 -0
  33. package/dist/{chunk-CHOJUCVE.js → chunk-SJS6RJIT.js} +15 -9
  34. package/dist/chunk-SJS6RJIT.js.map +1 -0
  35. package/dist/{chunk-6ZYRUOTA.js → chunk-UV4HBVH7.js} +15 -9
  36. package/dist/chunk-UV4HBVH7.js.map +1 -0
  37. package/dist/command.js +2 -2
  38. package/dist/confirm-button.d.ts +1 -1
  39. package/dist/confirm-button.js +1 -1
  40. package/dist/continue-prompt.js +14 -8
  41. package/dist/continue-prompt.js.map +1 -1
  42. package/dist/data-table.js +2 -2
  43. package/dist/dialog.js +2 -2
  44. package/dist/engine-flow.d.ts +25 -2
  45. package/dist/engine-flow.js +27 -6
  46. package/dist/engine-flow.js.map +1 -1
  47. package/dist/force-update-screen.js +2 -2
  48. package/dist/force-update-screen.js.map +1 -1
  49. package/dist/genui.js +3 -3
  50. package/dist/glow-card.d.ts +5 -4
  51. package/dist/glow-card.js +2 -8
  52. package/dist/glow-card.js.map +1 -1
  53. package/dist/image-cropper.js +1 -1
  54. package/dist/index.js +4 -4
  55. package/dist/index.js.map +1 -1
  56. package/dist/iso.d.ts +1 -1
  57. package/dist/iso.js.map +1 -1
  58. package/dist/landscape.js +4 -4
  59. package/dist/landscape.js.map +1 -1
  60. package/dist/link-card.d.ts +3 -6
  61. package/dist/link-card.js +1 -1
  62. package/dist/maintenance-screen.js +1 -1
  63. package/dist/map.d.ts +50 -5
  64. package/dist/map.js +202 -66
  65. package/dist/map.js.map +1 -1
  66. package/dist/match-dialog.js +1 -1
  67. package/dist/media-gallery.js +1 -1
  68. package/dist/media-gallery.js.map +1 -1
  69. package/dist/media-grid.js +2 -2
  70. package/dist/motion.css +52 -122
  71. package/dist/motion.d.ts +2 -2
  72. package/dist/motion.js +1 -1
  73. package/dist/native/map.js +64 -64
  74. package/dist/native/map.js.map +1 -1
  75. package/dist/native.js +30 -30
  76. package/dist/native.js.map +1 -1
  77. package/dist/nav-bar.js +2 -2
  78. package/dist/pointer-light.d.ts +6 -9
  79. package/dist/pointer-light.js +9 -5
  80. package/dist/pointer-light.js.map +1 -1
  81. package/dist/radio-group.js +2 -2
  82. package/dist/radio-group.js.map +1 -1
  83. package/dist/service-card.js +1 -1
  84. package/dist/service-card.js.map +1 -1
  85. package/dist/site-footer.js +2 -2
  86. package/dist/slider.js +1 -1
  87. package/dist/slider.js.map +1 -1
  88. package/dist/story.css +5 -12
  89. package/dist/theme.d.ts +1 -1
  90. package/dist/theme.js +1 -1
  91. package/dist/update-prompt.js +2 -2
  92. package/dist/update-prompt.js.map +1 -1
  93. package/dist/vertical-video.js +1 -1
  94. package/dist/video-player.js +2 -2
  95. package/dist/video-player.js.map +1 -1
  96. package/package.json +7 -7
  97. package/registry/action-bar.json +1 -1
  98. package/registry/app-shell.json +4 -4
  99. package/registry/argument-card.json +7 -16
  100. package/registry/auth-layout.json +4 -4
  101. package/registry/button.json +20 -20
  102. package/registry/card-grid.json +21 -24
  103. package/registry/card.json +7 -7
  104. package/registry/component-page.json +2 -2
  105. package/registry/confirm-button.json +6 -6
  106. package/registry/contact-page.json +2 -2
  107. package/registry/cover-hero.json +3 -3
  108. package/registry/dashboard-layout.json +2 -2
  109. package/registry/detail-layout.json +4 -4
  110. package/registry/docs-hub-page.json +2 -2
  111. package/registry/empty-state.json +1 -1
  112. package/registry/engine-flow.json +74 -3
  113. package/registry/examples/app-shell/admin-console.js +1 -1
  114. package/registry/examples/app-shell/admin-console.tsx +1 -1
  115. package/registry/examples/app-shell/basic.js +1 -1
  116. package/registry/examples/app-shell/basic.tsx +1 -1
  117. package/registry/examples/app-shell/desktop.js +1 -1
  118. package/registry/examples/app-shell/desktop.tsx +1 -1
  119. package/registry/examples/app-shell/orochia-studio.js +1 -1
  120. package/registry/examples/app-shell/orochia-studio.tsx +1 -1
  121. package/registry/examples/argument-card/hover-quiet.js +1 -1
  122. package/registry/examples/argument-card/hover-quiet.tsx +1 -1
  123. package/registry/examples/auth-layout/onboarding.js +2 -2
  124. package/registry/examples/auth-layout/onboarding.tsx +2 -2
  125. package/registry/examples/auth-layout/sign-in.js +2 -2
  126. package/registry/examples/auth-layout/sign-in.tsx +2 -2
  127. package/registry/examples/auth-layout/verify.js +1 -1
  128. package/registry/examples/auth-layout/verify.tsx +1 -1
  129. package/registry/examples/avatar/native/presence.dark.png +0 -0
  130. package/registry/examples/avatar/native/presence.light.png +0 -0
  131. package/registry/examples/badge/native/tones.dark.png +0 -0
  132. package/registry/examples/badge/native/tones.light.png +0 -0
  133. package/registry/examples/button/native/disabled.dark.png +0 -0
  134. package/registry/examples/button/native/disabled.light.png +0 -0
  135. package/registry/examples/button/native/gradient.dark.png +0 -0
  136. package/registry/examples/button/native/gradient.light.png +0 -0
  137. package/registry/examples/button/native/icon-only.dark.png +0 -0
  138. package/registry/examples/button/native/icon-only.light.png +0 -0
  139. package/registry/examples/button/native/icon.dark.png +0 -0
  140. package/registry/examples/button/native/icon.light.png +0 -0
  141. package/registry/examples/button/native/loading.dark.png +0 -0
  142. package/registry/examples/button/native/loading.light.png +0 -0
  143. package/registry/examples/button/native/primary.dark.png +0 -0
  144. package/registry/examples/button/native/primary.light.png +0 -0
  145. package/registry/examples/button/native/sizes.dark.png +0 -0
  146. package/registry/examples/button/native/sizes.light.png +0 -0
  147. package/registry/examples/button/signature.js +2 -2
  148. package/registry/examples/button/signature.tsx +4 -4
  149. package/registry/examples/card/interactive-link.tsx +1 -1
  150. package/registry/examples/card/native/body.dark.png +0 -0
  151. package/registry/examples/card/native/body.light.png +0 -0
  152. package/registry/examples/card/native/default.dark.png +0 -0
  153. package/registry/examples/card/native/default.light.png +0 -0
  154. package/registry/examples/card/native/elevated.dark.png +0 -0
  155. package/registry/examples/card/native/elevated.light.png +0 -0
  156. package/registry/examples/card-grid/hover-quiet.js +1 -1
  157. package/registry/examples/card-grid/hover-quiet.tsx +1 -1
  158. package/registry/examples/chat/native/assistant.dark.png +0 -0
  159. package/registry/examples/chat/native/assistant.light.png +0 -0
  160. package/registry/examples/chat/native/conversation.dark.png +0 -0
  161. package/registry/examples/chat/native/conversation.light.png +0 -0
  162. package/registry/examples/chat/native/inbox.dark.png +0 -0
  163. package/registry/examples/chat/native/inbox.light.png +0 -0
  164. package/registry/examples/checkbox/native/terms.dark.png +0 -0
  165. package/registry/examples/checkbox/native/terms.light.png +0 -0
  166. package/registry/examples/chip/native/multiple.dark.png +0 -0
  167. package/registry/examples/chip/native/multiple.light.png +0 -0
  168. package/registry/examples/chip/native/single.dark.png +0 -0
  169. package/registry/examples/chip/native/single.light.png +0 -0
  170. package/registry/examples/chip/native/toggle.dark.png +0 -0
  171. package/registry/examples/chip/native/toggle.light.png +0 -0
  172. package/registry/examples/component-page/button.js +2 -2
  173. package/registry/examples/component-page/button.tsx +2 -2
  174. package/registry/examples/contact-page/team.js +1 -1
  175. package/registry/examples/contact-page/team.tsx +1 -1
  176. package/registry/examples/cover-hero/plain.js +1 -1
  177. package/registry/examples/cover-hero/plain.tsx +1 -1
  178. package/registry/examples/cover-hero/shop.js +1 -1
  179. package/registry/examples/cover-hero/shop.tsx +1 -1
  180. package/registry/examples/dashboard-layout/orazaka-eric.js +2 -2
  181. package/registry/examples/dashboard-layout/orazaka-eric.tsx +2 -2
  182. package/registry/examples/date-picker/native/calendar.dark.png +0 -0
  183. package/registry/examples/date-picker/native/calendar.light.png +0 -0
  184. package/registry/examples/date-picker/native/day-strip.dark.png +0 -0
  185. package/registry/examples/date-picker/native/day-strip.light.png +0 -0
  186. package/registry/examples/date-picker/native/month-year.dark.png +0 -0
  187. package/registry/examples/date-picker/native/month-year.light.png +0 -0
  188. package/registry/examples/date-picker/native/time-slots.dark.png +0 -0
  189. package/registry/examples/date-picker/native/time-slots.light.png +0 -0
  190. package/registry/examples/description-list/native/job-details.dark.png +0 -0
  191. package/registry/examples/description-list/native/job-details.light.png +0 -0
  192. package/registry/examples/detail-layout/checkout.js +1 -1
  193. package/registry/examples/detail-layout/checkout.tsx +1 -1
  194. package/registry/examples/detail-layout/job.js +1 -1
  195. package/registry/examples/detail-layout/job.tsx +1 -1
  196. package/registry/examples/detail-layout/video.js +2 -2
  197. package/registry/examples/detail-layout/video.tsx +2 -2
  198. package/registry/examples/dialog/native/sheet.dark.png +0 -0
  199. package/registry/examples/dialog/native/sheet.light.png +0 -0
  200. package/registry/examples/docs-hub-page/krizaka.js +1 -1
  201. package/registry/examples/docs-hub-page/krizaka.tsx +1 -1
  202. package/registry/examples/empty-state/library.js +1 -1
  203. package/registry/examples/empty-state/library.tsx +1 -1
  204. package/registry/examples/empty-state/native/default.dark.png +0 -0
  205. package/registry/examples/empty-state/native/default.light.png +0 -0
  206. package/registry/examples/fab/native/extended.dark.png +0 -0
  207. package/registry/examples/fab/native/extended.light.png +0 -0
  208. package/registry/examples/force-update-screen/native/default.dark.png +0 -0
  209. package/registry/examples/force-update-screen/native/default.light.png +0 -0
  210. package/registry/examples/form/native/amount.dark.png +0 -0
  211. package/registry/examples/form/native/amount.light.png +0 -0
  212. package/registry/examples/form/native/audience.dark.png +0 -0
  213. package/registry/examples/form/native/audience.light.png +0 -0
  214. package/registry/examples/form/native/profile.dark.png +0 -0
  215. package/registry/examples/form/native/profile.light.png +0 -0
  216. package/registry/examples/form/native/settings.dark.png +0 -0
  217. package/registry/examples/form/native/settings.light.png +0 -0
  218. package/registry/examples/form/native/sign-up.dark.png +0 -0
  219. package/registry/examples/form/native/sign-up.light.png +0 -0
  220. package/registry/examples/form/native/upload.dark.png +0 -0
  221. package/registry/examples/form/native/upload.light.png +0 -0
  222. package/registry/examples/glow-card/spotlight.tsx +1 -1
  223. package/registry/examples/goal-gauge/native/default.dark.png +0 -0
  224. package/registry/examples/goal-gauge/native/default.light.png +0 -0
  225. package/registry/examples/landing-page/krizaka.js +3 -3
  226. package/registry/examples/landing-page/krizaka.tsx +3 -3
  227. package/registry/examples/link-card/hover-quiet.js +1 -1
  228. package/registry/examples/link-card/hover-quiet.tsx +1 -1
  229. package/registry/examples/loader/native/block.dark.png +0 -0
  230. package/registry/examples/loader/native/block.light.png +0 -0
  231. package/registry/examples/map/native/location-picker.dark.png +0 -0
  232. package/registry/examples/map/native/location-picker.light.png +0 -0
  233. package/registry/examples/map/route.js +36 -0
  234. package/registry/examples/map/route.tsx +35 -0
  235. package/registry/examples/marketing-layout/pricing.js +1 -1
  236. package/registry/examples/marketing-layout/pricing.tsx +1 -1
  237. package/registry/examples/marketing-layout/product-page.js +1 -1
  238. package/registry/examples/marketing-layout/product-page.tsx +2 -2
  239. package/registry/examples/mascot-state/native/default.dark.png +0 -0
  240. package/registry/examples/mascot-state/native/default.light.png +0 -0
  241. package/registry/examples/mascots/native/crane.dark.png +0 -0
  242. package/registry/examples/mascots/native/crane.light.png +0 -0
  243. package/registry/examples/mascots/native/default.dark.png +0 -0
  244. package/registry/examples/mascots/native/default.light.png +0 -0
  245. package/registry/examples/mascots/native/lovebird.dark.png +0 -0
  246. package/registry/examples/mascots/native/lovebird.light.png +0 -0
  247. package/registry/examples/mascots/native/phoenix.dark.png +0 -0
  248. package/registry/examples/mascots/native/phoenix.light.png +0 -0
  249. package/registry/examples/match-dialog/native/its-a-match.dark.png +0 -0
  250. package/registry/examples/match-dialog/native/its-a-match.light.png +0 -0
  251. package/registry/examples/media-capture/native/photo.dark.png +0 -0
  252. package/registry/examples/media-capture/native/photo.light.png +0 -0
  253. package/registry/examples/meta-row/native/video.dark.png +0 -0
  254. package/registry/examples/meta-row/native/video.light.png +0 -0
  255. package/registry/examples/meter/native/credits.dark.png +0 -0
  256. package/registry/examples/meter/native/credits.light.png +0 -0
  257. package/registry/examples/meter/native/split-ring.dark.png +0 -0
  258. package/registry/examples/meter/native/split-ring.light.png +0 -0
  259. package/registry/examples/nav-bar/family-product.js +1 -1
  260. package/registry/examples/nav-bar/family-product.tsx +1 -1
  261. package/registry/examples/onboarding/native/first-launch.dark.png +0 -0
  262. package/registry/examples/onboarding/native/first-launch.light.png +0 -0
  263. package/registry/examples/permission-prompt/native/blocked.dark.png +0 -0
  264. package/registry/examples/permission-prompt/native/blocked.light.png +0 -0
  265. package/registry/examples/permission-prompt/native/camera.dark.png +0 -0
  266. package/registry/examples/permission-prompt/native/camera.light.png +0 -0
  267. package/registry/examples/photo-grid/native/profile-photos.dark.png +0 -0
  268. package/registry/examples/photo-grid/native/profile-photos.light.png +0 -0
  269. package/registry/examples/pricing-page/credits.js +3 -3
  270. package/registry/examples/pricing-page/credits.tsx +3 -3
  271. package/registry/examples/product-page/orazaka.js +2 -2
  272. package/registry/examples/product-page/orazaka.tsx +2 -2
  273. package/registry/examples/profile-card/native/default.dark.png +0 -0
  274. package/registry/examples/profile-card/native/default.light.png +0 -0
  275. package/registry/examples/progress/native/bar.dark.png +0 -0
  276. package/registry/examples/progress/native/bar.light.png +0 -0
  277. package/registry/examples/progress/native/indeterminate.dark.png +0 -0
  278. package/registry/examples/progress/native/indeterminate.light.png +0 -0
  279. package/registry/examples/progress/native/ring.dark.png +0 -0
  280. package/registry/examples/progress/native/ring.light.png +0 -0
  281. package/registry/examples/radio-group/native/buttons.dark.png +0 -0
  282. package/registry/examples/radio-group/native/buttons.light.png +0 -0
  283. package/registry/examples/radio-group/native/cards.dark.png +0 -0
  284. package/registry/examples/radio-group/native/cards.light.png +0 -0
  285. package/registry/examples/radio-group/native/list.dark.png +0 -0
  286. package/registry/examples/radio-group/native/list.light.png +0 -0
  287. package/registry/examples/screen/native/auth.dark.png +0 -0
  288. package/registry/examples/screen/native/auth.light.png +0 -0
  289. package/registry/examples/screen/native/chat.dark.png +0 -0
  290. package/registry/examples/screen/native/chat.light.png +0 -0
  291. package/registry/examples/screen/native/profile.dark.png +0 -0
  292. package/registry/examples/screen/native/profile.light.png +0 -0
  293. package/registry/examples/screen/native/settings.dark.png +0 -0
  294. package/registry/examples/screen/native/settings.light.png +0 -0
  295. package/registry/examples/search-field/native/cancel.dark.png +0 -0
  296. package/registry/examples/search-field/native/cancel.light.png +0 -0
  297. package/registry/examples/settings-layout/account.js +1 -1
  298. package/registry/examples/settings-layout/account.tsx +1 -1
  299. package/registry/examples/settings-layout/native/account.dark.png +0 -0
  300. package/registry/examples/settings-layout/native/account.light.png +0 -0
  301. package/registry/examples/settings-layout/sections.js +1 -1
  302. package/registry/examples/settings-layout/sections.tsx +1 -1
  303. package/registry/examples/showcase/native/slides.dark.png +0 -0
  304. package/registry/examples/showcase/native/slides.light.png +0 -0
  305. package/registry/examples/slider/native/default.dark.png +0 -0
  306. package/registry/examples/slider/native/default.light.png +0 -0
  307. package/registry/examples/slider/native/range.dark.png +0 -0
  308. package/registry/examples/slider/native/range.light.png +0 -0
  309. package/registry/examples/slider/native/stepped.dark.png +0 -0
  310. package/registry/examples/slider/native/stepped.light.png +0 -0
  311. package/registry/examples/slider/native/vertical.dark.png +0 -0
  312. package/registry/examples/slider/native/vertical.light.png +0 -0
  313. package/registry/examples/spinner/native/sizes.dark.png +0 -0
  314. package/registry/examples/spinner/native/sizes.light.png +0 -0
  315. package/registry/examples/split-view/messages.js +1 -1
  316. package/registry/examples/split-view/messages.tsx +1 -1
  317. package/registry/examples/status-page/building.js +1 -1
  318. package/registry/examples/status-page/building.tsx +1 -1
  319. package/registry/examples/step-progress/native/sign-up.dark.png +0 -0
  320. package/registry/examples/step-progress/native/sign-up.light.png +0 -0
  321. package/registry/examples/stepper/native/run.dark.png +0 -0
  322. package/registry/examples/stepper/native/run.light.png +0 -0
  323. package/registry/examples/sticky-action/phone.js +1 -1
  324. package/registry/examples/sticky-action/phone.tsx +1 -1
  325. package/registry/examples/switch/native/default.dark.png +0 -0
  326. package/registry/examples/switch/native/default.light.png +0 -0
  327. package/registry/examples/switch/native/settings.dark.png +0 -0
  328. package/registry/examples/switch/native/settings.light.png +0 -0
  329. package/registry/examples/tab-bar/native/main-sections.dark.png +0 -0
  330. package/registry/examples/tab-bar/native/main-sections.light.png +0 -0
  331. package/registry/examples/tabs/native/sections.dark.png +0 -0
  332. package/registry/examples/tabs/native/sections.light.png +0 -0
  333. package/registry/examples/tag-selector/native/interests.dark.png +0 -0
  334. package/registry/examples/tag-selector/native/interests.light.png +0 -0
  335. package/registry/examples/toast/native/snackbar.dark.png +0 -0
  336. package/registry/examples/toast/native/snackbar.light.png +0 -0
  337. package/registry/examples/toast/native/tones.dark.png +0 -0
  338. package/registry/examples/toast/native/tones.light.png +0 -0
  339. package/registry/examples/txt/native/tones.dark.png +0 -0
  340. package/registry/examples/txt/native/tones.light.png +0 -0
  341. package/registry/examples/update-prompt/native/default.dark.png +0 -0
  342. package/registry/examples/update-prompt/native/default.light.png +0 -0
  343. package/registry/examples/verified-badge/native/verified.dark.png +0 -0
  344. package/registry/examples/verified-badge/native/verified.light.png +0 -0
  345. package/registry/examples/vertical-video/native/rail.dark.png +0 -0
  346. package/registry/examples/vertical-video/native/rail.light.png +0 -0
  347. package/registry/examples/waveform/native/card.dark.png +0 -0
  348. package/registry/examples/waveform/native/card.light.png +0 -0
  349. package/registry/force-update-screen.json +1 -1
  350. package/registry/form.json +6 -6
  351. package/registry/glow-card.json +19 -19
  352. package/registry/index.json +20 -21
  353. package/registry/iso.json +7 -7
  354. package/registry/landing-page.json +2 -2
  355. package/registry/landscape.json +1 -1
  356. package/registry/link-card.json +15 -24
  357. package/registry/map.json +235 -6
  358. package/registry/marketing-layout.json +3 -3
  359. package/registry/media-gallery.json +1 -1
  360. package/registry/nav-bar.json +3 -3
  361. package/registry/pointer-light.json +17 -22
  362. package/registry/pricing-page.json +2 -2
  363. package/registry/product-page.json +2 -2
  364. package/registry/progress.json +2 -2
  365. package/registry/radio-group.json +1 -1
  366. package/registry/rail-nav.json +2 -2
  367. package/registry/service-card.json +1 -1
  368. package/registry/settings-layout.json +3 -3
  369. package/registry/site-footer.json +2 -2
  370. package/registry/slider.json +1 -1
  371. package/registry/split-view.json +2 -2
  372. package/registry/status-page.json +1 -1
  373. package/registry/sticky-action.json +2 -2
  374. package/registry/update-prompt.json +1 -1
  375. package/registry/video-player.json +1 -1
  376. package/dist/chunk-6ZYRUOTA.js.map +0 -1
  377. package/dist/chunk-AB5D3KBG.js +0 -37
  378. package/dist/chunk-AB5D3KBG.js.map +0 -1
  379. package/dist/chunk-APNIVNIX.js.map +0 -1
  380. package/dist/chunk-CHOJUCVE.js.map +0 -1
  381. package/dist/chunk-DZU6RMYD.js.map +0 -1
  382. package/dist/chunk-HAQWKORZ.js.map +0 -1
  383. package/dist/chunk-KM6VAKIE.js.map +0 -1
  384. package/dist/chunk-MQPC3P4T.js.map +0 -1
  385. package/dist/chunk-TFJDIOFK.js.map +0 -1
  386. package/registry/examples/argument-card/hover-light.js +0 -17
  387. package/registry/examples/argument-card/hover-light.tsx +0 -19
  388. package/registry/examples/link-card/hover-light.d.ts +0 -4
  389. package/registry/examples/link-card/hover-light.js +0 -18
  390. package/registry/examples/link-card/hover-light.tsx +0 -20
  391. /package/dist/{chunk-GYKQ52HX.js.map → chunk-2AJ4NJUF.js.map} +0 -0
  392. /package/dist/{chunk-LYOPPBMC.js.map → chunk-KIBICUZK.js.map} +0 -0
  393. /package/dist/{chunk-HBZPJ3EP.js.map → chunk-NJFCSVPT.js.map} +0 -0
  394. /package/registry/examples/{argument-card/hover-light.d.ts → map/route.d.ts} +0 -0
@@ -100,7 +100,7 @@
100
100
  },
101
101
  {
102
102
  "path": "media-gallery/media-gallery.tsx",
103
- "content": "// Client (the reader's motion preference). A shop's photos and loops in tiles: the first is the featured one, loops play\n// muted while their tile is on screen (a still under reduced motion), a caption rides the foot of a tile.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const mediaGallery = tv({\n slots: {\n root: \"m-0 grid list-none grid-cols-2 gap-2 p-0 sm:gap-3 md:grid-cols-4\",\n tile:\n \"group relative isolate m-0 aspect-[3/4] overflow-hidden rounded-xl border border-border-default bg-media data-[shape=wide]:aspect-[4/3] \" +\n // The featured tile takes two columns; from md up it has no ratio of its own and stretches to the height of the row its neighbours set.\n \"data-[featured]:col-span-2 data-[featured]:aspect-[4/3] md:data-[featured]:aspect-auto\",\n media: \"absolute inset-0 h-full w-full object-cover transition-transform duration-500 motion-safe:group-hover:scale-105\",\n caption: \"pointer-events-none absolute inset-x-0 bottom-0 bg-linear-to-t from-scrim-strong to-transparent p-3 pt-8 text-xs font-semibold text-fg-on-media\",\n },\n});\nconst s = mediaGallery();\n\nexport type GalleryMedia = {\n /** A stable id. */\n id: string;\n /** The still: a photo, or the poster of a loop. */\n src: string;\n /** What it shows, in words. */\n alt: string;\n /** A muted loop: WebM first, MP4 for Safari. */\n video?: { webm?: string; mp4?: string };\n /** One line over the foot of the tile. */\n caption?: string;\n /** `wide` for a landscape source (4:3 tile); the default tile is a 3:4 portrait. */\n shape?: \"portrait\" | \"wide\";\n};\n\nexport type MediaGalleryProps = Omit<React.ComponentProps<\"ul\">, \"children\"> & {\n /** The photos and loops, the featured one first. */\n items: readonly GalleryMedia[];\n /** The gallery's accessible name, passed translated. */\n label: string;\n};\n\nfunction Tile({ item, featured }: { item: GalleryMedia; featured: boolean }) {\n const ref = React.useRef<HTMLLIElement>(null);\n const [play, setPlay] = React.useState(false);\n React.useEffect(() => {\n const node = ref.current;\n if (!item.video || !node || window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n const io = new IntersectionObserver(([entry]) => setPlay(Boolean(entry?.isIntersecting)), { rootMargin: \"120px\" });\n io.observe(node);\n return () => io.disconnect();\n }, [item.video]);\n return (\n <li ref={ref} className={s.tile()} data-featured={featured ? \"\" : undefined} data-shape={item.shape ?? \"portrait\"}>\n <figure className=\"m-0 h-full w-full\">\n <img src={item.src} alt={item.alt} loading=\"lazy\" decoding=\"async\" className={s.media()} />\n {play && item.video && (\n <video className={s.media()} poster={item.src} autoPlay muted loop playsInline preload=\"none\" aria-hidden tabIndex={-1}>\n {item.video.webm && <source src={item.video.webm} type=\"video/webm\" />}\n {item.video.mp4 && <source src={item.video.mp4} type=\"video/mp4\" />}\n </video>\n )}\n {item.caption && <figcaption className={s.caption()}>{item.caption}</figcaption>}\n </figure>\n </li>\n );\n}\n\n/** Photos and loops in tiles, the first featured; loops play while seen. */\nexport function MediaGallery({ items, label, className, ...props }: MediaGalleryProps) {\n return (\n <ul aria-label={label} className={s.root({ className })} {...props}>\n {items.map((item, i) => (\n <Tile key={item.id} item={item} featured={i === 0 && items.length > 2} />\n ))}\n </ul>\n );\n}\n"
103
+ "content": "// Client (the reader's motion preference). A shop's photos and loops in tiles: the first is the featured one, loops play\n// muted while their tile is on screen (a still under reduced motion), a caption rides the foot of a tile.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const mediaGallery = tv({\n slots: {\n root: \"m-0 grid list-none grid-cols-2 gap-2 p-0 sm:gap-3 md:grid-cols-4\",\n tile:\n \"group relative isolate m-0 aspect-[3/4] overflow-hidden rounded-xl border border-border-default bg-media data-[shape=wide]:aspect-[4/3] \" +\n // The featured tile takes two columns; from md up it has no ratio of its own and stretches to the height of the row its neighbours set.\n \"data-[featured]:col-span-2 data-[featured]:aspect-[4/3] md:data-[featured]:aspect-auto\",\n media: \"absolute inset-0 h-full w-full object-cover\",\n caption: \"pointer-events-none absolute inset-x-0 bottom-0 bg-linear-to-t from-scrim-strong to-transparent p-3 pt-8 text-xs font-semibold text-fg-on-media\",\n },\n});\nconst s = mediaGallery();\n\nexport type GalleryMedia = {\n /** A stable id. */\n id: string;\n /** The still: a photo, or the poster of a loop. */\n src: string;\n /** What it shows, in words. */\n alt: string;\n /** A muted loop: WebM first, MP4 for Safari. */\n video?: { webm?: string; mp4?: string };\n /** One line over the foot of the tile. */\n caption?: string;\n /** `wide` for a landscape source (4:3 tile); the default tile is a 3:4 portrait. */\n shape?: \"portrait\" | \"wide\";\n};\n\nexport type MediaGalleryProps = Omit<React.ComponentProps<\"ul\">, \"children\"> & {\n /** The photos and loops, the featured one first. */\n items: readonly GalleryMedia[];\n /** The gallery's accessible name, passed translated. */\n label: string;\n};\n\nfunction Tile({ item, featured }: { item: GalleryMedia; featured: boolean }) {\n const ref = React.useRef<HTMLLIElement>(null);\n const [play, setPlay] = React.useState(false);\n React.useEffect(() => {\n const node = ref.current;\n if (!item.video || !node || window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n const io = new IntersectionObserver(([entry]) => setPlay(Boolean(entry?.isIntersecting)), { rootMargin: \"120px\" });\n io.observe(node);\n return () => io.disconnect();\n }, [item.video]);\n return (\n <li ref={ref} className={s.tile()} data-featured={featured ? \"\" : undefined} data-shape={item.shape ?? \"portrait\"}>\n <figure className=\"m-0 h-full w-full\">\n <img src={item.src} alt={item.alt} loading=\"lazy\" decoding=\"async\" className={s.media()} />\n {play && item.video && (\n <video className={s.media()} poster={item.src} autoPlay muted loop playsInline preload=\"none\" aria-hidden tabIndex={-1}>\n {item.video.webm && <source src={item.video.webm} type=\"video/webm\" />}\n {item.video.mp4 && <source src={item.video.mp4} type=\"video/mp4\" />}\n </video>\n )}\n {item.caption && <figcaption className={s.caption()}>{item.caption}</figcaption>}\n </figure>\n </li>\n );\n}\n\n/** Photos and loops in tiles, the first featured; loops play while seen. */\nexport function MediaGallery({ items, label, className, ...props }: MediaGalleryProps) {\n return (\n <ul aria-label={label} className={s.root({ className })} {...props}>\n {items.map((item, i) => (\n <Tile key={item.id} item={item} featured={i === 0 && items.length > 2} />\n ))}\n </ul>\n );\n}\n"
104
104
  },
105
105
  {
106
106
  "path": "media-gallery/meta.ts",
@@ -478,7 +478,7 @@
478
478
  "description": "A product's site, the standard of every Krizaka product: its mark alone, Product ▾ · Platforms ▾ · Docs with menus (icon · title · a description of two lines), the repository, the theme, the language, Sign in and “Talk to us”; the phone panel groups the same entries and ends with the preferences.",
479
479
  "path": "examples/nav-bar/family-product.tsx",
480
480
  "module": "@krizaka/ui/registry/examples/nav-bar/family-product",
481
- "code": "import { OrazakaLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { languagesFor } from \"@krizaka/ui/language-switch\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\nimport { ThemeProvider, ThemeToggle } from \"@krizaka/ui/theme\";\n\n// The product standard: mark · Product ▾ · Platforms ▾ · Docs; theme, language, Sign in, one action; phone: groups, preferences.\nconst groups = [\n { id: \"product\", label: \"Product\", links: [[\"#how\", \"How it works\", \"Install to first answer in three commands.\"], [\"#demo\", \"Demo\", \"Real runs: prompts, images, videos.\"]] },\n { id: \"platforms\", label: \"Platforms\", links: [[\"#web\", \"Web\", \"The workspace in your browser.\"], [\"#mac\", \"Mac\", \"Orazaka Desktop for Apple Silicon.\"]] },\n];\nconst icon = <svg data-kz-icon viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" aria-hidden><circle data-node cx=\"12\" cy=\"12\" r=\"4\" /></svg>;\nconst [theme, language] = [<ThemeToggle key=\"t\" label=\"Change the theme\" />, <NavBar.Language key=\"l\" label=\"Language\" current=\"en\" languages={languagesFor(\"/en\")} />];\nexport default function NavBarFamilyProduct() {\n return (\n <ThemeProvider>\n <NavBar.Root label=\"Main\" floating={false}>\n <NavBar.Brand href=\"#home\"><OrazakaLogo size={22} />Orazaka</NavBar.Brand>\n <NavBar.Items>\n {groups.map((g) => (\n <NavBar.Menu key={g.id} value={g.id} label={g.label}>\n {g.links.map(([href, title, line]) => <NavBar.MenuLink key={href} href={href} title={title} description={line} icon={icon} />)}\n </NavBar.Menu>\n ))}\n <NavBar.Link href=\"#docs\">Docs</NavBar.Link></NavBar.Items>\n <NavBar.Actions>\n <span className=\"hidden items-center gap-1 md:inline-flex\">{theme}{language}</span>\n <Button asChild variant=\"ghost\" size=\"sm\" className=\"hidden md:inline-flex\"><a href=\"#sign-in\">Sign in</a></Button>\n <Button asChild variant=\"primary\" size=\"sm\" shape=\"cut\" className=\"hidden md:inline-flex\"><a href=\"#talk\">Talk to us</a></Button>\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n {groups.map((g) => (\n <NavBar.MobileGroup key={g.id} label={g.label}>{g.links.map(([href, title]) => <NavBar.MobileLink key={href} href={href}>{title}</NavBar.MobileLink>)}</NavBar.MobileGroup>\n ))}\n <NavBar.MobileLink href=\"#docs\">Docs</NavBar.MobileLink><NavBar.MobilePreferences label=\"Preferences\">{theme}{language}</NavBar.MobilePreferences>\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n </ThemeProvider>\n );\n}\n"
481
+ "code": "import { OrazakaLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { languagesFor } from \"@krizaka/ui/language-switch\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\nimport { ThemeProvider, ThemeToggle } from \"@krizaka/ui/theme\";\n\n// The product standard: mark · Product ▾ · Platforms ▾ · Docs; theme, language, Sign in, one action; phone: groups, preferences.\nconst groups = [\n { id: \"product\", label: \"Product\", links: [[\"#how\", \"How it works\", \"Install to first answer in three commands.\"], [\"#demo\", \"Demo\", \"Real runs: prompts, images, videos.\"]] },\n { id: \"platforms\", label: \"Platforms\", links: [[\"#web\", \"Web\", \"The workspace in your browser.\"], [\"#mac\", \"Mac\", \"Orazaka Desktop for Apple Silicon.\"]] },\n];\nconst icon = <svg data-kz-icon viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" aria-hidden><circle data-node cx=\"12\" cy=\"12\" r=\"4\" /></svg>;\nconst [theme, language] = [<ThemeToggle key=\"t\" label=\"Change the theme\" />, <NavBar.Language key=\"l\" label=\"Language\" current=\"en\" languages={languagesFor(\"/en\")} />];\nexport default function NavBarFamilyProduct() {\n return (\n <ThemeProvider>\n <NavBar.Root label=\"Main\" floating={false}>\n <NavBar.Brand href=\"#home\"><OrazakaLogo size={22} />Orazaka</NavBar.Brand>\n <NavBar.Items>\n {groups.map((g) => (\n <NavBar.Menu key={g.id} value={g.id} label={g.label}>\n {g.links.map(([href, title, line]) => <NavBar.MenuLink key={href} href={href} title={title} description={line} icon={icon} />)}\n </NavBar.Menu>\n ))}\n <NavBar.Link href=\"#docs\">Docs</NavBar.Link></NavBar.Items>\n <NavBar.Actions>\n <span className=\"hidden items-center gap-1 md:inline-flex\">{theme}{language}</span>\n <Button asChild variant=\"ghost\" size=\"sm\" className=\"hidden md:inline-flex\"><a href=\"#sign-in\">Sign in</a></Button>\n <Button asChild variant=\"primary\" size=\"sm\" className=\"hidden md:inline-flex\"><a href=\"#talk\">Talk to us</a></Button>\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n {groups.map((g) => (\n <NavBar.MobileGroup key={g.id} label={g.label}>{g.links.map(([href, title]) => <NavBar.MobileLink key={href} href={href}>{title}</NavBar.MobileLink>)}</NavBar.MobileGroup>\n ))}\n <NavBar.MobileLink href=\"#docs\">Docs</NavBar.MobileLink><NavBar.MobilePreferences label=\"Preferences\">{theme}{language}</NavBar.MobilePreferences>\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n </ThemeProvider>\n );\n}\n"
482
482
  },
483
483
  {
484
484
  "name": "family-admin",
@@ -566,7 +566,7 @@
566
566
  },
567
567
  {
568
568
  "path": "marks/krizaka-geometry.ts",
569
- "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Krizaka mark's geometry — the one description drawn by the web mark (KrizakaLogo) and the native one\n * (KrizakaMark). Built on the Orochia mark's grammar (docs: packages/tokens/BRAND.md, \"Marks\"): a 360×360 artwork at\n * (20, 20) in a 400×400 view box, one bold stroke (22) drawn as an open figure, an end that breaks out as a node, a\n * core, a dashed orbit and a ring; below 48 px the view box crops to the emblem.\n *\n * Krizaka is the parent: the hexagonal shield is **ink** (the host's text roles), the node and the core carry the\n * Krizaka blue (`marks.krizaka` of @krizaka/tokens: the brand's accent hsl(217 92% 53%) → its text tint hsl(217 92% 68%)).\n */\nexport const KRIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"86 82 228 228\",\n offset: 20,\n /** The shield: a pointy-top hexagon (r 92 around 180,180), open at its upper-right edge. */\n shield: \"M259.7,134 L259.7,226 L180,272 L100.3,226 L100.3,134 L180,88 L212,106.5\",\n stroke: 22,\n ink: { x1: 70, y1: 290, x2: 290, y2: 70 },\n blue: { x1: 180, y1: 180, x2: 300, y2: 60, stops: marks.krizaka },\n /** The vertex where the shield opens: a node (the serpent's head in Orochia's grammar). */\n node: { cx: 259.7, cy: 134, r: 19, inner: 6 },\n core: { cx: 180, cy: 180, r: 24, inner: 8 },\n highlight: \"#dbeafe\",\n glow: { color: \"#196df5\", opacity: 0.16 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
569
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Krizaka mark's geometry — the one description drawn by the web mark (KrizakaLogo) and the native one\n * (KrizakaMark). Built on the Orochia mark's grammar (docs: packages/tokens/BRAND.md, \"Marks\"): a 360×360 artwork at\n * (20, 20) in a 400×400 view box, one bold stroke (22) drawn as an open figure, an end that breaks out as a node, a\n * core, a dashed orbit and a ring; below 48 px the view box crops to the emblem.\n *\n * Krizaka is the parent: the hexagonal shield is **ink** (the host's text roles), the node and the core carry the\n * Krizaka blue (`marks.krizaka` of @krizaka/tokens: the brand's accent hsl(208 46% 45%) → its text tint hsl(206 58% 72%)).\n */\nexport const KRIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"86 82 228 228\",\n offset: 20,\n /** The shield: a pointy-top hexagon (r 92 around 180,180), open at its upper-right edge. */\n shield: \"M259.7,134 L259.7,226 L180,272 L100.3,226 L100.3,134 L180,88 L212,106.5\",\n stroke: 22,\n ink: { x1: 70, y1: 290, x2: 290, y2: 70 },\n blue: { x1: 180, y1: 180, x2: 300, y2: 60, stops: marks.krizaka },\n /** The vertex where the shield opens: a node (the serpent's head in Orochia's grammar). */\n node: { cx: 259.7, cy: 134, r: 19, inner: 6 },\n core: { cx: 180, cy: 180, r: 24, inner: 8 },\n highlight: \"#ddeaf4\",\n glow: { color: marks.krizaka[0]![1], opacity: 0.16 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
570
570
  },
571
571
  {
572
572
  "path": "marks/orazaka-geometry.ts",
@@ -618,7 +618,7 @@
618
618
  }
619
619
  ],
620
620
  "dependencies": [
621
- "@krizaka/tokens@^3.30.0",
621
+ "@krizaka/tokens@^3.31.0",
622
622
  "radix-ui@^1.7.0",
623
623
  "tailwind-variants@^3.3.1"
624
624
  ],
@@ -2,10 +2,15 @@
2
2
  "name": "pointer-light",
3
3
  "type": "primitive",
4
4
  "title": "Pointer light",
5
- "summary": "The light that follows the pointer over a group of cards: one passive listener writes the pointer's position into the card it is over, the card's CSS draws the light.",
6
- "description": "The light that follows the pointer over a group of cards: one passive listener writes the pointer's position into the card it is over, the card's CSS draws the light.",
7
- "status": "beta",
8
- "deprecated": null,
5
+ "summary": "Deprecated (krizaka-ui#393): it drew a light that followed the pointer over cards. It now does nothing — the cards have one quiet hover of their own.",
6
+ "description": "Deprecated (krizaka-ui#393): it drew a light that followed the pointer over cards. It now does nothing — the cards have one quiet hover of their own.",
7
+ "status": "deprecated",
8
+ "deprecated": {
9
+ "since": "3.31.0",
10
+ "removedIn": "4.0.0",
11
+ "use": "card-grid",
12
+ "reason": "krizaka-ui#393: the light that followed the pointer is retired — every card has one quiet hover (a surface and hairline step, no movement, no light). It renders a hidden span and does nothing."
13
+ },
9
14
  "category": "blocks",
10
15
  "platforms": "web",
11
16
  "parity": {
@@ -55,20 +60,15 @@
55
60
  "entry": "@krizaka/ui/pointer-light",
56
61
  "import": "import { PointerLight, usePointerLight } from \"@krizaka/ui/pointer-light\";",
57
62
  "props": [
58
- {
59
- "component": "POINTER_LIGHT_TARGET",
60
- "description": "Every card of the page blocks carries it: LinkCard, ArgumentCard.",
61
- "props": []
62
- },
63
63
  {
64
64
  "component": "PointerLight",
65
- "description": "Drop it inside any group of cards (a grid, a list) — it listens on its parent element. Renders an empty, hidden span.\nCardGrid includes it already.",
65
+ "description": "",
66
66
  "props": [
67
67
  {
68
68
  "name": "target",
69
69
  "type": "string",
70
70
  "default": "[data-kz-light]",
71
- "description": "The cards to light, as a selector inside the parent element.",
71
+ "description": "Ignored.",
72
72
  "required": false
73
73
  }
74
74
  ]
@@ -78,7 +78,7 @@
78
78
  {
79
79
  "name": "list",
80
80
  "title": "A row of cards",
81
- "description": "Two link cards in a flex row, lit by the pointer.",
81
+ "description": "Two link cards in a flex row, with their own quiet hover: the deprecated PointerLight inside adds nothing.",
82
82
  "path": "examples/pointer-light/list.tsx",
83
83
  "module": "@krizaka/ui/registry/examples/pointer-light/list",
84
84
  "code": "import { LinkCard } from \"@krizaka/ui/link-card\";\nimport { PointerLight } from \"@krizaka/ui/pointer-light\";\n\n// Any group of cards that is not a CardGrid (a flex row, a list): drop `PointerLight` inside it — it listens on its\n// parent and lights the card the pointer is over (`[data-kz-light]`). Nothing on touch screens or under reduced motion.\nexport default function PointerLightList() {\n return (\n <div className=\"flex w-full max-w-3xl flex-wrap gap-3\">\n <LinkCard className=\"flex-1 basis-56\" href=\"#tokens\" title=\"Tokens\" description=\"Every colour, radius and shadow.\" />\n <LinkCard className=\"flex-1 basis-56\" href=\"#preset\" title=\"Tailwind preset\" description=\"The tokens as utilities.\" />\n <PointerLight />\n </div>\n );\n}\n"
@@ -93,15 +93,15 @@
93
93
  },
94
94
  {
95
95
  "path": "pointer-light/index.ts",
96
- "content": "/** @krizaka/ui/pointer-light — `PointerLight`, `usePointerLight`: the light that follows the pointer over cards. Client. */\nexport { POINTER_LIGHT_TARGET, PointerLight, type PointerLightProps, usePointerLight } from \"./pointer-light\";\n"
96
+ "content": "/** @krizaka/ui/pointer-light — deprecated since 3.31 (krizaka-ui#393): `PointerLight` and `usePointerLight` do nothing. */\nexport { POINTER_LIGHT_TARGET, PointerLight, type PointerLightProps, usePointerLight } from \"./pointer-light\";\n"
97
97
  },
98
98
  {
99
99
  "path": "pointer-light/meta.ts",
100
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Pointer light\",\n summary:\n \"The light that follows the pointer over a group of cards: one passive listener writes the pointer's position into the card it is over, the card's CSS draws the light.\",\n status: \"beta\",\n category: \"blocks\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"It follows a mouse pointer; a phone has none.\" }, desktop: { status: \"n/a\", reason: \"A public site's page, not a window of the Mac app.\" } },\n whenToUse: [\n \"Inside a group of cards that is not a `CardGrid` (a flex row, a list, a carousel).\",\n \"With `usePointerLight` in a component of your own that lays cards out.\",\n ],\n whenNotToUse: [\n { when: \"Inside a CardGrid: it lights its cards already.\", use: \"card-grid\" },\n { when: \"On one card alone, with a ring of the brand's gradient.\", use: \"glow-card\" },\n ],\n bestPractices: [\n \"One light per group: drop it once, as the last child of the group.\",\n \"The cards of the blocks carry `data-kz-light`; a card of your own adds it and draws `--kz-pointer-x/y` in its CSS.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"Purely decorative: it renders a hidden, empty element and adds nothing to the accessibility tree.\",\n \"Nothing runs on touch screens or under `prefers-reduced-motion`; nothing loops.\",\n ],\n },\n related: [\"card-grid\", \"glow-card\", \"link-card\"],\n web: {\n imports: [\"PointerLight\", \"usePointerLight\"],\n examples: [{ name: \"list\", title: \"A row of cards\", description: \"Two link cards in a flex row, lit by the pointer.\" }],\n },\n} satisfies ComponentMeta;\n"
100
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Pointer light\",\n summary:\n \"Deprecated (krizaka-ui#393): it drew a light that followed the pointer over cards. It now does nothing — the cards have one quiet hover of their own.\",\n status: \"deprecated\",\n deprecated: {\n since: \"3.31.0\",\n removedIn: \"4.0.0\",\n use: \"card-grid\",\n reason: \"krizaka-ui#393: the light that followed the pointer is retired — every card has one quiet hover (a surface and hairline step, no movement, no light). It renders a hidden span and does nothing.\",\n },\n category: \"blocks\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"It follows a mouse pointer; a phone has none.\" }, desktop: { status: \"n/a\", reason: \"A public site's page, not a window of the Mac app.\" } },\n whenToUse: [\n \"Inside a group of cards that is not a `CardGrid` (a flex row, a list, a carousel).\",\n \"With `usePointerLight` in a component of your own that lays cards out.\",\n ],\n whenNotToUse: [\n { when: \"Inside a CardGrid: it lights its cards already.\", use: \"card-grid\" },\n { when: \"On one card alone, with a ring of the brand's gradient.\", use: \"glow-card\" },\n ],\n bestPractices: [\n \"One light per group: drop it once, as the last child of the group.\",\n \"The cards of the blocks carry `data-kz-light`; a card of your own adds it and draws `--kz-pointer-x/y` in its CSS.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"Purely decorative: it renders a hidden, empty element and adds nothing to the accessibility tree.\",\n \"Nothing runs on touch screens or under `prefers-reduced-motion`; nothing loops.\",\n ],\n },\n related: [\"card-grid\", \"glow-card\", \"link-card\"],\n web: {\n imports: [\"PointerLight\", \"usePointerLight\"],\n examples: [{ name: \"list\", title: \"A row of cards\", description: \"Two link cards in a flex row, with their own quiet hover: the deprecated PointerLight inside adds nothing.\" }],\n },\n} satisfies ComponentMeta;\n"
101
101
  },
102
102
  {
103
103
  "path": "pointer-light/pointer-light.tsx",
104
- "content": "// Client. The light that follows the pointer over the cards of a group (blocks.css `.kz-block-card::after`): one passive\n// pointermove listener on the group writes the pointer's position into the card it is over (`--kz-pointer-x/y`); the\n// card's CSS draws the light. Nothing runs on a touch screen or under reduced motion, nothing loops.\nimport * as React from \"react\";\n\n/** Every card of the page blocks carries it: LinkCard, ArgumentCard. */\nexport const POINTER_LIGHT_TARGET = \"[data-kz-light]\";\n\n/** Lights the cards inside `ref` that match `target`. Returns nothing; the effect cleans itself up. */\nexport function usePointerLight(ref: React.RefObject<HTMLElement | null>, target: string = POINTER_LIGHT_TARGET) {\n React.useEffect(() => {\n const group = ref.current;\n if (!group || typeof window.matchMedia !== \"function\" || !window.matchMedia(\"(hover: hover) and (prefers-reduced-motion: no-preference)\").matches) return;\n const onMove = (e: PointerEvent) => {\n const card = (e.target as Element | null)?.closest<HTMLElement>(target);\n if (!card || !group.contains(card)) return;\n const box = card.getBoundingClientRect();\n card.style.setProperty(\"--kz-pointer-x\", `${Math.round(e.clientX - box.left)}px`);\n card.style.setProperty(\"--kz-pointer-y\", `${Math.round(e.clientY - box.top)}px`);\n };\n group.addEventListener(\"pointermove\", onMove, { passive: true });\n return () => group.removeEventListener(\"pointermove\", onMove);\n }, [ref, target]);\n}\n\nexport type PointerLightProps = {\n /**\n * The cards to light, as a selector inside the parent element.\n * @default \"[data-kz-light]\"\n */\n target?: string;\n};\n\n/**\n * Drop it inside any group of cards (a grid, a list) — it listens on its parent element. Renders an empty, hidden span.\n * CardGrid includes it already.\n */\nexport function PointerLight({ target = POINTER_LIGHT_TARGET }: PointerLightProps) {\n const ref = React.useRef<HTMLSpanElement>(null);\n const parent = React.useRef<HTMLElement | null>(null);\n React.useLayoutEffect(() => {\n parent.current = ref.current?.parentElement ?? null;\n }, []);\n usePointerLight(parent, target);\n return <span ref={ref} hidden />;\n}\n"
104
+ "content": "// Deprecated (krizaka-ui#393): the light that followed the pointer over the cards is retired — the family's hover is\n// quiet (a border and a surface step, no movement, no light). The exports stay so nothing breaks; they do nothing.\nimport type * as React from \"react\";\n\n/** @deprecated Since 3.31: no card carries it any more. */\nexport const POINTER_LIGHT_TARGET = \"[data-kz-light]\";\n\n/** @deprecated Since 3.31 (krizaka-ui#393): does nothing. Removed in 4.0. */\nexport function usePointerLight(_ref: React.RefObject<HTMLElement | null>, _target: string = POINTER_LIGHT_TARGET): void {}\n\nexport type PointerLightProps = {\n /**\n * Ignored.\n * @default \"[data-kz-light]\"\n */\n target?: string;\n};\n\n/** @deprecated Since 3.31 (krizaka-ui#393): renders an empty hidden span and nothing else. Remove it. Removed in 4.0. */\nexport function PointerLight(_props: PointerLightProps) {\n return <span hidden />;\n}\n"
105
105
  }
106
106
  ],
107
107
  "dependencies": [],
@@ -111,20 +111,15 @@
111
111
  "content": "import { LinkCard } from \"@krizaka/ui/link-card\";\nimport { PointerLight } from \"@krizaka/ui/pointer-light\";\n\n// Any group of cards that is not a CardGrid (a flex row, a list): drop `PointerLight` inside it — it listens on its\n// parent and lights the card the pointer is over (`[data-kz-light]`). Nothing on touch screens or under reduced motion.\nexport default function PointerLightList() {\n return (\n <div className=\"flex w-full max-w-3xl flex-wrap gap-3\">\n <LinkCard className=\"flex-1 basis-56\" href=\"#tokens\" title=\"Tokens\" description=\"Every colour, radius and shadow.\" />\n <LinkCard className=\"flex-1 basis-56\" href=\"#preset\" title=\"Tailwind preset\" description=\"The tokens as utilities.\" />\n <PointerLight />\n </div>\n );\n}\n"
112
112
  },
113
113
  "props": [
114
- {
115
- "component": "POINTER_LIGHT_TARGET",
116
- "description": "Every card of the page blocks carries it: LinkCard, ArgumentCard.",
117
- "props": []
118
- },
119
114
  {
120
115
  "component": "PointerLight",
121
- "description": "Drop it inside any group of cards (a grid, a list) — it listens on its parent element. Renders an empty, hidden span.\nCardGrid includes it already.",
116
+ "description": "",
122
117
  "props": [
123
118
  {
124
119
  "name": "target",
125
120
  "type": "string",
126
121
  "default": "[data-kz-light]",
127
- "description": "The cards to light, as a selector inside the parent element.",
122
+ "description": "Ignored.",
128
123
  "required": false
129
124
  }
130
125
  ]
@@ -143,7 +143,7 @@
143
143
  "why": "A buyer compares three plans side by side and answers the last objections before choosing.",
144
144
  "path": "examples/pricing-page/credits.tsx",
145
145
  "module": "@krizaka/ui/registry/examples/pricing-page/credits",
146
- "code": "import { Button } from \"@krizaka/ui/button\";\nimport { Meter } from \"@krizaka/ui/meter\";\nimport { PricingPage } from \"@krizaka/ui/pricing-page\";\n\n// TEMPLATE — plans and credits (Orazaka): the plans side by side, the featured one lifted; the comparison scrolls on a\n// phone; what a credit buys; the questions people ask, folded.\nexport default function PricingPageCredits() {\n return (\n <PricingPage\n brand=\"orazaka\"\n eyebrow=\"Pricing\"\n title=\"Pay once for what was done\"\n lead=\"Credits buy answers, images and minutes of video — never a seat you do not use.\"\n plansLabel=\"Plans\"\n plans={[\n { id: \"community\", name: \"Community\", price: \"Free\", unit: \"self-hosted\", features: [\"Every feature\", \"Your hardware\"], action: <Button variant=\"secondary\" shape=\"cut\" className=\"w-full\">Install</Button> },\n { id: \"workspace\", name: \"Workspace\", price: \"CA$29\", unit: \"per month · 12,000 credits\", features: [\"Packs & Studios\", \"Shared workspaces\"], featured: true, badge: \"Most chosen\", action: <Button variant=\"primary\" shape=\"cut\" shimmer className=\"w-full\">Choose Workspace</Button> },\n { id: \"sovereign\", name: \"Sovereign\", price: \"Custom\", unit: \"on your servers\", features: [\"Installed with you\", \"A named engineer\"], action: <Button variant=\"secondary\" shape=\"cut\" className=\"w-full\">Talk to us</Button> },\n ]}\n compare={{\n caption: \"Compare the plans\",\n columns: [\"Community\", \"Workspace\", \"Sovereign\"],\n rows: [\n { feature: \"Local models\", cells: [\"✓\", \"✓\", \"✓\"] },\n { feature: \"Shared workspaces\", cells: [\"—\", \"✓\", \"✓\"] },\n { feature: \"Support\", cells: [\"Community\", \"Email\", \"Named engineer\"] },\n ],\n }}\n credits={{ title: \"What a credit buys\", children: <div className=\"max-w-md\"><Meter label=\"Credits this month\" value={3800} max={12000} valueText=\"3,800 of 12,000 used\" showLabel /><p className=\"mt-3 text-sm text-fg-secondary\">1 credit ≈ a chat answer · 4 an image · 30 a minute of video.</p></div> }}\n faq={{ title: \"Questions\", items: [{ question: \"What happens when credits run out?\", answer: \"Nothing breaks: requests wait until the next month or a top-up.\" }, { question: \"Do credits expire?\", answer: \"Unused credits roll over for one month.\" }] }}\n />\n );\n}\n"
146
+ "code": "import { Button } from \"@krizaka/ui/button\";\nimport { Meter } from \"@krizaka/ui/meter\";\nimport { PricingPage } from \"@krizaka/ui/pricing-page\";\n\n// TEMPLATE — plans and credits (Orazaka): the plans side by side, the featured one lifted; the comparison scrolls on a\n// phone; what a credit buys; the questions people ask, folded.\nexport default function PricingPageCredits() {\n return (\n <PricingPage\n brand=\"orazaka\"\n eyebrow=\"Pricing\"\n title=\"Pay once for what was done\"\n lead=\"Credits buy answers, images and minutes of video — never a seat you do not use.\"\n plansLabel=\"Plans\"\n plans={[\n { id: \"community\", name: \"Community\", price: \"Free\", unit: \"self-hosted\", features: [\"Every feature\", \"Your hardware\"], action: <Button variant=\"secondary\" className=\"w-full\">Install</Button> },\n { id: \"workspace\", name: \"Workspace\", price: \"CA$29\", unit: \"per month · 12,000 credits\", features: [\"Packs & Studios\", \"Shared workspaces\"], featured: true, badge: \"Most chosen\", action: <Button variant=\"primary\" className=\"w-full\">Choose Workspace</Button> },\n { id: \"sovereign\", name: \"Sovereign\", price: \"Custom\", unit: \"on your servers\", features: [\"Installed with you\", \"A named engineer\"], action: <Button variant=\"secondary\" className=\"w-full\">Talk to us</Button> },\n ]}\n compare={{\n caption: \"Compare the plans\",\n columns: [\"Community\", \"Workspace\", \"Sovereign\"],\n rows: [\n { feature: \"Local models\", cells: [\"✓\", \"✓\", \"✓\"] },\n { feature: \"Shared workspaces\", cells: [\"—\", \"✓\", \"✓\"] },\n { feature: \"Support\", cells: [\"Community\", \"Email\", \"Named engineer\"] },\n ],\n }}\n credits={{ title: \"What a credit buys\", children: <div className=\"max-w-md\"><Meter label=\"Credits this month\" value={3800} max={12000} valueText=\"3,800 of 12,000 used\" showLabel /><p className=\"mt-3 text-sm text-fg-secondary\">1 credit ≈ a chat answer · 4 an image · 30 a minute of video.</p></div> }}\n faq={{ title: \"Questions\", items: [{ question: \"What happens when credits run out?\", answer: \"Nothing breaks: requests wait until the next month or a top-up.\" }, { question: \"Do credits expire?\", answer: \"Unused credits roll over for one month.\" }] }}\n />\n );\n}\n"
147
147
  }
148
148
  ]
149
149
  },
@@ -178,7 +178,7 @@
178
178
  ],
179
179
  "demo": {
180
180
  "path": "examples/pricing-page/credits.tsx",
181
- "content": "import { Button } from \"@krizaka/ui/button\";\nimport { Meter } from \"@krizaka/ui/meter\";\nimport { PricingPage } from \"@krizaka/ui/pricing-page\";\n\n// TEMPLATE — plans and credits (Orazaka): the plans side by side, the featured one lifted; the comparison scrolls on a\n// phone; what a credit buys; the questions people ask, folded.\nexport default function PricingPageCredits() {\n return (\n <PricingPage\n brand=\"orazaka\"\n eyebrow=\"Pricing\"\n title=\"Pay once for what was done\"\n lead=\"Credits buy answers, images and minutes of video — never a seat you do not use.\"\n plansLabel=\"Plans\"\n plans={[\n { id: \"community\", name: \"Community\", price: \"Free\", unit: \"self-hosted\", features: [\"Every feature\", \"Your hardware\"], action: <Button variant=\"secondary\" shape=\"cut\" className=\"w-full\">Install</Button> },\n { id: \"workspace\", name: \"Workspace\", price: \"CA$29\", unit: \"per month · 12,000 credits\", features: [\"Packs & Studios\", \"Shared workspaces\"], featured: true, badge: \"Most chosen\", action: <Button variant=\"primary\" shape=\"cut\" shimmer className=\"w-full\">Choose Workspace</Button> },\n { id: \"sovereign\", name: \"Sovereign\", price: \"Custom\", unit: \"on your servers\", features: [\"Installed with you\", \"A named engineer\"], action: <Button variant=\"secondary\" shape=\"cut\" className=\"w-full\">Talk to us</Button> },\n ]}\n compare={{\n caption: \"Compare the plans\",\n columns: [\"Community\", \"Workspace\", \"Sovereign\"],\n rows: [\n { feature: \"Local models\", cells: [\"✓\", \"✓\", \"✓\"] },\n { feature: \"Shared workspaces\", cells: [\"—\", \"✓\", \"✓\"] },\n { feature: \"Support\", cells: [\"Community\", \"Email\", \"Named engineer\"] },\n ],\n }}\n credits={{ title: \"What a credit buys\", children: <div className=\"max-w-md\"><Meter label=\"Credits this month\" value={3800} max={12000} valueText=\"3,800 of 12,000 used\" showLabel /><p className=\"mt-3 text-sm text-fg-secondary\">1 credit ≈ a chat answer · 4 an image · 30 a minute of video.</p></div> }}\n faq={{ title: \"Questions\", items: [{ question: \"What happens when credits run out?\", answer: \"Nothing breaks: requests wait until the next month or a top-up.\" }, { question: \"Do credits expire?\", answer: \"Unused credits roll over for one month.\" }] }}\n />\n );\n}\n"
181
+ "content": "import { Button } from \"@krizaka/ui/button\";\nimport { Meter } from \"@krizaka/ui/meter\";\nimport { PricingPage } from \"@krizaka/ui/pricing-page\";\n\n// TEMPLATE — plans and credits (Orazaka): the plans side by side, the featured one lifted; the comparison scrolls on a\n// phone; what a credit buys; the questions people ask, folded.\nexport default function PricingPageCredits() {\n return (\n <PricingPage\n brand=\"orazaka\"\n eyebrow=\"Pricing\"\n title=\"Pay once for what was done\"\n lead=\"Credits buy answers, images and minutes of video — never a seat you do not use.\"\n plansLabel=\"Plans\"\n plans={[\n { id: \"community\", name: \"Community\", price: \"Free\", unit: \"self-hosted\", features: [\"Every feature\", \"Your hardware\"], action: <Button variant=\"secondary\" className=\"w-full\">Install</Button> },\n { id: \"workspace\", name: \"Workspace\", price: \"CA$29\", unit: \"per month · 12,000 credits\", features: [\"Packs & Studios\", \"Shared workspaces\"], featured: true, badge: \"Most chosen\", action: <Button variant=\"primary\" className=\"w-full\">Choose Workspace</Button> },\n { id: \"sovereign\", name: \"Sovereign\", price: \"Custom\", unit: \"on your servers\", features: [\"Installed with you\", \"A named engineer\"], action: <Button variant=\"secondary\" className=\"w-full\">Talk to us</Button> },\n ]}\n compare={{\n caption: \"Compare the plans\",\n columns: [\"Community\", \"Workspace\", \"Sovereign\"],\n rows: [\n { feature: \"Local models\", cells: [\"✓\", \"✓\", \"✓\"] },\n { feature: \"Shared workspaces\", cells: [\"—\", \"✓\", \"✓\"] },\n { feature: \"Support\", cells: [\"Community\", \"Email\", \"Named engineer\"] },\n ],\n }}\n credits={{ title: \"What a credit buys\", children: <div className=\"max-w-md\"><Meter label=\"Credits this month\" value={3800} max={12000} valueText=\"3,800 of 12,000 used\" showLabel /><p className=\"mt-3 text-sm text-fg-secondary\">1 credit ≈ a chat answer · 4 an image · 30 a minute of video.</p></div> }}\n faq={{ title: \"Questions\", items: [{ question: \"What happens when credits run out?\", answer: \"Nothing breaks: requests wait until the next month or a top-up.\" }, { question: \"Do credits expire?\", answer: \"Unused credits roll over for one month.\" }] }}\n />\n );\n}\n"
182
182
  },
183
183
  "props": [
184
184
  {
@@ -179,7 +179,7 @@
179
179
  "why": "A visitor understands what a product does, the traps it avoids and the decisions behind it, then acts.",
180
180
  "path": "examples/product-page/orazaka.tsx",
181
181
  "module": "@krizaka/ui/registry/examples/product-page/orazaka",
182
- "code": "import { ProductLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { Mascot } from \"@krizaka/ui/mascots\";\nimport { ProductPage } from \"@krizaka/ui/product-page\";\n\n// TEMPLATE — a product's overview (Orazaka, in its orange): the promise beside the console alive, the traps it removes\n// as compact cards with their bird, its decisions and what they cost, the owl's promise, the closing call.\nexport default function ProductPageOrazaka() {\n return (\n <ProductPage\n brand=\"orazaka\"\n mark={<><ProductLogo id=\"orazaka\" size={18} animated={false} /> Orazaka · sovereign AI</>}\n title=\"The AI that never leaves home\"\n lead=\"Chat, image, voice and video on your own server — one governed pipeline, nothing sent anywhere.\"\n facts={[{ label: \"Apache-2.0\", tone: \"ok\" }, { label: \"Law 25 · GDPR\" }]}\n actions={<Button variant=\"primary\" size=\"lg\" shape=\"cut\" shimmer>Install in three commands</Button>}\n visualLabel=\"orazaka.local — chat\"\n visual={<p className=\"p-6 text-sm text-fg-secondary\">“Summarise the lease in plain French.” — answered locally in 1.8 s.</p>}\n reasonsHeading={{ eyebrow: \"Why\", title: \"Three traps it removes\" }}\n reasonLabels={{ trap: \"The trap\", more: \"Details\" }}\n reasons={[\n { id: \"local\", title: \"Local inference\", trap: \"Elsewhere, every prompt crosses the ocean.\", mark: <Mascot bird=\"falcon\" size={40} animated={false} />, href: \"#local\" },\n { id: \"paid\", title: \"Paid once\", trap: \"Elsewhere, you pay per token, forever.\", mark: <Mascot bird=\"flamingo\" size={40} animated={false} />, href: \"#paid\" },\n { id: \"asked\", title: \"Agents that ask\", trap: \"Elsewhere, an agent acts first.\", mark: <Mascot bird=\"pigeon\" size={40} animated={false} />, href: \"#asked\" },\n ]}\n decisions={{\n eyebrow: \"Decisions\",\n title: \"What we chose, and what it costs you\",\n labels: { chose: \"We chose\", refused: \"We refused\", because: \"Because\", cost: \"What it costs you\" },\n items: [{ id: \"local\", chose: \"Local models by default.\", refused: \"A hosted API by default.\", because: \"A prompt that never leaves cannot leak.\", cost: \"A GPU, or patience.\" }],\n }}\n promise={{ bird: \"owl\", label: \"The owl's promise\", text: \"Every personal record stays in Québec, and you can read who touched it.\" }}\n closing={{ title: \"Ready to run it at home?\", actions: <Button variant=\"primary\" size=\"lg\" shape=\"cut\">Install</Button> }}\n />\n );\n}\n"
182
+ "code": "import { ProductLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { Mascot } from \"@krizaka/ui/mascots\";\nimport { ProductPage } from \"@krizaka/ui/product-page\";\n\n// TEMPLATE — a product's overview (Orazaka, in its orange): the promise beside the console alive, the traps it removes\n// as compact cards with their bird, its decisions and what they cost, the owl's promise, the closing call.\nexport default function ProductPageOrazaka() {\n return (\n <ProductPage\n brand=\"orazaka\"\n mark={<><ProductLogo id=\"orazaka\" size={18} animated={false} /> Orazaka · sovereign AI</>}\n title=\"The AI that never leaves home\"\n lead=\"Chat, image, voice and video on your own server — one governed pipeline, nothing sent anywhere.\"\n facts={[{ label: \"Apache-2.0\", tone: \"ok\" }, { label: \"Law 25 · GDPR\" }]}\n actions={<Button variant=\"primary\" size=\"lg\">Install in three commands</Button>}\n visualLabel=\"orazaka.local — chat\"\n visual={<p className=\"p-6 text-sm text-fg-secondary\">“Summarise the lease in plain French.” — answered locally in 1.8 s.</p>}\n reasonsHeading={{ eyebrow: \"Why\", title: \"Three traps it removes\" }}\n reasonLabels={{ trap: \"The trap\", more: \"Details\" }}\n reasons={[\n { id: \"local\", title: \"Local inference\", trap: \"Elsewhere, every prompt crosses the ocean.\", mark: <Mascot bird=\"falcon\" size={40} animated={false} />, href: \"#local\" },\n { id: \"paid\", title: \"Paid once\", trap: \"Elsewhere, you pay per token, forever.\", mark: <Mascot bird=\"flamingo\" size={40} animated={false} />, href: \"#paid\" },\n { id: \"asked\", title: \"Agents that ask\", trap: \"Elsewhere, an agent acts first.\", mark: <Mascot bird=\"pigeon\" size={40} animated={false} />, href: \"#asked\" },\n ]}\n decisions={{\n eyebrow: \"Decisions\",\n title: \"What we chose, and what it costs you\",\n labels: { chose: \"We chose\", refused: \"We refused\", because: \"Because\", cost: \"What it costs you\" },\n items: [{ id: \"local\", chose: \"Local models by default.\", refused: \"A hosted API by default.\", because: \"A prompt that never leaves cannot leak.\", cost: \"A GPU, or patience.\" }],\n }}\n promise={{ bird: \"owl\", label: \"The owl's promise\", text: \"Every personal record stays in Québec, and you can read who touched it.\" }}\n closing={{ title: \"Ready to run it at home?\", actions: <Button variant=\"primary\" size=\"lg\">Install</Button> }}\n />\n );\n}\n"
183
183
  }
184
184
  ]
185
185
  },
@@ -217,7 +217,7 @@
217
217
  ],
218
218
  "demo": {
219
219
  "path": "examples/product-page/orazaka.tsx",
220
- "content": "import { ProductLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { Mascot } from \"@krizaka/ui/mascots\";\nimport { ProductPage } from \"@krizaka/ui/product-page\";\n\n// TEMPLATE — a product's overview (Orazaka, in its orange): the promise beside the console alive, the traps it removes\n// as compact cards with their bird, its decisions and what they cost, the owl's promise, the closing call.\nexport default function ProductPageOrazaka() {\n return (\n <ProductPage\n brand=\"orazaka\"\n mark={<><ProductLogo id=\"orazaka\" size={18} animated={false} /> Orazaka · sovereign AI</>}\n title=\"The AI that never leaves home\"\n lead=\"Chat, image, voice and video on your own server — one governed pipeline, nothing sent anywhere.\"\n facts={[{ label: \"Apache-2.0\", tone: \"ok\" }, { label: \"Law 25 · GDPR\" }]}\n actions={<Button variant=\"primary\" size=\"lg\" shape=\"cut\" shimmer>Install in three commands</Button>}\n visualLabel=\"orazaka.local — chat\"\n visual={<p className=\"p-6 text-sm text-fg-secondary\">“Summarise the lease in plain French.” — answered locally in 1.8 s.</p>}\n reasonsHeading={{ eyebrow: \"Why\", title: \"Three traps it removes\" }}\n reasonLabels={{ trap: \"The trap\", more: \"Details\" }}\n reasons={[\n { id: \"local\", title: \"Local inference\", trap: \"Elsewhere, every prompt crosses the ocean.\", mark: <Mascot bird=\"falcon\" size={40} animated={false} />, href: \"#local\" },\n { id: \"paid\", title: \"Paid once\", trap: \"Elsewhere, you pay per token, forever.\", mark: <Mascot bird=\"flamingo\" size={40} animated={false} />, href: \"#paid\" },\n { id: \"asked\", title: \"Agents that ask\", trap: \"Elsewhere, an agent acts first.\", mark: <Mascot bird=\"pigeon\" size={40} animated={false} />, href: \"#asked\" },\n ]}\n decisions={{\n eyebrow: \"Decisions\",\n title: \"What we chose, and what it costs you\",\n labels: { chose: \"We chose\", refused: \"We refused\", because: \"Because\", cost: \"What it costs you\" },\n items: [{ id: \"local\", chose: \"Local models by default.\", refused: \"A hosted API by default.\", because: \"A prompt that never leaves cannot leak.\", cost: \"A GPU, or patience.\" }],\n }}\n promise={{ bird: \"owl\", label: \"The owl's promise\", text: \"Every personal record stays in Québec, and you can read who touched it.\" }}\n closing={{ title: \"Ready to run it at home?\", actions: <Button variant=\"primary\" size=\"lg\" shape=\"cut\">Install</Button> }}\n />\n );\n}\n"
220
+ "content": "import { ProductLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { Mascot } from \"@krizaka/ui/mascots\";\nimport { ProductPage } from \"@krizaka/ui/product-page\";\n\n// TEMPLATE — a product's overview (Orazaka, in its orange): the promise beside the console alive, the traps it removes\n// as compact cards with their bird, its decisions and what they cost, the owl's promise, the closing call.\nexport default function ProductPageOrazaka() {\n return (\n <ProductPage\n brand=\"orazaka\"\n mark={<><ProductLogo id=\"orazaka\" size={18} animated={false} /> Orazaka · sovereign AI</>}\n title=\"The AI that never leaves home\"\n lead=\"Chat, image, voice and video on your own server — one governed pipeline, nothing sent anywhere.\"\n facts={[{ label: \"Apache-2.0\", tone: \"ok\" }, { label: \"Law 25 · GDPR\" }]}\n actions={<Button variant=\"primary\" size=\"lg\">Install in three commands</Button>}\n visualLabel=\"orazaka.local — chat\"\n visual={<p className=\"p-6 text-sm text-fg-secondary\">“Summarise the lease in plain French.” — answered locally in 1.8 s.</p>}\n reasonsHeading={{ eyebrow: \"Why\", title: \"Three traps it removes\" }}\n reasonLabels={{ trap: \"The trap\", more: \"Details\" }}\n reasons={[\n { id: \"local\", title: \"Local inference\", trap: \"Elsewhere, every prompt crosses the ocean.\", mark: <Mascot bird=\"falcon\" size={40} animated={false} />, href: \"#local\" },\n { id: \"paid\", title: \"Paid once\", trap: \"Elsewhere, you pay per token, forever.\", mark: <Mascot bird=\"flamingo\" size={40} animated={false} />, href: \"#paid\" },\n { id: \"asked\", title: \"Agents that ask\", trap: \"Elsewhere, an agent acts first.\", mark: <Mascot bird=\"pigeon\" size={40} animated={false} />, href: \"#asked\" },\n ]}\n decisions={{\n eyebrow: \"Decisions\",\n title: \"What we chose, and what it costs you\",\n labels: { chose: \"We chose\", refused: \"We refused\", because: \"Because\", cost: \"What it costs you\" },\n items: [{ id: \"local\", chose: \"Local models by default.\", refused: \"A hosted API by default.\", because: \"A prompt that never leaves cannot leak.\", cost: \"A GPU, or patience.\" }],\n }}\n promise={{ bird: \"owl\", label: \"The owl's promise\", text: \"Every personal record stays in Québec, and you can read who touched it.\" }}\n closing={{ title: \"Ready to run it at home?\", actions: <Button variant=\"primary\" size=\"lg\">Install</Button> }}\n />\n );\n}\n"
221
221
  },
222
222
  "props": [
223
223
  {
@@ -124,7 +124,7 @@
124
124
  },
125
125
  {
126
126
  "name": "status",
127
- "type": "\"error\" | \"paused\" | \"active\"",
127
+ "type": "\"error\" | \"active\" | \"paused\"",
128
128
  "default": "active",
129
129
  "description": "active · paused (the fill rests, striped: waiting for someone) · error (the fill turns to the danger colour where it stopped).",
130
130
  "required": false
@@ -427,7 +427,7 @@
427
427
  },
428
428
  {
429
429
  "name": "status",
430
- "type": "\"error\" | \"paused\" | \"active\"",
430
+ "type": "\"error\" | \"active\" | \"paused\"",
431
431
  "default": "active",
432
432
  "description": "active · paused (the fill rests, striped: waiting for someone) · error (the fill turns to the danger colour where it stopped).",
433
433
  "required": false
@@ -331,7 +331,7 @@
331
331
  },
332
332
  {
333
333
  "path": "radio-group/radio-group.tsx",
334
- "content": "// Client (a context). Radix RadioGroup: role=\"radiogroup\" / \"radio\", one tab stop, the arrows move and select, a\n// hidden input inside a form. Four looks, one behaviour:\n// - `RadioGroup.Item`: a dot and its words (a settings list);\n// - `RadioGroup.Card`: a whole card is the radio — an amount, a pack, a plan — its check lit when chosen;\n// - `RadioGroup.Button`: a tactile key that presses in — raised on a ledge, sunk when chosen (a size, a tip, a level);\n// - `RadioGroup.Segment` in `<RadioGroup.Root variant=\"segmented\">`: a segmented control, one light gliding to the choice.\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { CheckGlyph } from \"../glyphs\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\nimport { tv } from \"../tv\";\n\nexport const radioGroup = tv({\n slots: {\n root: \"grid gap-2 data-[orientation=horizontal]:flex data-[orientation=horizontal]:flex-wrap\",\n item:\n \"peer inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-border-strong bg-surface-1 inset-shadow-well transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:focus-ring hover:border-accent disabled:cursor-not-allowed disabled:opacity-40 \" +\n \"data-[state=checked]:border-accent data-[state=checked]:bg-accent data-[state=checked]:shadow-glow aria-invalid:border-danger\",\n dot: \"h-2 w-2 rounded-full bg-on-accent\",\n label: \"flex cursor-pointer items-start gap-3 text-sm text-fg has-disabled:cursor-not-allowed has-disabled:opacity-60\",\n card:\n \"group relative flex min-w-0 flex-col items-start gap-1 rounded-2xl border border-border-default bg-surface-1 bg-sheen p-4 pr-10 text-left text-sm text-fg inset-shadow-edge \" +\n \"transition-[border-color,background-color,box-shadow,transform] duration-200 hover:border-border-strong active:scale-[0.99] motion-reduce:transition-none motion-reduce:active:scale-100 \" +\n \"focus-visible:outline-hidden focus-visible:focus-ring disabled:cursor-not-allowed disabled:opacity-40 aria-invalid:border-danger \" +\n \"data-[state=checked]:border-accent data-[state=checked]:bg-accent-soft data-[state=checked]:shadow-glow\",\n // The card's check: an empty ring at rest, the accent filled with its check once chosen.\n cardCheck:\n \"absolute right-3.5 top-3.5 inline-flex h-5 w-5 items-center justify-center rounded-full border border-border-strong bg-surface-1 text-on-accent transition-colors \" +\n \"group-data-[state=checked]:border-accent group-data-[state=checked]:bg-accent\",\n // The tactile key: on a ledge (its lower edge), lifted on hover, pressed in while held and once chosen.\n button:\n \"relative inline-flex min-h-12 min-w-12 select-none flex-col items-center justify-center gap-0.5 rounded-xl border border-border-default bg-surface-2 bg-sheen px-4 py-2 \" +\n \"text-sm font-semibold text-fg inset-shadow-edge shadow-[0_4px_0_0_var(--kz-border-strong)] \" +\n \"transition-[transform,box-shadow,background-color,border-color,color] duration-100 ease-out motion-reduce:transition-none \" +\n \"hover:-translate-y-px hover:shadow-[0_5px_0_0_var(--kz-border-strong)] \" +\n \"active:translate-y-[3px] active:shadow-[0_1px_0_0_var(--kz-border-strong)] \" +\n \"focus-visible:outline-hidden focus-visible:focus-ring disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:translate-y-0 aria-invalid:border-danger \" +\n \"data-[state=checked]:translate-y-[3px] data-[state=checked]:border-accent data-[state=checked]:bg-accent data-[state=checked]:bg-fill-shade \" +\n \"data-[state=checked]:text-on-accent data-[state=checked]:inset-shadow-edge-fill data-[state=checked]:shadow-[0_1px_0_0_var(--kz-accent-hover)] \" +\n \"data-[state=checked]:hover:translate-y-[3px]\",\n buttonHint: \"text-[11px] font-medium text-fg-secondary group-data-[state=checked]:text-on-accent\",\n segmented:\n \"relative isolate inline-grid auto-cols-fr grid-flow-col gap-1 rounded-xl border border-border-default bg-surface-2 p-1 inset-shadow-well \" +\n \"before:pointer-events-none before:absolute before:left-0 before:top-0 before:-z-10 before:h-(--kz-ind-h) before:w-(--kz-ind-w) \" +\n \"before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) before:rounded-lg before:border before:border-border-default before:bg-surface-0 before:bg-sheen \" +\n \"before:opacity-0 before:shadow-raised before:transition-[translate,width,height,opacity] before:duration-300 motion-reduce:before:transition-none data-[indicator=on]:before:opacity-100\",\n segment:\n \"inline-flex min-h-9 select-none items-center justify-center gap-1.5 whitespace-nowrap rounded-lg px-3.5 text-sm font-semibold text-fg-secondary transition-colors \" +\n \"hover:text-fg focus-visible:outline-hidden focus-visible:focus-ring disabled:cursor-not-allowed disabled:opacity-40 \" +\n \"data-[state=checked]:border data-[state=checked]:border-border-default data-[state=checked]:bg-surface-0 data-[state=checked]:text-fg data-[state=checked]:shadow-raised \" +\n \"group-data-[indicator=on]/seg:data-[state=checked]:border-transparent group-data-[indicator=on]/seg:data-[state=checked]:bg-transparent group-data-[indicator=on]/seg:data-[state=checked]:shadow-none\",\n },\n variants: {\n variant: {\n list: {},\n cards: { root: \"grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-3\" },\n buttons: { root: \"flex flex-wrap gap-2.5 pb-1\" },\n segmented: {},\n },\n },\n defaultVariants: { variant: \"list\" },\n});\nconst s = radioGroup();\n\nexport type RadioGroupRootProps = React.ComponentProps<typeof RadioGroupPrimitive.Root> & {\n /** The group's accessible name — passed translated (or `aria-labelledby` a visible heading). */\n label?: string;\n /** Marks every radio invalid: aria-invalid (pair it with a `Field.Error`). */\n invalid?: boolean;\n /**\n * The layout of the radios: `list` (dots), `cards` (a grid of `Card`s), `buttons` (a row of tactile `Button`s), or\n * `segmented` (a track of `Segment`s, one light gliding to the choice).\n * @default \"list\"\n */\n variant?: \"list\" | \"cards\" | \"buttons\" | \"segmented\";\n};\n\n/** `value`/`defaultValue` + `onValueChange`, `orientation`, `required`, `name` for a form, `variant` for the layout. */\nexport function RadioGroupRoot({ label, invalid, variant = \"list\", className, ...props }: RadioGroupRootProps) {\n const { ref } = useSlidingIndicator<HTMLDivElement>({ item: \"[role=radio]\", active: \"[role=radio][data-state=checked]\" });\n const segmented = variant === \"segmented\";\n return (\n <RadioGroupPrimitive.Root\n ref={segmented ? ref : undefined}\n aria-label={label}\n aria-invalid={invalid || undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-variant={variant}\n orientation={segmented ? \"horizontal\" : props.orientation}\n className={segmented ? s.segmented({ className: [\"group/seg\", className].filter(Boolean).join(\" \") }) : radioGroup({ variant }).root({ className })}\n {...props}\n />\n );\n}\n\nexport type RadioGroupItemProps = Omit<React.ComponentProps<typeof RadioGroupPrimitive.Item>, \"children\"> & {\n /** The words next to the dot, clickable. Otherwise name it with `Field.Label htmlFor` or `aria-label`. */\n children?: React.ReactNode;\n};\n\n/** A radio: a dot, and its words when given. */\nexport function RadioGroupItem({ className, children, ...props }: RadioGroupItemProps) {\n const item = (\n <RadioGroupPrimitive.Item className={s.item({ className: children ? \"mt-0.5\" : className })} {...props}>\n <RadioGroupPrimitive.Indicator className={s.dot()} />\n </RadioGroupPrimitive.Item>\n );\n if (!children) return item;\n return (\n <label className={s.label({ className })}>\n {item}\n <span className=\"min-w-0\">{children}</span>\n </label>\n );\n}\n\n/** A whole card as the radio: its content is the radio's name; its check lights up when chosen. */\nexport function RadioGroupCard({ className, children, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n return (\n <RadioGroupPrimitive.Item className={s.card({ className })} {...props}>\n {children}\n <span aria-hidden className={s.cardCheck()}>\n <RadioGroupPrimitive.Indicator forceMount className=\"inline-flex data-[state=unchecked]:hidden\">\n <CheckGlyph className=\"h-3 w-3\" />\n </RadioGroupPrimitive.Indicator>\n </span>\n </RadioGroupPrimitive.Item>\n );\n}\n\nexport type RadioGroupButtonProps = React.ComponentProps<typeof RadioGroupPrimitive.Item> & {\n /** A second line under the words (\"+15 %\"), part of the radio's name. */\n hint?: React.ReactNode;\n};\n\n/** A tactile key: raised on its ledge, pressed in while held and once chosen — sizes, a tip, a level. */\nexport function RadioGroupButton({ className, children, hint, ...props }: RadioGroupButtonProps) {\n return (\n <RadioGroupPrimitive.Item className={s.button({ className: [\"group\", className].filter(Boolean).join(\" \") })} {...props}>\n <span>{children}</span>\n {hint ? <span className={s.buttonHint()}>{hint}</span> : null}\n </RadioGroupPrimitive.Item>\n );\n}\n\n/** A segment of a `variant=\"segmented\"` group: equal widths, one light gliding to the choice. */\nexport function RadioGroupSegment({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n return <RadioGroupPrimitive.Item className={s.segment({ className })} {...props} />;\n}\n\n/** `RadioGroup.Root/Item/Card/Button/Segment`. */\nexport const RadioGroup = { Root: RadioGroupRoot, Item: RadioGroupItem, Card: RadioGroupCard, Button: RadioGroupButton, Segment: RadioGroupSegment };\n"
334
+ "content": "// Client (a context). Radix RadioGroup: role=\"radiogroup\" / \"radio\", one tab stop, the arrows move and select, a\n// hidden input inside a form. Four looks, one behaviour:\n// - `RadioGroup.Item`: a dot and its words (a settings list);\n// - `RadioGroup.Card`: a whole card is the radio — an amount, a pack, a plan — its check lit when chosen;\n// - `RadioGroup.Button`: a tactile key that presses in — raised on a ledge, sunk when chosen (a size, a tip, a level);\n// - `RadioGroup.Segment` in `<RadioGroup.Root variant=\"segmented\">`: a segmented control, one light gliding to the choice.\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { CheckGlyph } from \"../glyphs\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\nimport { tv } from \"../tv\";\n\nexport const radioGroup = tv({\n slots: {\n root: \"grid gap-2 data-[orientation=horizontal]:flex data-[orientation=horizontal]:flex-wrap\",\n item:\n \"peer inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-border-strong bg-surface-1 inset-shadow-well transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:focus-ring hover:border-accent disabled:cursor-not-allowed disabled:opacity-40 \" +\n \"data-[state=checked]:border-accent data-[state=checked]:bg-accent data-[state=checked]:shadow-glow aria-invalid:border-danger\",\n dot: \"h-2 w-2 rounded-full bg-on-accent\",\n label: \"flex cursor-pointer items-start gap-3 text-sm text-fg has-disabled:cursor-not-allowed has-disabled:opacity-60\",\n card:\n \"group relative flex min-w-0 flex-col items-start gap-1 rounded-2xl border border-border-default bg-surface-1 bg-sheen p-4 pr-10 text-left text-sm text-fg inset-shadow-edge \" +\n \"transition-[border-color,background-color,box-shadow,transform] duration-200 hover:border-border-strong active:scale-[0.99] motion-reduce:transition-none motion-reduce:active:scale-100 \" +\n \"focus-visible:outline-hidden focus-visible:focus-ring disabled:cursor-not-allowed disabled:opacity-40 aria-invalid:border-danger \" +\n \"data-[state=checked]:border-accent data-[state=checked]:bg-accent-soft data-[state=checked]:shadow-glow\",\n // The card's check: an empty ring at rest, the accent filled with its check once chosen.\n cardCheck:\n \"absolute right-3.5 top-3.5 inline-flex h-5 w-5 items-center justify-center rounded-full border border-border-strong bg-surface-1 text-on-accent transition-colors \" +\n \"group-data-[state=checked]:border-accent group-data-[state=checked]:bg-accent\",\n // The tactile key: on a ledge (its lower edge), its hairline stronger on hover, pressed in while held and once chosen.\n button:\n \"relative inline-flex min-h-12 min-w-12 select-none flex-col items-center justify-center gap-0.5 rounded-xl border border-border-default bg-surface-2 bg-sheen px-4 py-2 \" +\n \"text-sm font-semibold text-fg inset-shadow-edge shadow-[0_4px_0_0_var(--kz-border-strong)] \" +\n \"transition-[transform,box-shadow,background-color,border-color,color] duration-100 ease-out motion-reduce:transition-none \" +\n \"hover:border-border-strong \" +\n \"active:translate-y-[3px] active:shadow-[0_1px_0_0_var(--kz-border-strong)] \" +\n \"focus-visible:outline-hidden focus-visible:focus-ring disabled:cursor-not-allowed disabled:opacity-40 aria-invalid:border-danger \" +\n \"data-[state=checked]:translate-y-[3px] data-[state=checked]:border-accent data-[state=checked]:bg-accent data-[state=checked]:bg-fill-shade \" +\n \"data-[state=checked]:text-on-accent data-[state=checked]:inset-shadow-edge-fill data-[state=checked]:shadow-[0_1px_0_0_var(--kz-accent-hover)]\",\n buttonHint: \"text-[11px] font-medium text-fg-secondary group-data-[state=checked]:text-on-accent\",\n segmented:\n \"relative isolate inline-grid auto-cols-fr grid-flow-col gap-1 rounded-xl border border-border-default bg-surface-2 p-1 inset-shadow-well \" +\n \"before:pointer-events-none before:absolute before:left-0 before:top-0 before:-z-10 before:h-(--kz-ind-h) before:w-(--kz-ind-w) \" +\n \"before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) before:rounded-lg before:border before:border-border-default before:bg-surface-0 before:bg-sheen \" +\n \"before:opacity-0 before:shadow-raised before:transition-[translate,width,height,opacity] before:duration-300 motion-reduce:before:transition-none data-[indicator=on]:before:opacity-100\",\n segment:\n \"inline-flex min-h-9 select-none items-center justify-center gap-1.5 whitespace-nowrap rounded-lg px-3.5 text-sm font-semibold text-fg-secondary transition-colors \" +\n \"hover:text-fg focus-visible:outline-hidden focus-visible:focus-ring disabled:cursor-not-allowed disabled:opacity-40 \" +\n \"data-[state=checked]:border data-[state=checked]:border-border-default data-[state=checked]:bg-surface-0 data-[state=checked]:text-fg data-[state=checked]:shadow-raised \" +\n \"group-data-[indicator=on]/seg:data-[state=checked]:border-transparent group-data-[indicator=on]/seg:data-[state=checked]:bg-transparent group-data-[indicator=on]/seg:data-[state=checked]:shadow-none\",\n },\n variants: {\n variant: {\n list: {},\n cards: { root: \"grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-3\" },\n buttons: { root: \"flex flex-wrap gap-2.5 pb-1\" },\n segmented: {},\n },\n },\n defaultVariants: { variant: \"list\" },\n});\nconst s = radioGroup();\n\nexport type RadioGroupRootProps = React.ComponentProps<typeof RadioGroupPrimitive.Root> & {\n /** The group's accessible name — passed translated (or `aria-labelledby` a visible heading). */\n label?: string;\n /** Marks every radio invalid: aria-invalid (pair it with a `Field.Error`). */\n invalid?: boolean;\n /**\n * The layout of the radios: `list` (dots), `cards` (a grid of `Card`s), `buttons` (a row of tactile `Button`s), or\n * `segmented` (a track of `Segment`s, one light gliding to the choice).\n * @default \"list\"\n */\n variant?: \"list\" | \"cards\" | \"buttons\" | \"segmented\";\n};\n\n/** `value`/`defaultValue` + `onValueChange`, `orientation`, `required`, `name` for a form, `variant` for the layout. */\nexport function RadioGroupRoot({ label, invalid, variant = \"list\", className, ...props }: RadioGroupRootProps) {\n const { ref } = useSlidingIndicator<HTMLDivElement>({ item: \"[role=radio]\", active: \"[role=radio][data-state=checked]\" });\n const segmented = variant === \"segmented\";\n return (\n <RadioGroupPrimitive.Root\n ref={segmented ? ref : undefined}\n aria-label={label}\n aria-invalid={invalid || undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-variant={variant}\n orientation={segmented ? \"horizontal\" : props.orientation}\n className={segmented ? s.segmented({ className: [\"group/seg\", className].filter(Boolean).join(\" \") }) : radioGroup({ variant }).root({ className })}\n {...props}\n />\n );\n}\n\nexport type RadioGroupItemProps = Omit<React.ComponentProps<typeof RadioGroupPrimitive.Item>, \"children\"> & {\n /** The words next to the dot, clickable. Otherwise name it with `Field.Label htmlFor` or `aria-label`. */\n children?: React.ReactNode;\n};\n\n/** A radio: a dot, and its words when given. */\nexport function RadioGroupItem({ className, children, ...props }: RadioGroupItemProps) {\n const item = (\n <RadioGroupPrimitive.Item className={s.item({ className: children ? \"mt-0.5\" : className })} {...props}>\n <RadioGroupPrimitive.Indicator className={s.dot()} />\n </RadioGroupPrimitive.Item>\n );\n if (!children) return item;\n return (\n <label className={s.label({ className })}>\n {item}\n <span className=\"min-w-0\">{children}</span>\n </label>\n );\n}\n\n/** A whole card as the radio: its content is the radio's name; its check lights up when chosen. */\nexport function RadioGroupCard({ className, children, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n return (\n <RadioGroupPrimitive.Item className={s.card({ className })} {...props}>\n {children}\n <span aria-hidden className={s.cardCheck()}>\n <RadioGroupPrimitive.Indicator forceMount className=\"inline-flex data-[state=unchecked]:hidden\">\n <CheckGlyph className=\"h-3 w-3\" />\n </RadioGroupPrimitive.Indicator>\n </span>\n </RadioGroupPrimitive.Item>\n );\n}\n\nexport type RadioGroupButtonProps = React.ComponentProps<typeof RadioGroupPrimitive.Item> & {\n /** A second line under the words (\"+15 %\"), part of the radio's name. */\n hint?: React.ReactNode;\n};\n\n/** A tactile key: raised on its ledge, pressed in while held and once chosen — sizes, a tip, a level. */\nexport function RadioGroupButton({ className, children, hint, ...props }: RadioGroupButtonProps) {\n return (\n <RadioGroupPrimitive.Item className={s.button({ className: [\"group\", className].filter(Boolean).join(\" \") })} {...props}>\n <span>{children}</span>\n {hint ? <span className={s.buttonHint()}>{hint}</span> : null}\n </RadioGroupPrimitive.Item>\n );\n}\n\n/** A segment of a `variant=\"segmented\"` group: equal widths, one light gliding to the choice. */\nexport function RadioGroupSegment({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n return <RadioGroupPrimitive.Item className={s.segment({ className })} {...props} />;\n}\n\n/** `RadioGroup.Root/Item/Card/Button/Segment`. */\nexport const RadioGroup = { Root: RadioGroupRoot, Item: RadioGroupItem, Card: RadioGroupCard, Button: RadioGroupButton, Segment: RadioGroupSegment };\n"
335
335
  },
336
336
  {
337
337
  "path": "sliding-indicator.ts",
@@ -298,7 +298,7 @@
298
298
  },
299
299
  {
300
300
  "path": "marks/krizaka-geometry.ts",
301
- "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Krizaka mark's geometry — the one description drawn by the web mark (KrizakaLogo) and the native one\n * (KrizakaMark). Built on the Orochia mark's grammar (docs: packages/tokens/BRAND.md, \"Marks\"): a 360×360 artwork at\n * (20, 20) in a 400×400 view box, one bold stroke (22) drawn as an open figure, an end that breaks out as a node, a\n * core, a dashed orbit and a ring; below 48 px the view box crops to the emblem.\n *\n * Krizaka is the parent: the hexagonal shield is **ink** (the host's text roles), the node and the core carry the\n * Krizaka blue (`marks.krizaka` of @krizaka/tokens: the brand's accent hsl(217 92% 53%) → its text tint hsl(217 92% 68%)).\n */\nexport const KRIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"86 82 228 228\",\n offset: 20,\n /** The shield: a pointy-top hexagon (r 92 around 180,180), open at its upper-right edge. */\n shield: \"M259.7,134 L259.7,226 L180,272 L100.3,226 L100.3,134 L180,88 L212,106.5\",\n stroke: 22,\n ink: { x1: 70, y1: 290, x2: 290, y2: 70 },\n blue: { x1: 180, y1: 180, x2: 300, y2: 60, stops: marks.krizaka },\n /** The vertex where the shield opens: a node (the serpent's head in Orochia's grammar). */\n node: { cx: 259.7, cy: 134, r: 19, inner: 6 },\n core: { cx: 180, cy: 180, r: 24, inner: 8 },\n highlight: \"#dbeafe\",\n glow: { color: \"#196df5\", opacity: 0.16 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
301
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Krizaka mark's geometry — the one description drawn by the web mark (KrizakaLogo) and the native one\n * (KrizakaMark). Built on the Orochia mark's grammar (docs: packages/tokens/BRAND.md, \"Marks\"): a 360×360 artwork at\n * (20, 20) in a 400×400 view box, one bold stroke (22) drawn as an open figure, an end that breaks out as a node, a\n * core, a dashed orbit and a ring; below 48 px the view box crops to the emblem.\n *\n * Krizaka is the parent: the hexagonal shield is **ink** (the host's text roles), the node and the core carry the\n * Krizaka blue (`marks.krizaka` of @krizaka/tokens: the brand's accent hsl(208 46% 45%) → its text tint hsl(206 58% 72%)).\n */\nexport const KRIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"86 82 228 228\",\n offset: 20,\n /** The shield: a pointy-top hexagon (r 92 around 180,180), open at its upper-right edge. */\n shield: \"M259.7,134 L259.7,226 L180,272 L100.3,226 L100.3,134 L180,88 L212,106.5\",\n stroke: 22,\n ink: { x1: 70, y1: 290, x2: 290, y2: 70 },\n blue: { x1: 180, y1: 180, x2: 300, y2: 60, stops: marks.krizaka },\n /** The vertex where the shield opens: a node (the serpent's head in Orochia's grammar). */\n node: { cx: 259.7, cy: 134, r: 19, inner: 6 },\n core: { cx: 180, cy: 180, r: 24, inner: 8 },\n highlight: \"#ddeaf4\",\n glow: { color: marks.krizaka[0]![1], opacity: 0.16 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
302
302
  },
303
303
  {
304
304
  "path": "marks/orazaka-geometry.ts",
@@ -342,7 +342,7 @@
342
342
  }
343
343
  ],
344
344
  "dependencies": [
345
- "@krizaka/tokens@^3.30.0",
345
+ "@krizaka/tokens@^3.31.0",
346
346
  "radix-ui@^1.7.0",
347
347
  "tailwind-variants@^3.3.1"
348
348
  ],
@@ -224,7 +224,7 @@
224
224
  },
225
225
  {
226
226
  "path": "service-card/service-card.tsx",
227
- "content": "// Server-safe: no hook, no context. A service a shop sells, as a card that argues: icon or photo, name, duration, price\n// (« from » when it varies), what is included, the capacity, a « Popular » tag — and one way in: a link (the booking page\n// with this service chosen) or a button (`onSelect`, from a client parent). The whole card takes the press.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { CheckGlyph } from \"../glyphs\";\n\nexport const serviceCard = tv({\n slots: {\n root:\n \"group kz-spotlight kz-lift relative flex h-full min-w-0 flex-col overflow-hidden rounded-xl border border-border-default bg-surface-1 text-fg transition-colors \" +\n \"hover:border-accent/50 focus-within:ring-2 focus-within:ring-ring data-[selected]:border-accent data-[selected]:bg-accent-soft\",\n media: \"relative aspect-[16/9] w-full overflow-hidden bg-media [&>img]:h-full [&>img]:w-full [&>img]:object-cover\",\n body: \"flex flex-1 flex-col gap-3 p-4 sm:p-5\",\n head: \"flex items-start gap-3\",\n icon: \"inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-accent-soft text-fg-accent [&>svg]:h-5 [&>svg]:w-5\",\n titles: \"flex min-w-0 flex-1 flex-col gap-1\",\n name: \"m-0 font-display text-lg font-bold leading-tight tracking-tight text-fg\",\n facts: \"m-0 flex list-none flex-wrap gap-x-2 gap-y-0.5 p-0 text-xs text-fg-secondary\",\n tag: \"shrink-0 rounded-full bg-accent px-2 py-0.5 text-[10px] font-extrabold uppercase tracking-wide text-on-accent\",\n description: \"m-0 text-sm leading-relaxed text-fg-secondary\",\n included: \"m-0 flex list-none flex-col gap-1.5 p-0 text-sm text-fg-secondary\",\n includedItem: \"flex items-start gap-2 [&>svg]:mt-0.5 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 [&>svg]:text-fg-accent\",\n foot: \"mt-auto flex items-end justify-between gap-3 border-t border-border-subtle pt-3\",\n priceBlock: \"flex flex-col leading-none\",\n from: \"mb-1 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n price: \"font-display text-2xl font-black tabular-nums text-fg\",\n action:\n \"inline-flex min-h-9 items-center gap-1 rounded-full text-sm font-bold text-fg-accent no-underline \" +\n \"after:absolute after:inset-0 after:content-[''] focus-visible:outline-hidden group-data-[selected]:text-fg\",\n },\n});\nconst s = serviceCard();\n\nexport type ServiceCardProps = Omit<React.ComponentProps<\"article\">, \"title\" | \"children\"> & {\n /** The service's name (the heading). */\n name: string;\n /** Its price, formatted by the app (`22,00 $`): the card never formats money. */\n price: string;\n /** The words before the price when it varies (`From`, `À partir de`), passed translated. */\n priceFrom?: string;\n /** How long it takes (`30 min`) — the first fact. */\n duration?: string;\n /** How many it takes (`Up to 5 dogs`, `1 person`) — the second fact. */\n capacity?: string;\n /** One or two sentences: what it is. */\n description?: string;\n /** What is included, one short line each (a check precedes it). */\n included?: readonly string[];\n /** A tag on the top right (`Popular`): only from real data, never decoration. */\n tag?: string;\n /** An icon tile before the name (a signature icon): decorative. */\n icon?: React.ReactNode;\n /** A photo or a loop above the card (an `img`, a `video`): fills a 16:9 band. */\n media?: React.ReactNode;\n /** This service is the chosen one (the accent, `data-selected`). */\n selected?: boolean;\n /** The words of the way in (`Book`, `Choose`): the accessible name of the press. */\n actionLabel: string;\n /** The way in, as an address (the booking page with this service preselected). */\n href?: string;\n /** The app's link component for internal addresses (Next's `Link`). */\n linkAs?: LinkComponent;\n /** The way in, as a press (a client parent). Ignored when `href` is set. */\n onSelect?: () => void;\n /**\n * The heading level of the name.\n * @default \"h3\"\n */\n titleAs?: \"h2\" | \"h3\" | \"h4\";\n /** Enters on scroll (MotionObserver): the index in the grid becomes the delay. */\n reveal?: number;\n};\n\n/** A service as a card: icon or photo, duration, price, what is included, the capacity, a tag — one way in. */\nexport function ServiceCard({\n name, price, priceFrom, duration, capacity, description, included, tag, icon, media, selected, actionLabel, href, linkAs, onSelect, titleAs: Title = \"h3\", reveal, className, style, ...props\n}: ServiceCardProps) {\n const id = React.useId();\n const facts = [duration, capacity].filter(Boolean);\n const arrow = <span aria-hidden>→</span>;\n const revealStyle = reveal === undefined ? style : ({ ...style, \"--kz-delay\": `${Math.min(Math.max(reveal, 0), 8) * 50}ms` } as React.CSSProperties);\n return (\n <article\n aria-labelledby={id}\n data-selected={selected ? \"\" : undefined}\n data-reveal={reveal === undefined ? undefined : \"\"}\n style={revealStyle}\n className={s.root({ className })}\n {...props}\n >\n {media && <div className={s.media()}>{media}</div>}\n <div className={s.body()}>\n <div className={s.head()}>\n {icon && <span aria-hidden className={s.icon()}>{icon}</span>}\n <div className={s.titles()}>\n <Title id={id} className={s.name()}>{name}</Title>\n {facts.length > 0 && (\n <ul className={s.facts()}>\n {facts.map((fact, i) => (\n <li key={i}>{i > 0 && <span aria-hidden>· </span>}{fact}</li>\n ))}\n </ul>\n )}\n </div>\n {tag && <span className={s.tag()}>{tag}</span>}\n </div>\n {description && <p className={s.description()}>{description}</p>}\n {included && included.length > 0 && (\n <ul className={s.included()}>\n {included.map((line) => (\n <li key={line} className={s.includedItem()}>\n <CheckGlyph />\n <span>{line}</span>\n </li>\n ))}\n </ul>\n )}\n <div className={s.foot()}>\n <p className={s.priceBlock()}>\n {priceFrom && <span className={s.from()}>{priceFrom}</span>}\n <span className={s.price()}>{price}</span>\n </p>\n {href ? (\n <BlockLink href={href} linkAs={linkAs} className={s.action()} aria-describedby={id}>\n {actionLabel} {arrow}\n </BlockLink>\n ) : onSelect ? (\n <button type=\"button\" onClick={onSelect} aria-pressed={selected ?? false} aria-describedby={id} className={s.action()}>\n {actionLabel} {arrow}\n </button>\n ) : null}\n </div>\n </div>\n </article>\n );\n}\n"
227
+ "content": "// Server-safe: no hook, no context. A service a shop sells, as a card that argues: icon or photo, name, duration, price\n// (« from » when it varies), what is included, the capacity, a « Popular » tag — and one way in: a link (the booking page\n// with this service chosen) or a button (`onSelect`, from a client parent). The whole card takes the press.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { CheckGlyph } from \"../glyphs\";\n\nexport const serviceCard = tv({\n slots: {\n root:\n \"group relative flex h-full min-w-0 flex-col overflow-hidden rounded-xl border border-border-default bg-surface-1 text-fg transition-colors duration-150 hover:border-border-strong hover:bg-surface-2 \" +\n \"hover:border-accent/50 focus-within:ring-2 focus-within:ring-ring data-[selected]:border-accent data-[selected]:bg-accent-soft\",\n media: \"relative aspect-[16/9] w-full overflow-hidden bg-media [&>img]:h-full [&>img]:w-full [&>img]:object-cover\",\n body: \"flex flex-1 flex-col gap-3 p-4 sm:p-5\",\n head: \"flex items-start gap-3\",\n icon: \"inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-accent-soft text-fg-accent [&>svg]:h-5 [&>svg]:w-5\",\n titles: \"flex min-w-0 flex-1 flex-col gap-1\",\n name: \"m-0 font-display text-lg font-bold leading-tight tracking-tight text-fg\",\n facts: \"m-0 flex list-none flex-wrap gap-x-2 gap-y-0.5 p-0 text-xs text-fg-secondary\",\n tag: \"shrink-0 rounded-full bg-accent px-2 py-0.5 text-[10px] font-extrabold uppercase tracking-wide text-on-accent\",\n description: \"m-0 text-sm leading-relaxed text-fg-secondary\",\n included: \"m-0 flex list-none flex-col gap-1.5 p-0 text-sm text-fg-secondary\",\n includedItem: \"flex items-start gap-2 [&>svg]:mt-0.5 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 [&>svg]:text-fg-accent\",\n foot: \"mt-auto flex items-end justify-between gap-3 border-t border-border-subtle pt-3\",\n priceBlock: \"flex flex-col leading-none\",\n from: \"mb-1 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n price: \"font-display text-2xl font-black tabular-nums text-fg\",\n action:\n \"inline-flex min-h-9 items-center gap-1 rounded-full text-sm font-bold text-fg-accent no-underline \" +\n \"after:absolute after:inset-0 after:content-[''] focus-visible:outline-hidden group-data-[selected]:text-fg\",\n },\n});\nconst s = serviceCard();\n\nexport type ServiceCardProps = Omit<React.ComponentProps<\"article\">, \"title\" | \"children\"> & {\n /** The service's name (the heading). */\n name: string;\n /** Its price, formatted by the app (`22,00 $`): the card never formats money. */\n price: string;\n /** The words before the price when it varies (`From`, `À partir de`), passed translated. */\n priceFrom?: string;\n /** How long it takes (`30 min`) — the first fact. */\n duration?: string;\n /** How many it takes (`Up to 5 dogs`, `1 person`) — the second fact. */\n capacity?: string;\n /** One or two sentences: what it is. */\n description?: string;\n /** What is included, one short line each (a check precedes it). */\n included?: readonly string[];\n /** A tag on the top right (`Popular`): only from real data, never decoration. */\n tag?: string;\n /** An icon tile before the name (a signature icon): decorative. */\n icon?: React.ReactNode;\n /** A photo or a loop above the card (an `img`, a `video`): fills a 16:9 band. */\n media?: React.ReactNode;\n /** This service is the chosen one (the accent, `data-selected`). */\n selected?: boolean;\n /** The words of the way in (`Book`, `Choose`): the accessible name of the press. */\n actionLabel: string;\n /** The way in, as an address (the booking page with this service preselected). */\n href?: string;\n /** The app's link component for internal addresses (Next's `Link`). */\n linkAs?: LinkComponent;\n /** The way in, as a press (a client parent). Ignored when `href` is set. */\n onSelect?: () => void;\n /**\n * The heading level of the name.\n * @default \"h3\"\n */\n titleAs?: \"h2\" | \"h3\" | \"h4\";\n /** Enters on scroll (MotionObserver): the index in the grid becomes the delay. */\n reveal?: number;\n};\n\n/** A service as a card: icon or photo, duration, price, what is included, the capacity, a tag — one way in. */\nexport function ServiceCard({\n name, price, priceFrom, duration, capacity, description, included, tag, icon, media, selected, actionLabel, href, linkAs, onSelect, titleAs: Title = \"h3\", reveal, className, style, ...props\n}: ServiceCardProps) {\n const id = React.useId();\n const facts = [duration, capacity].filter(Boolean);\n const arrow = <span aria-hidden>→</span>;\n const revealStyle = reveal === undefined ? style : ({ ...style, \"--kz-delay\": `${Math.min(Math.max(reveal, 0), 8) * 50}ms` } as React.CSSProperties);\n return (\n <article\n aria-labelledby={id}\n data-selected={selected ? \"\" : undefined}\n data-reveal={reveal === undefined ? undefined : \"\"}\n style={revealStyle}\n className={s.root({ className })}\n {...props}\n >\n {media && <div className={s.media()}>{media}</div>}\n <div className={s.body()}>\n <div className={s.head()}>\n {icon && <span aria-hidden className={s.icon()}>{icon}</span>}\n <div className={s.titles()}>\n <Title id={id} className={s.name()}>{name}</Title>\n {facts.length > 0 && (\n <ul className={s.facts()}>\n {facts.map((fact, i) => (\n <li key={i}>{i > 0 && <span aria-hidden>· </span>}{fact}</li>\n ))}\n </ul>\n )}\n </div>\n {tag && <span className={s.tag()}>{tag}</span>}\n </div>\n {description && <p className={s.description()}>{description}</p>}\n {included && included.length > 0 && (\n <ul className={s.included()}>\n {included.map((line) => (\n <li key={line} className={s.includedItem()}>\n <CheckGlyph />\n <span>{line}</span>\n </li>\n ))}\n </ul>\n )}\n <div className={s.foot()}>\n <p className={s.priceBlock()}>\n {priceFrom && <span className={s.from()}>{priceFrom}</span>}\n <span className={s.price()}>{price}</span>\n </p>\n {href ? (\n <BlockLink href={href} linkAs={linkAs} className={s.action()} aria-describedby={id}>\n {actionLabel} {arrow}\n </BlockLink>\n ) : onSelect ? (\n <button type=\"button\" onClick={onSelect} aria-pressed={selected ?? false} aria-describedby={id} className={s.action()}>\n {actionLabel} {arrow}\n </button>\n ) : null}\n </div>\n </div>\n </article>\n );\n}\n"
228
228
  }
229
229
  ],
230
230
  "dependencies": [
@@ -176,7 +176,7 @@
176
176
  "why": "A person changes one setting at a time, saves it at once and knows where each lives.",
177
177
  "path": "examples/settings-layout/account.tsx",
178
178
  "module": "@krizaka/ui/registry/examples/settings-layout/account",
179
- "code": "import { Button } from \"@krizaka/ui/button\";\nimport { Field, Input, Select } from \"@krizaka/ui/field\";\nimport { SettingsLayout, SettingsSection } from \"@krizaka/ui/settings-layout\";\nimport { Switch } from \"@krizaka/ui/switch\";\n\nconst NAV = [\n { id: \"profile\", label: \"Profile\", href: \"#profile\", current: true },\n { id: \"notifications\", label: \"Notifications\", href: \"#notifications\" },\n { id: \"payouts\", label: \"Payouts\", href: \"#payouts\" },\n { id: \"security\", label: \"Security\", href: \"#security\" },\n];\n\n// TEMPLATE — account settings: the sections on the left (a row of chips that scrolls on a phone), each form a titled\n// card with its own save, so a change in one never waits for another.\nexport default function SettingsLayoutAccount() {\n return (\n <SettingsLayout className=\"w-full\" title=\"Settings\" description=\"Your profile, what we tell you and how you are paid.\" nav={NAV} navLabel=\"Settings sections\">\n <SettingsSection\n id=\"profile\"\n title=\"Profile\"\n description=\"What your fans see.\"\n footer={<Button size=\"sm\" variant=\"primary\" shape=\"cut\">Save the profile</Button>}\n >\n <Field.Root>\n <Field.Label htmlFor=\"settings-name\">Display name</Field.Label>\n <Input id=\"settings-name\" defaultValue=\"Elena Varga\" />\n </Field.Root>\n <Field.Root>\n <Field.Label htmlFor=\"settings-language\">Language</Field.Label>\n <Select id=\"settings-language\" defaultValue=\"fr\">\n <option value=\"en\">English</option>\n <option value=\"fr\">Français</option>\n </Select>\n </Field.Root>\n </SettingsSection>\n <SettingsSection id=\"notifications\" title=\"Notifications\" description=\"Only what you ask for.\">\n {[\n { id: \"unlock\", label: \"A fan unlocks a video\", on: true },\n { id: \"auction\", label: \"An auction ends\", on: true },\n { id: \"weekly\", label: \"Weekly summary by email\", on: false },\n ].map((n) => (\n <div key={n.id} className=\"flex items-center justify-between gap-4 text-sm text-fg\">\n <span id={`notify-${n.id}`}>{n.label}</span>\n <Switch aria-labelledby={`notify-${n.id}`} defaultChecked={n.on} />\n </div>\n ))}\n </SettingsSection>\n </SettingsLayout>\n );\n}\n"
179
+ "code": "import { Button } from \"@krizaka/ui/button\";\nimport { Field, Input, Select } from \"@krizaka/ui/field\";\nimport { SettingsLayout, SettingsSection } from \"@krizaka/ui/settings-layout\";\nimport { Switch } from \"@krizaka/ui/switch\";\n\nconst NAV = [\n { id: \"profile\", label: \"Profile\", href: \"#profile\", current: true },\n { id: \"notifications\", label: \"Notifications\", href: \"#notifications\" },\n { id: \"payouts\", label: \"Payouts\", href: \"#payouts\" },\n { id: \"security\", label: \"Security\", href: \"#security\" },\n];\n\n// TEMPLATE — account settings: the sections on the left (a row of chips that scrolls on a phone), each form a titled\n// card with its own save, so a change in one never waits for another.\nexport default function SettingsLayoutAccount() {\n return (\n <SettingsLayout className=\"w-full\" title=\"Settings\" description=\"Your profile, what we tell you and how you are paid.\" nav={NAV} navLabel=\"Settings sections\">\n <SettingsSection\n id=\"profile\"\n title=\"Profile\"\n description=\"What your fans see.\"\n footer={<Button size=\"sm\" variant=\"primary\">Save the profile</Button>}\n >\n <Field.Root>\n <Field.Label htmlFor=\"settings-name\">Display name</Field.Label>\n <Input id=\"settings-name\" defaultValue=\"Elena Varga\" />\n </Field.Root>\n <Field.Root>\n <Field.Label htmlFor=\"settings-language\">Language</Field.Label>\n <Select id=\"settings-language\" defaultValue=\"fr\">\n <option value=\"en\">English</option>\n <option value=\"fr\">Français</option>\n </Select>\n </Field.Root>\n </SettingsSection>\n <SettingsSection id=\"notifications\" title=\"Notifications\" description=\"Only what you ask for.\">\n {[\n { id: \"unlock\", label: \"A fan unlocks a video\", on: true },\n { id: \"auction\", label: \"An auction ends\", on: true },\n { id: \"weekly\", label: \"Weekly summary by email\", on: false },\n ].map((n) => (\n <div key={n.id} className=\"flex items-center justify-between gap-4 text-sm text-fg\">\n <span id={`notify-${n.id}`}>{n.label}</span>\n <Switch aria-labelledby={`notify-${n.id}`} defaultChecked={n.on} />\n </div>\n ))}\n </SettingsSection>\n </SettingsLayout>\n );\n}\n"
180
180
  },
181
181
  {
182
182
  "name": "flat",
@@ -195,7 +195,7 @@
195
195
  "description": "A workspace's settings, the current section marked.",
196
196
  "path": "examples/settings-layout/sections.tsx",
197
197
  "module": "@krizaka/ui/registry/examples/settings-layout/sections",
198
- "code": "import { Button } from \"@krizaka/ui/button\";\nimport { Field, Input } from \"@krizaka/ui/field\";\nimport { SettingsLayout, SettingsSection } from \"@krizaka/ui/settings-layout\";\n\n// One section per page: the navigation's links are pages, the current one is marked.\nexport default function SettingsLayoutSections() {\n return (\n <SettingsLayout\n className=\"w-full\"\n nav={[\n { id: \"workspace\", label: \"Workspace\", href: \"#workspace\", current: true },\n { id: \"models\", label: \"Models\", href: \"#models\" },\n ]}\n navLabel=\"Settings sections\"\n >\n <SettingsSection title=\"Workspace\" footer={<Button size=\"sm\" variant=\"primary\" shape=\"cut\">Save</Button>}>\n <Field.Root>\n <Field.Label htmlFor=\"sections-name\">Name</Field.Label>\n <Input id=\"sections-name\" defaultValue=\"Tremblay & associés\" />\n </Field.Root>\n </SettingsSection>\n </SettingsLayout>\n );\n}\n"
198
+ "code": "import { Button } from \"@krizaka/ui/button\";\nimport { Field, Input } from \"@krizaka/ui/field\";\nimport { SettingsLayout, SettingsSection } from \"@krizaka/ui/settings-layout\";\n\n// One section per page: the navigation's links are pages, the current one is marked.\nexport default function SettingsLayoutSections() {\n return (\n <SettingsLayout\n className=\"w-full\"\n nav={[\n { id: \"workspace\", label: \"Workspace\", href: \"#workspace\", current: true },\n { id: \"models\", label: \"Models\", href: \"#models\" },\n ]}\n navLabel=\"Settings sections\"\n >\n <SettingsSection title=\"Workspace\" footer={<Button size=\"sm\" variant=\"primary\">Save</Button>}>\n <Field.Root>\n <Field.Label htmlFor=\"sections-name\">Name</Field.Label>\n <Input id=\"sections-name\" defaultValue=\"Tremblay & associés\" />\n </Field.Root>\n </SettingsSection>\n </SettingsLayout>\n );\n}\n"
199
199
  }
200
200
  ]
201
201
  },
@@ -329,7 +329,7 @@
329
329
  "registryDependencies": [],
330
330
  "demo": {
331
331
  "path": "examples/settings-layout/account.tsx",
332
- "content": "import { Button } from \"@krizaka/ui/button\";\nimport { Field, Input, Select } from \"@krizaka/ui/field\";\nimport { SettingsLayout, SettingsSection } from \"@krizaka/ui/settings-layout\";\nimport { Switch } from \"@krizaka/ui/switch\";\n\nconst NAV = [\n { id: \"profile\", label: \"Profile\", href: \"#profile\", current: true },\n { id: \"notifications\", label: \"Notifications\", href: \"#notifications\" },\n { id: \"payouts\", label: \"Payouts\", href: \"#payouts\" },\n { id: \"security\", label: \"Security\", href: \"#security\" },\n];\n\n// TEMPLATE — account settings: the sections on the left (a row of chips that scrolls on a phone), each form a titled\n// card with its own save, so a change in one never waits for another.\nexport default function SettingsLayoutAccount() {\n return (\n <SettingsLayout className=\"w-full\" title=\"Settings\" description=\"Your profile, what we tell you and how you are paid.\" nav={NAV} navLabel=\"Settings sections\">\n <SettingsSection\n id=\"profile\"\n title=\"Profile\"\n description=\"What your fans see.\"\n footer={<Button size=\"sm\" variant=\"primary\" shape=\"cut\">Save the profile</Button>}\n >\n <Field.Root>\n <Field.Label htmlFor=\"settings-name\">Display name</Field.Label>\n <Input id=\"settings-name\" defaultValue=\"Elena Varga\" />\n </Field.Root>\n <Field.Root>\n <Field.Label htmlFor=\"settings-language\">Language</Field.Label>\n <Select id=\"settings-language\" defaultValue=\"fr\">\n <option value=\"en\">English</option>\n <option value=\"fr\">Français</option>\n </Select>\n </Field.Root>\n </SettingsSection>\n <SettingsSection id=\"notifications\" title=\"Notifications\" description=\"Only what you ask for.\">\n {[\n { id: \"unlock\", label: \"A fan unlocks a video\", on: true },\n { id: \"auction\", label: \"An auction ends\", on: true },\n { id: \"weekly\", label: \"Weekly summary by email\", on: false },\n ].map((n) => (\n <div key={n.id} className=\"flex items-center justify-between gap-4 text-sm text-fg\">\n <span id={`notify-${n.id}`}>{n.label}</span>\n <Switch aria-labelledby={`notify-${n.id}`} defaultChecked={n.on} />\n </div>\n ))}\n </SettingsSection>\n </SettingsLayout>\n );\n}\n"
332
+ "content": "import { Button } from \"@krizaka/ui/button\";\nimport { Field, Input, Select } from \"@krizaka/ui/field\";\nimport { SettingsLayout, SettingsSection } from \"@krizaka/ui/settings-layout\";\nimport { Switch } from \"@krizaka/ui/switch\";\n\nconst NAV = [\n { id: \"profile\", label: \"Profile\", href: \"#profile\", current: true },\n { id: \"notifications\", label: \"Notifications\", href: \"#notifications\" },\n { id: \"payouts\", label: \"Payouts\", href: \"#payouts\" },\n { id: \"security\", label: \"Security\", href: \"#security\" },\n];\n\n// TEMPLATE — account settings: the sections on the left (a row of chips that scrolls on a phone), each form a titled\n// card with its own save, so a change in one never waits for another.\nexport default function SettingsLayoutAccount() {\n return (\n <SettingsLayout className=\"w-full\" title=\"Settings\" description=\"Your profile, what we tell you and how you are paid.\" nav={NAV} navLabel=\"Settings sections\">\n <SettingsSection\n id=\"profile\"\n title=\"Profile\"\n description=\"What your fans see.\"\n footer={<Button size=\"sm\" variant=\"primary\">Save the profile</Button>}\n >\n <Field.Root>\n <Field.Label htmlFor=\"settings-name\">Display name</Field.Label>\n <Input id=\"settings-name\" defaultValue=\"Elena Varga\" />\n </Field.Root>\n <Field.Root>\n <Field.Label htmlFor=\"settings-language\">Language</Field.Label>\n <Select id=\"settings-language\" defaultValue=\"fr\">\n <option value=\"en\">English</option>\n <option value=\"fr\">Français</option>\n </Select>\n </Field.Root>\n </SettingsSection>\n <SettingsSection id=\"notifications\" title=\"Notifications\" description=\"Only what you ask for.\">\n {[\n { id: \"unlock\", label: \"A fan unlocks a video\", on: true },\n { id: \"auction\", label: \"An auction ends\", on: true },\n { id: \"weekly\", label: \"Weekly summary by email\", on: false },\n ].map((n) => (\n <div key={n.id} className=\"flex items-center justify-between gap-4 text-sm text-fg\">\n <span id={`notify-${n.id}`}>{n.label}</span>\n <Switch aria-labelledby={`notify-${n.id}`} defaultChecked={n.on} />\n </div>\n ))}\n </SettingsSection>\n </SettingsLayout>\n );\n}\n"
333
333
  },
334
334
  "props": [
335
335
  {
@@ -285,7 +285,7 @@
285
285
  },
286
286
  {
287
287
  "path": "marks/krizaka-geometry.ts",
288
- "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Krizaka mark's geometry — the one description drawn by the web mark (KrizakaLogo) and the native one\n * (KrizakaMark). Built on the Orochia mark's grammar (docs: packages/tokens/BRAND.md, \"Marks\"): a 360×360 artwork at\n * (20, 20) in a 400×400 view box, one bold stroke (22) drawn as an open figure, an end that breaks out as a node, a\n * core, a dashed orbit and a ring; below 48 px the view box crops to the emblem.\n *\n * Krizaka is the parent: the hexagonal shield is **ink** (the host's text roles), the node and the core carry the\n * Krizaka blue (`marks.krizaka` of @krizaka/tokens: the brand's accent hsl(217 92% 53%) → its text tint hsl(217 92% 68%)).\n */\nexport const KRIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"86 82 228 228\",\n offset: 20,\n /** The shield: a pointy-top hexagon (r 92 around 180,180), open at its upper-right edge. */\n shield: \"M259.7,134 L259.7,226 L180,272 L100.3,226 L100.3,134 L180,88 L212,106.5\",\n stroke: 22,\n ink: { x1: 70, y1: 290, x2: 290, y2: 70 },\n blue: { x1: 180, y1: 180, x2: 300, y2: 60, stops: marks.krizaka },\n /** The vertex where the shield opens: a node (the serpent's head in Orochia's grammar). */\n node: { cx: 259.7, cy: 134, r: 19, inner: 6 },\n core: { cx: 180, cy: 180, r: 24, inner: 8 },\n highlight: \"#dbeafe\",\n glow: { color: \"#196df5\", opacity: 0.16 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
288
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Krizaka mark's geometry — the one description drawn by the web mark (KrizakaLogo) and the native one\n * (KrizakaMark). Built on the Orochia mark's grammar (docs: packages/tokens/BRAND.md, \"Marks\"): a 360×360 artwork at\n * (20, 20) in a 400×400 view box, one bold stroke (22) drawn as an open figure, an end that breaks out as a node, a\n * core, a dashed orbit and a ring; below 48 px the view box crops to the emblem.\n *\n * Krizaka is the parent: the hexagonal shield is **ink** (the host's text roles), the node and the core carry the\n * Krizaka blue (`marks.krizaka` of @krizaka/tokens: the brand's accent hsl(208 46% 45%) → its text tint hsl(206 58% 72%)).\n */\nexport const KRIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"86 82 228 228\",\n offset: 20,\n /** The shield: a pointy-top hexagon (r 92 around 180,180), open at its upper-right edge. */\n shield: \"M259.7,134 L259.7,226 L180,272 L100.3,226 L100.3,134 L180,88 L212,106.5\",\n stroke: 22,\n ink: { x1: 70, y1: 290, x2: 290, y2: 70 },\n blue: { x1: 180, y1: 180, x2: 300, y2: 60, stops: marks.krizaka },\n /** The vertex where the shield opens: a node (the serpent's head in Orochia's grammar). */\n node: { cx: 259.7, cy: 134, r: 19, inner: 6 },\n core: { cx: 180, cy: 180, r: 24, inner: 8 },\n highlight: \"#ddeaf4\",\n glow: { color: marks.krizaka[0]![1], opacity: 0.16 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
289
289
  },
290
290
  {
291
291
  "path": "marks/orazaka-geometry.ts",
@@ -329,7 +329,7 @@
329
329
  }
330
330
  ],
331
331
  "dependencies": [
332
- "@krizaka/tokens@^3.30.0",
332
+ "@krizaka/tokens@^3.31.0",
333
333
  "radix-ui@^1.7.0",
334
334
  "tailwind-variants@^3.3.1"
335
335
  ],
@@ -455,7 +455,7 @@
455
455
  },
456
456
  {
457
457
  "path": "slider/slider.tsx",
458
- "content": "// Client. Radix Slider: role=\"slider\" on each thumb, arrows / Page Up / Page Down / Home / End, a range with two\n// thumbs, horizontal or vertical. The words arrive as props: `label` names the thumb(s), `formatValue` gives the\n// aria-valuetext and the value shown (above the track, or floating over the thumb while it is touched). `ticks` draws the\n// graduations of the steps (and `marks` their words under the track); `tone` fills with the brand's gradient or its\n// accent alone.\nimport { Slider as SliderPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { tv } from \"../tv\";\n\nexport const slider = tv({\n slots: {\n root: \"flex flex-col gap-2 data-[orientation=vertical]:h-full data-[orientation=vertical]:items-center\",\n header: \"flex items-baseline justify-between gap-3 text-xs font-semibold text-fg-secondary\",\n value: \"font-mono text-[11px] tabular-nums text-fg-secondary\",\n control:\n \"relative flex touch-none select-none items-center data-disabled:cursor-not-allowed data-disabled:opacity-40 \" +\n \"data-[orientation=horizontal]:h-6 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-6 data-[orientation=vertical]:flex-col\",\n track:\n \"relative grow overflow-hidden rounded-full bg-surface-3 inset-shadow-well \" +\n \"data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\",\n range: \"absolute rounded-full data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\",\n thumb:\n \"group/thumb relative block h-5 w-5 cursor-grab rounded-full border-2 border-accent bg-fg-on-media shadow-raised transition-[box-shadow,scale] duration-150 \" +\n \"hover:scale-110 focus-visible:outline-hidden focus-visible:focus-ring active:scale-110 active:cursor-grabbing motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:active:scale-100\",\n // The value over the thumb: shown while the thumb is hovered, focused or held.\n bubble:\n \"pointer-events-none absolute bottom-full left-1/2 mb-2.5 -translate-x-1/2 whitespace-nowrap rounded-lg bg-fg px-2 py-1 font-mono text-[11px] font-semibold tabular-nums text-surface-0 opacity-0 shadow-floating \" +\n \"transition-opacity duration-150 after:absolute after:left-1/2 after:top-full after:-translate-x-1/2 after:border-4 after:border-transparent after:border-t-fg \" +\n \"group-hover/thumb:opacity-100 group-focus-visible/thumb:opacity-100 group-active/thumb:opacity-100 data-[always]:opacity-100 motion-reduce:transition-none\",\n bubbleVertical: \"bottom-auto left-full top-1/2 mb-0 ml-3 -translate-x-0 -translate-y-1/2 after:hidden\",\n ticks: \"pointer-events-none absolute inset-0\",\n tick: \"absolute rounded-full bg-border-strong data-[on]:bg-on-accent/70 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:h-1 data-[orientation=horizontal]:w-px data-[orientation=horizontal]:-translate-x-1/2 data-[orientation=horizontal]:-translate-y-1/2 data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:h-px data-[orientation=vertical]:w-1 data-[orientation=vertical]:-translate-x-1/2 data-[orientation=vertical]:translate-y-1/2\",\n marks: \"relative h-4 w-full text-[11px] font-medium tabular-nums text-fg-secondary\",\n mark: \"absolute top-0 -translate-x-1/2 whitespace-nowrap first:translate-x-0 last:-translate-x-full\",\n },\n variants: {\n tone: {\n /** The brand's signature: accent → accent-2, a glow under it. */\n gradient: { range: \"bg-linear-to-r from-accent to-accent-2 shadow-glow data-[orientation=vertical]:bg-linear-to-t\" },\n /** The accent alone. */\n solid: { range: \"bg-accent\" },\n },\n size: {\n md: {},\n lg: {\n track: \"data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5\",\n thumb: \"h-6 w-6\",\n control: \"data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8\",\n },\n },\n },\n defaultVariants: { tone: \"gradient\", size: \"md\" },\n});\n\nexport type SliderValue = number | [number, number];\n\nexport type SliderMark = {\n /** Where the mark stands. */\n value: number;\n /** Its words under the track (\"0\", \"50 %\", \"Max\"), passed translated. */\n label: string;\n};\n\nexport type SliderProps<V extends SliderValue = number> = Omit<\n React.ComponentProps<typeof SliderPrimitive.Root>,\n \"value\" | \"defaultValue\" | \"onValueChange\" | \"onValueCommit\" | \"children\"\n> & {\n /** One number (a thumb) or two (a range). Controlled with `onValueChange`. */\n value?: V;\n /** The value at first, uncontrolled: one number, or two for a range. */\n defaultValue?: V;\n /** Called on every move, with one number or two. */\n onValueChange?: (value: V) => void;\n /** Called once the drag or the key press ends. */\n onValueCommit?: (value: V) => void;\n /** The accessible name — of the thumb, or of both thumbs of a range — passed translated. */\n label: string;\n /** For a range: each thumb's own name ([\"Minimum\", \"Maximum\"]), passed translated. */\n thumbLabels?: [string, string];\n /** The value as words (aria-valuetext) and as shown: \"1.5×\", \"−12 %\", \"3 min\". */\n formatValue?: (value: number) => string;\n /** Shows the label and the value above the track. Without it, `label` only names the thumb. */\n showLabel?: boolean;\n /** Shows the value floating over the thumb while it is hovered, focused or held (`\"always\"`: at all times). */\n floatingValue?: boolean | \"always\";\n /** Fills from this value instead of the minimum (a ±range from 0: brightness, balance). One horizontal thumb only. */\n origin?: number;\n /** Draws a graduation at every step (`true`, up to 40 of them) or at the values given. */\n ticks?: boolean | readonly number[];\n /** Words under the track at some values (`[{ value: 0, label: \"0\" }, { value: 100, label: \"100\" }]`). Horizontal only. */\n marks?: readonly SliderMark[];\n /**\n * `gradient` (the brand's accent → accent-2, a glow) or `solid` (the accent alone).\n * @default \"gradient\"\n */\n tone?: \"gradient\" | \"solid\";\n /**\n * md · lg (a thicker track and thumb: a hero control, a touch screen).\n * @default \"md\"\n */\n size?: \"md\" | \"lg\";\n};\n\nconst toArray = (v: SliderValue | undefined) => (v === undefined ? undefined : typeof v === \"number\" ? [v] : v);\n\n/** A slider on one value or a range: `value`/`defaultValue` + `onValueChange`, `min`, `max`, `step`, `orientation`. */\nexport function Slider<V extends SliderValue = number>({\n value,\n defaultValue,\n onValueChange,\n onValueCommit,\n label,\n thumbLabels,\n formatValue = String,\n showLabel,\n floatingValue,\n origin,\n ticks,\n marks,\n tone,\n size,\n min = 0,\n max = 100,\n step = 1,\n orientation = \"horizontal\",\n className,\n ...props\n}: SliderProps<V>) {\n const s = slider({ tone, size });\n const range = Array.isArray(value ?? defaultValue);\n const vertical = orientation === \"vertical\";\n const [inner, setInner] = React.useState(() => toArray(defaultValue) ?? [min]);\n const current = toArray(value) ?? inner;\n const out = (next: number[]) => (range ? next : next[0]) as V;\n const pct = (v: number) => ((v - min) / (max - min || 1)) * 100;\n const fill =\n origin !== undefined && !range && !vertical\n ? { left: `${Math.min(pct(origin), pct(current[0]!))}%`, right: `${100 - Math.max(pct(origin), pct(current[0]!))}%` }\n : undefined;\n const lo = range ? Math.min(...current) : (origin ?? min);\n const hi = range ? Math.max(...current) : current[0]!;\n const tickValues = ticks === true ? (max - min) / step <= 40 ? Array.from({ length: Math.round((max - min) / step) + 1 }, (_, i) => min + i * step) : [] : ticks ? [...ticks] : [];\n const id = React.useId();\n return (\n <div className={s.root({ className })} data-range={range ? \"\" : undefined} data-orientation={orientation}>\n {showLabel && (\n <div className={s.header()}>\n <span id={`${id}-label`}>{label}</span>\n <span className={s.value()} aria-hidden>\n {current.map(formatValue).join(\" – \")}\n </span>\n </div>\n )}\n <SliderPrimitive.Root\n min={min}\n max={max}\n step={step}\n orientation={orientation}\n value={current}\n onValueChange={(next) => {\n if (value === undefined) setInner(next);\n onValueChange?.(out(next));\n }}\n onValueCommit={onValueCommit && ((next) => onValueCommit(out(next)))}\n className={s.control()}\n {...props}\n >\n <SliderPrimitive.Track className={s.track()}>\n {fill ? <span data-origin=\"\" data-orientation={orientation} className={s.range()} style={fill} /> : <SliderPrimitive.Range className={s.range()} />}\n {tickValues.length ? (\n <span aria-hidden className={s.ticks()}>\n {tickValues.map((t) => (\n <span\n key={t}\n data-orientation={orientation}\n data-on={t > lo && t < hi ? \"\" : undefined}\n className={s.tick()}\n style={vertical ? { bottom: `${pct(t)}%` } : { left: `${pct(t)}%` }}\n />\n ))}\n </span>\n ) : null}\n </SliderPrimitive.Track>\n {current.map((v, i) => (\n <SliderPrimitive.Thumb\n key={i}\n aria-label={range ? (thumbLabels?.[i] ?? label) : showLabel ? undefined : label}\n aria-labelledby={!range && showLabel ? `${id}-label` : undefined}\n aria-valuetext={formatValue(v)}\n className={s.thumb()}\n >\n {floatingValue ? (\n <span aria-hidden data-always={floatingValue === \"always\" ? \"\" : undefined} className={s.bubble({ className: vertical ? s.bubbleVertical() : undefined })}>\n {formatValue(v)}\n </span>\n ) : null}\n </SliderPrimitive.Thumb>\n ))}\n </SliderPrimitive.Root>\n {marks?.length && !vertical ? (\n <div aria-hidden className={s.marks()}>\n {marks.map((m) => (\n <span key={m.value} className={s.mark()} style={{ left: `${pct(m.value)}%` }}>\n {m.label}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n"
458
+ "content": "// Client. Radix Slider: role=\"slider\" on each thumb, arrows / Page Up / Page Down / Home / End, a range with two\n// thumbs, horizontal or vertical. The words arrive as props: `label` names the thumb(s), `formatValue` gives the\n// aria-valuetext and the value shown (above the track, or floating over the thumb while it is touched). `ticks` draws the\n// graduations of the steps (and `marks` their words under the track); `tone` fills with the brand's gradient or its\n// accent alone.\nimport { Slider as SliderPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { tv } from \"../tv\";\n\nexport const slider = tv({\n slots: {\n root: \"flex flex-col gap-2 data-[orientation=vertical]:h-full data-[orientation=vertical]:items-center\",\n header: \"flex items-baseline justify-between gap-3 text-xs font-semibold text-fg-secondary\",\n value: \"font-mono text-[11px] tabular-nums text-fg-secondary\",\n control:\n \"relative flex touch-none select-none items-center data-disabled:cursor-not-allowed data-disabled:opacity-40 \" +\n \"data-[orientation=horizontal]:h-6 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-6 data-[orientation=vertical]:flex-col\",\n track:\n \"relative grow overflow-hidden rounded-full bg-surface-3 inset-shadow-well \" +\n \"data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\",\n range: \"absolute rounded-full data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\",\n thumb:\n \"group/thumb relative block h-5 w-5 cursor-grab rounded-full border-2 border-accent bg-fg-on-media shadow-raised transition-[box-shadow,scale] duration-150 \" +\n \"hover:ring-4 hover:ring-accent/15 focus-visible:outline-hidden focus-visible:focus-ring active:scale-110 active:cursor-grabbing motion-reduce:transition-none motion-reduce:active:scale-100\",\n // The value over the thumb: shown while the thumb is hovered, focused or held.\n bubble:\n \"pointer-events-none absolute bottom-full left-1/2 mb-2.5 -translate-x-1/2 whitespace-nowrap rounded-lg bg-fg px-2 py-1 font-mono text-[11px] font-semibold tabular-nums text-surface-0 opacity-0 shadow-floating \" +\n \"transition-opacity duration-150 after:absolute after:left-1/2 after:top-full after:-translate-x-1/2 after:border-4 after:border-transparent after:border-t-fg \" +\n \"group-hover/thumb:opacity-100 group-focus-visible/thumb:opacity-100 group-active/thumb:opacity-100 data-[always]:opacity-100 motion-reduce:transition-none\",\n bubbleVertical: \"bottom-auto left-full top-1/2 mb-0 ml-3 -translate-x-0 -translate-y-1/2 after:hidden\",\n ticks: \"pointer-events-none absolute inset-0\",\n tick: \"absolute rounded-full bg-border-strong data-[on]:bg-on-accent/70 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:h-1 data-[orientation=horizontal]:w-px data-[orientation=horizontal]:-translate-x-1/2 data-[orientation=horizontal]:-translate-y-1/2 data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:h-px data-[orientation=vertical]:w-1 data-[orientation=vertical]:-translate-x-1/2 data-[orientation=vertical]:translate-y-1/2\",\n marks: \"relative h-4 w-full text-[11px] font-medium tabular-nums text-fg-secondary\",\n mark: \"absolute top-0 -translate-x-1/2 whitespace-nowrap first:translate-x-0 last:-translate-x-full\",\n },\n variants: {\n tone: {\n /** The brand's signature: accent → accent-2, a glow under it. */\n gradient: { range: \"bg-linear-to-r from-accent to-accent-2 shadow-glow data-[orientation=vertical]:bg-linear-to-t\" },\n /** The accent alone. */\n solid: { range: \"bg-accent\" },\n },\n size: {\n md: {},\n lg: {\n track: \"data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5\",\n thumb: \"h-6 w-6\",\n control: \"data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8\",\n },\n },\n },\n defaultVariants: { tone: \"gradient\", size: \"md\" },\n});\n\nexport type SliderValue = number | [number, number];\n\nexport type SliderMark = {\n /** Where the mark stands. */\n value: number;\n /** Its words under the track (\"0\", \"50 %\", \"Max\"), passed translated. */\n label: string;\n};\n\nexport type SliderProps<V extends SliderValue = number> = Omit<\n React.ComponentProps<typeof SliderPrimitive.Root>,\n \"value\" | \"defaultValue\" | \"onValueChange\" | \"onValueCommit\" | \"children\"\n> & {\n /** One number (a thumb) or two (a range). Controlled with `onValueChange`. */\n value?: V;\n /** The value at first, uncontrolled: one number, or two for a range. */\n defaultValue?: V;\n /** Called on every move, with one number or two. */\n onValueChange?: (value: V) => void;\n /** Called once the drag or the key press ends. */\n onValueCommit?: (value: V) => void;\n /** The accessible name — of the thumb, or of both thumbs of a range — passed translated. */\n label: string;\n /** For a range: each thumb's own name ([\"Minimum\", \"Maximum\"]), passed translated. */\n thumbLabels?: [string, string];\n /** The value as words (aria-valuetext) and as shown: \"1.5×\", \"−12 %\", \"3 min\". */\n formatValue?: (value: number) => string;\n /** Shows the label and the value above the track. Without it, `label` only names the thumb. */\n showLabel?: boolean;\n /** Shows the value floating over the thumb while it is hovered, focused or held (`\"always\"`: at all times). */\n floatingValue?: boolean | \"always\";\n /** Fills from this value instead of the minimum (a ±range from 0: brightness, balance). One horizontal thumb only. */\n origin?: number;\n /** Draws a graduation at every step (`true`, up to 40 of them) or at the values given. */\n ticks?: boolean | readonly number[];\n /** Words under the track at some values (`[{ value: 0, label: \"0\" }, { value: 100, label: \"100\" }]`). Horizontal only. */\n marks?: readonly SliderMark[];\n /**\n * `gradient` (the brand's accent → accent-2, a glow) or `solid` (the accent alone).\n * @default \"gradient\"\n */\n tone?: \"gradient\" | \"solid\";\n /**\n * md · lg (a thicker track and thumb: a hero control, a touch screen).\n * @default \"md\"\n */\n size?: \"md\" | \"lg\";\n};\n\nconst toArray = (v: SliderValue | undefined) => (v === undefined ? undefined : typeof v === \"number\" ? [v] : v);\n\n/** A slider on one value or a range: `value`/`defaultValue` + `onValueChange`, `min`, `max`, `step`, `orientation`. */\nexport function Slider<V extends SliderValue = number>({\n value,\n defaultValue,\n onValueChange,\n onValueCommit,\n label,\n thumbLabels,\n formatValue = String,\n showLabel,\n floatingValue,\n origin,\n ticks,\n marks,\n tone,\n size,\n min = 0,\n max = 100,\n step = 1,\n orientation = \"horizontal\",\n className,\n ...props\n}: SliderProps<V>) {\n const s = slider({ tone, size });\n const range = Array.isArray(value ?? defaultValue);\n const vertical = orientation === \"vertical\";\n const [inner, setInner] = React.useState(() => toArray(defaultValue) ?? [min]);\n const current = toArray(value) ?? inner;\n const out = (next: number[]) => (range ? next : next[0]) as V;\n const pct = (v: number) => ((v - min) / (max - min || 1)) * 100;\n const fill =\n origin !== undefined && !range && !vertical\n ? { left: `${Math.min(pct(origin), pct(current[0]!))}%`, right: `${100 - Math.max(pct(origin), pct(current[0]!))}%` }\n : undefined;\n const lo = range ? Math.min(...current) : (origin ?? min);\n const hi = range ? Math.max(...current) : current[0]!;\n const tickValues = ticks === true ? (max - min) / step <= 40 ? Array.from({ length: Math.round((max - min) / step) + 1 }, (_, i) => min + i * step) : [] : ticks ? [...ticks] : [];\n const id = React.useId();\n return (\n <div className={s.root({ className })} data-range={range ? \"\" : undefined} data-orientation={orientation}>\n {showLabel && (\n <div className={s.header()}>\n <span id={`${id}-label`}>{label}</span>\n <span className={s.value()} aria-hidden>\n {current.map(formatValue).join(\" – \")}\n </span>\n </div>\n )}\n <SliderPrimitive.Root\n min={min}\n max={max}\n step={step}\n orientation={orientation}\n value={current}\n onValueChange={(next) => {\n if (value === undefined) setInner(next);\n onValueChange?.(out(next));\n }}\n onValueCommit={onValueCommit && ((next) => onValueCommit(out(next)))}\n className={s.control()}\n {...props}\n >\n <SliderPrimitive.Track className={s.track()}>\n {fill ? <span data-origin=\"\" data-orientation={orientation} className={s.range()} style={fill} /> : <SliderPrimitive.Range className={s.range()} />}\n {tickValues.length ? (\n <span aria-hidden className={s.ticks()}>\n {tickValues.map((t) => (\n <span\n key={t}\n data-orientation={orientation}\n data-on={t > lo && t < hi ? \"\" : undefined}\n className={s.tick()}\n style={vertical ? { bottom: `${pct(t)}%` } : { left: `${pct(t)}%` }}\n />\n ))}\n </span>\n ) : null}\n </SliderPrimitive.Track>\n {current.map((v, i) => (\n <SliderPrimitive.Thumb\n key={i}\n aria-label={range ? (thumbLabels?.[i] ?? label) : showLabel ? undefined : label}\n aria-labelledby={!range && showLabel ? `${id}-label` : undefined}\n aria-valuetext={formatValue(v)}\n className={s.thumb()}\n >\n {floatingValue ? (\n <span aria-hidden data-always={floatingValue === \"always\" ? \"\" : undefined} className={s.bubble({ className: vertical ? s.bubbleVertical() : undefined })}>\n {formatValue(v)}\n </span>\n ) : null}\n </SliderPrimitive.Thumb>\n ))}\n </SliderPrimitive.Root>\n {marks?.length && !vertical ? (\n <div aria-hidden className={s.marks()}>\n {marks.map((m) => (\n <span key={m.value} className={s.mark()} style={{ left: `${pct(m.value)}%` }}>\n {m.label}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n"
459
459
  },
460
460
  {
461
461
  "path": "tv.ts",