@krizaka/ui 2.6.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/README.md +37 -4
  2. package/dist/achievement.d.ts +54 -0
  3. package/dist/achievement.js +73 -0
  4. package/dist/achievement.js.map +1 -0
  5. package/dist/app-shell.d.ts +203 -0
  6. package/dist/app-shell.js +137 -0
  7. package/dist/app-shell.js.map +1 -0
  8. package/dist/auth-layout.d.ts +136 -0
  9. package/dist/auth-layout.js +75 -0
  10. package/dist/auth-layout.js.map +1 -0
  11. package/dist/choice-grid.d.ts +84 -0
  12. package/dist/choice-grid.js +141 -0
  13. package/dist/choice-grid.js.map +1 -0
  14. package/dist/chunk-555LWYIN.js +11 -0
  15. package/dist/chunk-555LWYIN.js.map +1 -0
  16. package/dist/chunk-AZPKXP3O.js +36 -0
  17. package/dist/chunk-AZPKXP3O.js.map +1 -0
  18. package/dist/chunk-NOVUREEC.js +274 -0
  19. package/dist/chunk-NOVUREEC.js.map +1 -0
  20. package/dist/chunk-VCAAHCHL.js +17 -0
  21. package/dist/chunk-VCAAHCHL.js.map +1 -0
  22. package/dist/code-block.js +23 -44
  23. package/dist/code-block.js.map +1 -1
  24. package/dist/continue-prompt.d.ts +48 -0
  25. package/dist/continue-prompt.js +200 -0
  26. package/dist/continue-prompt.js.map +1 -0
  27. package/dist/dashboard-layout.d.ts +139 -0
  28. package/dist/dashboard-layout.js +68 -0
  29. package/dist/dashboard-layout.js.map +1 -0
  30. package/dist/detail-layout.d.ts +126 -0
  31. package/dist/detail-layout.js +48 -0
  32. package/dist/detail-layout.js.map +1 -0
  33. package/dist/docs-layout.d.ts +78 -0
  34. package/dist/docs-layout.js +60 -0
  35. package/dist/docs-layout.js.map +1 -0
  36. package/dist/geometry-CXrAW34x.d.ts +19 -0
  37. package/dist/goal-gauge.d.ts +38 -0
  38. package/dist/goal-gauge.js +53 -0
  39. package/dist/goal-gauge.js.map +1 -0
  40. package/dist/index.d.ts +11 -2
  41. package/dist/index.js +89 -4
  42. package/dist/index.js.map +1 -1
  43. package/dist/landscape.d.ts +41 -0
  44. package/dist/landscape.js +836 -0
  45. package/dist/landscape.js.map +1 -0
  46. package/dist/marketing-layout.d.ts +60 -0
  47. package/dist/marketing-layout.js +25 -0
  48. package/dist/marketing-layout.js.map +1 -0
  49. package/dist/mascot-quote.d.ts +4 -3
  50. package/dist/mascot-quote.js +21 -1
  51. package/dist/mascot-quote.js.map +1 -1
  52. package/dist/mascot-state.d.ts +128 -0
  53. package/dist/mascot-state.js +95 -0
  54. package/dist/mascot-state.js.map +1 -0
  55. package/dist/mascots.d.ts +18 -20
  56. package/dist/mascots.js +65 -221
  57. package/dist/mascots.js.map +1 -1
  58. package/dist/native.d.ts +124 -7
  59. package/dist/native.js +641 -239
  60. package/dist/native.js.map +1 -1
  61. package/dist/profile-card.d.ts +85 -0
  62. package/dist/profile-card.js +87 -0
  63. package/dist/profile-card.js.map +1 -0
  64. package/dist/promise-card.d.ts +28 -0
  65. package/dist/promise-card.js +29 -0
  66. package/dist/promise-card.js.map +1 -0
  67. package/dist/scroll-stage.d.ts +603 -0
  68. package/dist/scroll-stage.js +59 -0
  69. package/dist/scroll-stage.js.map +1 -0
  70. package/dist/settings-layout.d.ts +154 -0
  71. package/dist/settings-layout.js +57 -0
  72. package/dist/settings-layout.js.map +1 -0
  73. package/dist/split-view.d.ts +106 -0
  74. package/dist/split-view.js +41 -0
  75. package/dist/split-view.js.map +1 -0
  76. package/dist/stage-banner.d.ts +34 -0
  77. package/dist/stage-banner.js +26 -0
  78. package/dist/stage-banner.js.map +1 -0
  79. package/dist/story-variant-CLn4H5Nt.d.ts +4 -0
  80. package/dist/story.css +432 -0
  81. package/package.json +267 -10
  82. package/registry/achievement.json +322 -0
  83. package/registry/action-bar.json +1 -1
  84. package/registry/alert.json +1 -1
  85. package/registry/app-shell.json +451 -0
  86. package/registry/argument-card.json +1 -1
  87. package/registry/audio-player.json +1 -1
  88. package/registry/auth-layout.json +230 -0
  89. package/registry/avatar.json +1 -1
  90. package/registry/badge.json +1 -1
  91. package/registry/button.json +5 -5
  92. package/registry/card-grid.json +5 -5
  93. package/registry/card.json +3 -3
  94. package/registry/checkbox.json +1 -1
  95. package/registry/chip-link.json +1 -1
  96. package/registry/chip.json +1 -1
  97. package/registry/choice-grid.json +318 -0
  98. package/registry/code-block.json +1 -1
  99. package/registry/combobox.json +1 -1
  100. package/registry/command.json +1 -1
  101. package/registry/confirm-button.json +3 -3
  102. package/registry/continue-prompt.json +312 -0
  103. package/registry/countdown.json +1 -1
  104. package/registry/cta-panel.json +1 -1
  105. package/registry/dashboard-layout.json +273 -0
  106. package/registry/data-table.json +1 -1
  107. package/registry/date-picker.json +1 -1
  108. package/registry/decision-list.json +1 -1
  109. package/registry/detail-layout.json +233 -0
  110. package/registry/dialog.json +1 -1
  111. package/registry/docs-layout.json +148 -0
  112. package/registry/dropdown-menu.json +3 -3
  113. package/registry/empty-state.json +1 -1
  114. package/registry/examples/achievement/arcade.d.ts +4 -0
  115. package/registry/examples/achievement/arcade.js +15 -0
  116. package/registry/examples/achievement/arcade.tsx +12 -0
  117. package/registry/examples/achievement/milestones.d.ts +4 -0
  118. package/registry/examples/achievement/milestones.js +15 -0
  119. package/registry/examples/achievement/milestones.tsx +12 -0
  120. package/registry/examples/app-shell/admin-console.d.ts +4 -0
  121. package/registry/examples/app-shell/admin-console.js +52 -0
  122. package/registry/examples/app-shell/admin-console.tsx +64 -0
  123. package/registry/examples/app-shell/basic.d.ts +4 -0
  124. package/registry/examples/app-shell/basic.js +37 -0
  125. package/registry/examples/app-shell/basic.tsx +31 -0
  126. package/registry/examples/app-shell/orochia-studio.d.ts +4 -0
  127. package/registry/examples/app-shell/orochia-studio.js +89 -0
  128. package/registry/examples/app-shell/orochia-studio.tsx +101 -0
  129. package/registry/examples/auth-layout/onboarding.d.ts +4 -0
  130. package/registry/examples/auth-layout/onboarding.js +56 -0
  131. package/registry/examples/auth-layout/onboarding.tsx +47 -0
  132. package/registry/examples/auth-layout/sign-in.d.ts +4 -0
  133. package/registry/examples/auth-layout/sign-in.js +55 -0
  134. package/registry/examples/auth-layout/sign-in.tsx +47 -0
  135. package/registry/examples/auth-layout/verify.d.ts +4 -0
  136. package/registry/examples/auth-layout/verify.js +23 -0
  137. package/registry/examples/auth-layout/verify.tsx +19 -0
  138. package/registry/examples/choice-grid/line-up.d.ts +4 -0
  139. package/registry/examples/choice-grid/line-up.js +37 -0
  140. package/registry/examples/choice-grid/line-up.tsx +32 -0
  141. package/registry/examples/choice-grid/plans.d.ts +4 -0
  142. package/registry/examples/choice-grid/plans.js +28 -0
  143. package/registry/examples/choice-grid/plans.tsx +24 -0
  144. package/registry/examples/continue-prompt/arcade.d.ts +4 -0
  145. package/registry/examples/continue-prompt/arcade.js +28 -0
  146. package/registry/examples/continue-prompt/arcade.tsx +25 -0
  147. package/registry/examples/continue-prompt/offer.d.ts +4 -0
  148. package/registry/examples/continue-prompt/offer.js +24 -0
  149. package/registry/examples/continue-prompt/offer.tsx +19 -0
  150. package/registry/examples/dashboard-layout/orazaka-eric.d.ts +4 -0
  151. package/registry/examples/dashboard-layout/orazaka-eric.js +58 -0
  152. package/registry/examples/dashboard-layout/orazaka-eric.tsx +80 -0
  153. package/registry/examples/dashboard-layout/widgets.d.ts +4 -0
  154. package/registry/examples/dashboard-layout/widgets.js +17 -0
  155. package/registry/examples/dashboard-layout/widgets.tsx +22 -0
  156. package/registry/examples/detail-layout/job.d.ts +4 -0
  157. package/registry/examples/detail-layout/job.js +25 -0
  158. package/registry/examples/detail-layout/job.tsx +20 -0
  159. package/registry/examples/detail-layout/video.d.ts +4 -0
  160. package/registry/examples/detail-layout/video.js +44 -0
  161. package/registry/examples/detail-layout/video.tsx +53 -0
  162. package/registry/examples/docs-layout/page.d.ts +4 -0
  163. package/registry/examples/docs-layout/page.js +26 -0
  164. package/registry/examples/docs-layout/page.tsx +30 -0
  165. package/registry/examples/goal-gauge/arcade.d.ts +4 -0
  166. package/registry/examples/goal-gauge/arcade.js +11 -0
  167. package/registry/examples/goal-gauge/arcade.tsx +10 -0
  168. package/registry/examples/goal-gauge/challenge.d.ts +4 -0
  169. package/registry/examples/goal-gauge/challenge.js +11 -0
  170. package/registry/examples/goal-gauge/challenge.tsx +10 -0
  171. package/registry/examples/goal-gauge/drain.d.ts +4 -0
  172. package/registry/examples/goal-gauge/drain.js +11 -0
  173. package/registry/examples/goal-gauge/drain.tsx +10 -0
  174. package/registry/examples/goal-gauge/native/default.d.ts +4 -0
  175. package/registry/examples/goal-gauge/native/default.dark.png +0 -0
  176. package/registry/examples/goal-gauge/native/default.js +15 -0
  177. package/registry/examples/goal-gauge/native/default.light.png +0 -0
  178. package/registry/examples/goal-gauge/native/default.tsx +12 -0
  179. package/registry/examples/landscape/backdrop.d.ts +4 -0
  180. package/registry/examples/landscape/backdrop.js +14 -0
  181. package/registry/examples/landscape/backdrop.tsx +11 -0
  182. package/registry/examples/landscape/brand.d.ts +4 -0
  183. package/registry/examples/landscape/brand.js +11 -0
  184. package/registry/examples/landscape/brand.tsx +10 -0
  185. package/registry/examples/marketing-layout/basic.d.ts +4 -0
  186. package/registry/examples/marketing-layout/basic.js +31 -0
  187. package/registry/examples/marketing-layout/basic.tsx +28 -0
  188. package/registry/examples/marketing-layout/docs-hub.d.ts +4 -0
  189. package/registry/examples/marketing-layout/docs-hub.js +37 -0
  190. package/registry/examples/marketing-layout/docs-hub.tsx +44 -0
  191. package/registry/examples/marketing-layout/pricing.d.ts +4 -0
  192. package/registry/examples/marketing-layout/pricing.js +45 -0
  193. package/registry/examples/marketing-layout/pricing.tsx +55 -0
  194. package/registry/examples/marketing-layout/product-page.d.ts +4 -0
  195. package/registry/examples/marketing-layout/product-page.js +79 -0
  196. package/registry/examples/marketing-layout/product-page.tsx +83 -0
  197. package/registry/examples/mascot-state/empty.d.ts +4 -0
  198. package/registry/examples/mascot-state/empty.js +22 -0
  199. package/registry/examples/mascot-state/empty.tsx +20 -0
  200. package/registry/examples/mascot-state/error.d.ts +4 -0
  201. package/registry/examples/mascot-state/error.js +23 -0
  202. package/registry/examples/mascot-state/error.tsx +21 -0
  203. package/registry/examples/mascot-state/loading.d.ts +4 -0
  204. package/registry/examples/mascot-state/loading.js +22 -0
  205. package/registry/examples/mascot-state/loading.tsx +16 -0
  206. package/registry/examples/mascot-state/native/default.d.ts +4 -0
  207. package/registry/examples/mascot-state/native/default.dark.png +0 -0
  208. package/registry/examples/mascot-state/native/default.js +25 -0
  209. package/registry/examples/mascot-state/native/default.light.png +0 -0
  210. package/registry/examples/mascot-state/native/default.tsx +19 -0
  211. package/registry/examples/mascot-state/success.d.ts +4 -0
  212. package/registry/examples/mascot-state/success.js +21 -0
  213. package/registry/examples/mascot-state/success.tsx +19 -0
  214. package/registry/examples/mascot-state/welcome.d.ts +4 -0
  215. package/registry/examples/mascot-state/welcome.js +21 -0
  216. package/registry/examples/mascot-state/welcome.tsx +19 -0
  217. package/registry/examples/mascots/flock.tsx +1 -1
  218. package/registry/examples/mascots/lovebird.d.ts +4 -0
  219. package/registry/examples/mascots/lovebird.js +19 -0
  220. package/registry/examples/mascots/lovebird.tsx +18 -0
  221. package/registry/examples/mascots/native/lovebird.d.ts +4 -0
  222. package/registry/examples/mascots/native/lovebird.dark.png +0 -0
  223. package/registry/examples/mascots/native/lovebird.js +21 -0
  224. package/registry/examples/mascots/native/lovebird.light.png +0 -0
  225. package/registry/examples/mascots/native/lovebird.tsx +18 -0
  226. package/registry/examples/mascots/native/phoenix.d.ts +4 -0
  227. package/registry/examples/mascots/native/phoenix.dark.png +0 -0
  228. package/registry/examples/mascots/native/phoenix.js +21 -0
  229. package/registry/examples/mascots/native/phoenix.light.png +0 -0
  230. package/registry/examples/mascots/native/phoenix.tsx +18 -0
  231. package/registry/examples/mascots/phoenix.d.ts +4 -0
  232. package/registry/examples/mascots/phoenix.js +14 -0
  233. package/registry/examples/mascots/phoenix.tsx +11 -0
  234. package/registry/examples/profile-card/arcade.d.ts +4 -0
  235. package/registry/examples/profile-card/arcade.js +25 -0
  236. package/registry/examples/profile-card/arcade.tsx +19 -0
  237. package/registry/examples/profile-card/creator.d.ts +4 -0
  238. package/registry/examples/profile-card/creator.js +31 -0
  239. package/registry/examples/profile-card/creator.tsx +26 -0
  240. package/registry/examples/profile-card/match.d.ts +4 -0
  241. package/registry/examples/profile-card/match.js +25 -0
  242. package/registry/examples/profile-card/match.tsx +19 -0
  243. package/registry/examples/profile-card/native/default.d.ts +4 -0
  244. package/registry/examples/profile-card/native/default.dark.png +0 -0
  245. package/registry/examples/profile-card/native/default.js +27 -0
  246. package/registry/examples/profile-card/native/default.light.png +0 -0
  247. package/registry/examples/profile-card/native/default.tsx +22 -0
  248. package/registry/examples/promise-card/arcade.d.ts +4 -0
  249. package/registry/examples/promise-card/arcade.js +25 -0
  250. package/registry/examples/promise-card/arcade.tsx +19 -0
  251. package/registry/examples/promise-card/guarantee.d.ts +4 -0
  252. package/registry/examples/promise-card/guarantee.js +23 -0
  253. package/registry/examples/promise-card/guarantee.tsx +17 -0
  254. package/registry/examples/scroll-stage/arcade.d.ts +4 -0
  255. package/registry/examples/scroll-stage/arcade.js +16 -0
  256. package/registry/examples/scroll-stage/arcade.tsx +15 -0
  257. package/registry/examples/scroll-stage/chapters.d.ts +4 -0
  258. package/registry/examples/scroll-stage/chapters.js +25 -0
  259. package/registry/examples/scroll-stage/chapters.tsx +22 -0
  260. package/registry/examples/settings-layout/account.d.ts +4 -0
  261. package/registry/examples/settings-layout/account.js +51 -0
  262. package/registry/examples/settings-layout/account.tsx +50 -0
  263. package/registry/examples/settings-layout/sections.d.ts +4 -0
  264. package/registry/examples/settings-layout/sections.js +27 -0
  265. package/registry/examples/settings-layout/sections.tsx +24 -0
  266. package/registry/examples/split-view/empty.d.ts +4 -0
  267. package/registry/examples/split-view/empty.js +19 -0
  268. package/registry/examples/split-view/empty.tsx +13 -0
  269. package/registry/examples/split-view/messages.d.ts +4 -0
  270. package/registry/examples/split-view/messages.js +49 -0
  271. package/registry/examples/split-view/messages.tsx +52 -0
  272. package/registry/examples/stage-banner/round.d.ts +4 -0
  273. package/registry/examples/stage-banner/round.js +14 -0
  274. package/registry/examples/stage-banner/round.tsx +11 -0
  275. package/registry/examples/stage-banner/steps.d.ts +4 -0
  276. package/registry/examples/stage-banner/steps.js +14 -0
  277. package/registry/examples/stage-banner/steps.tsx +11 -0
  278. package/registry/field.json +1 -1
  279. package/registry/file-upload.json +1 -1
  280. package/registry/filter-bar.json +1 -1
  281. package/registry/glow-card.json +1 -1
  282. package/registry/goal-gauge.json +322 -0
  283. package/registry/image-cropper.json +1 -1
  284. package/registry/index.json +337 -5
  285. package/registry/iso.json +1 -1
  286. package/registry/kbd.json +1 -1
  287. package/registry/landscape.json +189 -0
  288. package/registry/link-card.json +1 -1
  289. package/registry/loader.json +1 -1
  290. package/registry/marketing-layout.json +172 -0
  291. package/registry/marquee.json +1 -1
  292. package/registry/mascot-quote.json +6 -6
  293. package/registry/mascot-state.json +1249 -0
  294. package/registry/mascots.json +140 -23
  295. package/registry/media-capture.json +1 -1
  296. package/registry/meter.json +1 -1
  297. package/registry/nav-bar.json +1 -1
  298. package/registry/number-ticker.json +1 -1
  299. package/registry/page-header.json +1 -1
  300. package/registry/page-hero.json +3 -3
  301. package/registry/page.json +5 -5
  302. package/registry/pagination.json +1 -1
  303. package/registry/pointer-light.json +1 -1
  304. package/registry/popover.json +1 -1
  305. package/registry/profile-card.json +501 -0
  306. package/registry/progress.json +1 -1
  307. package/registry/promise-card.json +228 -0
  308. package/registry/radio-group.json +1 -1
  309. package/registry/scroll-stage.json +197 -0
  310. package/registry/search-field.json +1 -1
  311. package/registry/section-backdrop.json +1 -1
  312. package/registry/separator.json +1 -1
  313. package/registry/settings-layout.json +231 -0
  314. package/registry/skeleton.json +1 -1
  315. package/registry/slider.json +1 -1
  316. package/registry/slot.json +1 -1
  317. package/registry/spinner.json +1 -1
  318. package/registry/split-view.json +233 -0
  319. package/registry/stage-banner.json +221 -0
  320. package/registry/stat.json +1 -1
  321. package/registry/stepper.json +1 -1
  322. package/registry/switch.json +1 -1
  323. package/registry/tabs.json +1 -1
  324. package/registry/tag-input.json +1 -1
  325. package/registry/terminal-replay.json +1 -1
  326. package/registry/theme.json +1 -1
  327. package/registry/toast.json +1 -1
  328. package/registry/toc.json +1 -1
  329. package/registry/tooltip.json +1 -1
  330. package/registry/video-player.json +1 -1
  331. package/registry/video-trimmer.json +1 -1
@@ -2,8 +2,8 @@
2
2
  "name": "mascots",
3
3
  "type": "primitive",
4
4
  "title": "Mascots",
5
- "summary": "The Krizaka flock: five birds (falcon, owl, duck, flamingo, pigeon) in five moods, drawn in the product's brand colours.",
6
- "description": "The Krizaka flock: five birds (falcon, owl, duck, flamingo, pigeon) in five moods, drawn in the product's brand colours.",
5
+ "summary": "The Krizaka flock: six birds (falcon, owl, duck, flamingo, pigeon, lovebird) in five moods, drawn in the product's brand colours — and the phoenix, the comeback.",
6
+ "description": "The Krizaka flock: six birds (falcon, owl, duck, flamingo, pigeon, lovebird) in five moods, drawn in the product's brand colours — and the phoenix, the comeback.",
7
7
  "status": "beta",
8
8
  "category": "feedback",
9
9
  "platforms": "both",
@@ -30,7 +30,8 @@
30
30
  ],
31
31
  "bestPractices": [
32
32
  "One mascot per screen, and only at a moment that matters: a welcome, a wait, a success, an empty list, an error.",
33
- "Keep the bird's role: the owl reads (documents), the falcon watches (security), the duck builds (code), the flamingo answers (support), the pigeon delivers (notifications, packages).",
33
+ "Keep the bird's role: the owl reads (documents), the falcon watches (security), the duck builds (code), the flamingo answers (support), the pigeon delivers (notifications, packages), the lovebird introduces (matches, two people who meet).",
34
+ "The phoenix (`Phoenix`) does no job: keep it for a comeback — a relaunch, a recovered account, a second chance — never as one more helper.",
34
35
  "Leave `tone=\"brand\"` in a product: the bird wears the product's accent; `own` is for the Krizaka story.",
35
36
  "The words stay beside it (a title, a line, an action): the mascot never carries the message alone."
36
37
  ],
@@ -50,16 +51,16 @@
50
51
  ],
51
52
  "web": {
52
53
  "entry": "@krizaka/ui/mascots",
53
- "import": "import { Mascot, MASCOT_BIRDS, MASCOT_MOODS } from \"@krizaka/ui/mascots\";",
54
+ "import": "import { Mascot, MASCOT_BIRDS, MASCOT_MOODS, Phoenix } from \"@krizaka/ui/mascots\";",
54
55
  "props": [
55
56
  {
56
57
  "component": "MASCOT_BIRDS",
57
- "description": "The Krizaka flock — the geometry of the mascots, drawn by the web `Mascot` (`./mascot.tsx`) and the native one\n(`../native/mascots.tsx`). Five birds that have carried the Krizaka story since the first site (the sentry falcon,\nthe owl that reads, the developer duck, the flamingo that answers, the carrier pigeon), redrawn on one 120 × 120\ngrid so that any bird can wear any mood.\n\nColours are roles, never fixed: `brand` (the default) paints a bird in the host's accent (`--kz-accent`,\n`--kz-accent-2`), so the same falcon is blue on krizaka.com, orange in Orazaka and violet in Orochia; `own` gives\neach bird the signature palette it has in the Krizaka story (the brand layer's exception, like the marks). Shades\nand highlights are black and white at a low opacity over the role, so they hold in both themes.",
58
+ "description": "The Krizaka flock — the geometry of the mascots, drawn by the web `Mascot` (`./mascot.tsx`) and the native one\n(`../native/mascots.tsx`). Five birds that have carried the Krizaka story since the first site (the sentry falcon,\nthe owl that reads, the developer duck, the flamingo that answers, the carrier pigeon) and the lovebird that\nintroduces (2026-10, with Yazakia: \"l'inséparable présente\"), redrawn on one 120 × 120 grid so that any bird can\nwear any mood.\n\nColours are roles, never fixed: `brand` (the default) paints a bird in the host's accent (`--kz-accent`,\n`--kz-accent-2`), so the same falcon is blue on krizaka.com, orange in Orazaka and violet in Orochia; `own` gives\neach bird the signature palette it has in the Krizaka story (the brand layer's exception, like the marks). Shades\nand highlights are black and white at a low opacity over the role, so they hold in both themes.",
58
59
  "props": []
59
60
  },
60
61
  {
61
62
  "component": "Mascot",
62
- "description": "One of the five Krizaka birds in one mood — for the moments a product speaks to a person: a welcome, a long wait,\na success, an empty list, an error. Drawn in the host's brand colours by default (`tone=\"own\"` for the story's).",
63
+ "description": "One of the six Krizaka birds in one mood — for the moments a product speaks to a person: a welcome, a long wait,\na success, an empty list, an error. Drawn in the host's brand colours by default (`tone=\"own\"` for the story's).",
63
64
  "props": [
64
65
  {
65
66
  "name": "animated",
@@ -70,9 +71,9 @@
70
71
  },
71
72
  {
72
73
  "name": "bird",
73
- "type": "\"falcon\" | \"owl\" | \"duck\" | \"flamingo\" | \"pigeon\"",
74
+ "type": "\"falcon\" | \"owl\" | \"duck\" | \"flamingo\" | \"pigeon\" | \"lovebird\"",
74
75
  "default": "owl",
75
- "description": "Which bird of the flock: `falcon` (watch, security), `owl` (reading, documents), `duck` (building, code), `flamingo` (answers, support), `pigeon` (delivery, notifications).",
76
+ "description": "Which bird of the flock: `falcon` (watch, security), `owl` (reading, documents), `duck` (building, code), `flamingo` (answers, support), `pigeon` (delivery, notifications), `lovebird` (introductions, two people who meet).",
76
77
  "required": false
77
78
  },
78
79
  {
@@ -104,6 +105,40 @@
104
105
  "required": false
105
106
  }
106
107
  ]
108
+ },
109
+ {
110
+ "component": "Phoenix",
111
+ "description": "The phoenix: the comeback, beside the five birds of the flock (`Mascot`). Decorative unless titled.",
112
+ "props": [
113
+ {
114
+ "name": "animated",
115
+ "type": "boolean",
116
+ "default": "true",
117
+ "description": "A slow float and a breathing core. Off with `false`; always off under `prefers-reduced-motion`.",
118
+ "required": false
119
+ },
120
+ {
121
+ "name": "size",
122
+ "type": "number",
123
+ "default": "120",
124
+ "description": "Width and height in pixels.",
125
+ "required": false
126
+ },
127
+ {
128
+ "name": "title",
129
+ "type": "string",
130
+ "default": null,
131
+ "description": "An accessible name (the phoenix becomes an image); without it, it is decorative.",
132
+ "required": false
133
+ },
134
+ {
135
+ "name": "tone",
136
+ "type": "\"brand\" | \"own\"",
137
+ "default": "own",
138
+ "description": "`own` (default): the violet wing and the gold wing of the Krizaka story. `brand`: the host's accent and accent-2.",
139
+ "required": false
140
+ }
141
+ ]
107
142
  }
108
143
  ],
109
144
  "examples": [
@@ -118,10 +153,10 @@
118
153
  {
119
154
  "name": "flock",
120
155
  "title": "The flock",
121
- "description": "The five birds in the brand colours of the host.",
156
+ "description": "The six birds in the brand colours of the host.",
122
157
  "path": "examples/mascots/flock.tsx",
123
158
  "module": "@krizaka/ui/registry/examples/mascots/flock",
124
- "code": "import { Mascot,MASCOT_BIRDS } from \"@krizaka/ui/mascots\";\n\n// The five birds in the host's brand colours: blue on Krizaka, orange in Orazaka, violet in Orochia.\nexport default function MascotFlock() {\n return (\n <div className=\"flex flex-wrap items-end gap-6\">\n {MASCOT_BIRDS.map((bird) => (\n <figure key={bird} className=\"flex flex-col items-center gap-2\">\n <Mascot bird={bird} mood=\"welcome\" size={88} />\n <figcaption className=\"text-xs text-fg-secondary\">{bird}</figcaption>\n </figure>\n ))}\n </div>\n );\n}\n"
159
+ "code": "import { Mascot,MASCOT_BIRDS } from \"@krizaka/ui/mascots\";\n\n// The six birds in the host's brand colours: blue on Krizaka, orange in Orazaka, violet in Orochia, turquoise in Yazakia.\nexport default function MascotFlock() {\n return (\n <div className=\"flex flex-wrap items-end gap-6\">\n {MASCOT_BIRDS.map((bird) => (\n <figure key={bird} className=\"flex flex-col items-center gap-2\">\n <Mascot bird={bird} mood=\"welcome\" size={88} />\n <figcaption className=\"text-xs text-fg-secondary\">{bird}</figcaption>\n </figure>\n ))}\n </div>\n );\n}\n"
125
160
  },
126
161
  {
127
162
  "name": "own-tone",
@@ -146,12 +181,28 @@
146
181
  "path": "examples/mascots/long-task.tsx",
147
182
  "module": "@krizaka/ui/registry/examples/mascots/long-task",
148
183
  "code": "import { Mascot } from \"@krizaka/ui/mascots\";\nimport { Progress } from \"@krizaka/ui/progress\";\n\n// A job of a few minutes: the pigeon thinks beside its progress; the label says what runs.\nexport default function MascotLongTask() {\n return (\n <div className=\"flex w-96 items-center gap-4 rounded-xl border border-border-default bg-surface-1 p-4\">\n <Mascot bird=\"pigeon\" mood=\"thinking\" size={72} />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-fg\">Rendering the product video</p>\n <Progress label=\"Rendering\" value={42} className=\"mt-2\" />\n </div>\n </div>\n );\n}\n"
184
+ },
185
+ {
186
+ "name": "lovebird",
187
+ "title": "An introduction",
188
+ "description": "The lovebird celebrates a mutual interest: two people who chose each other meet.",
189
+ "path": "examples/mascots/lovebird.tsx",
190
+ "module": "@krizaka/ui/registry/examples/mascots/lovebird",
191
+ "code": "import { Button } from \"@krizaka/ui/button\";\nimport { Mascot } from \"@krizaka/ui/mascots\";\n\n// The lovebird introduces: two people who chose each other meet — the words and the next step beside it.\nexport default function MascotLovebird() {\n return (\n <div className=\"flex w-96 items-center gap-4 rounded-xl border border-border-default bg-surface-1 p-4\">\n <Mascot bird=\"lovebird\" mood=\"success\" size={88} />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-fg\">You are both interested</p>\n <p className=\"mt-1 text-sm text-fg-secondary\">Your introduction is open for seven days.</p>\n <Button variant=\"primary\" size=\"sm\" className=\"mt-3\">\n Say hello\n </Button>\n </div>\n </div>\n );\n}\n"
192
+ },
193
+ {
194
+ "name": "phoenix",
195
+ "title": "The phoenix",
196
+ "description": "The sixth figure: a comeback — a relaunch, a recovered account — in its own colours or the brand's.",
197
+ "path": "examples/mascots/phoenix.tsx",
198
+ "module": "@krizaka/ui/registry/examples/mascots/phoenix",
199
+ "code": "import { Phoenix } from \"@krizaka/ui/mascots\";\n\n// The phoenix stands for a comeback — a relaunch, a recovered account: its own colours, then the product's accent.\nexport default function MascotPhoenix() {\n return (\n <div className=\"flex flex-wrap items-end gap-8\">\n <Phoenix size={112} title=\"The phoenix\" />\n <Phoenix size={112} tone=\"brand\" title=\"The phoenix in the brand's colours\" />\n </div>\n );\n}\n"
149
200
  }
150
201
  ]
151
202
  },
152
203
  "native": {
153
204
  "entry": "@krizaka/ui/native",
154
- "import": "import { Mascot } from \"@krizaka/ui/native\";",
205
+ "import": "import { Mascot, Phoenix } from \"@krizaka/ui/native\";",
155
206
  "differences": [
156
207
  "Only the idle bob and the badges: the wave, the dots and the hop are web gestures.",
157
208
  "Style with `style`, not `className`."
@@ -159,7 +210,7 @@
159
210
  "props": [
160
211
  {
161
212
  "component": "Mascot",
162
- "description": "One of the five Krizaka birds in one mood, for React Native — the geometry of the web `Mascot`\n(mascots/geometry.ts), in the theme's accent by default.",
213
+ "description": "One of the six Krizaka birds in one mood, for React Native — the geometry of the web `Mascot`\n(mascots/geometry.ts), in the theme's accent by default.",
163
214
  "props": [
164
215
  {
165
216
  "name": "animated",
@@ -170,9 +221,9 @@
170
221
  },
171
222
  {
172
223
  "name": "bird",
173
- "type": "\"falcon\" | \"owl\" | \"duck\" | \"flamingo\" | \"pigeon\"",
224
+ "type": "\"falcon\" | \"owl\" | \"duck\" | \"flamingo\" | \"pigeon\" | \"lovebird\"",
174
225
  "default": "owl",
175
- "description": "Which bird of the flock: `falcon` (watch), `owl` (reading), `duck` (building), `flamingo` (answers), `pigeon` (delivery).",
226
+ "description": "Which bird of the flock: `falcon` (watch), `owl` (reading), `duck` (building), `flamingo` (answers), `pigeon` (delivery), `lovebird` (introductions).",
176
227
  "required": false
177
228
  },
178
229
  {
@@ -225,29 +276,61 @@
225
276
  "dark": "examples/mascots/native/default.dark.png",
226
277
  "light": "examples/mascots/native/default.light.png"
227
278
  }
279
+ },
280
+ {
281
+ "name": "lovebird",
282
+ "title": "The lovebird",
283
+ "description": "The lovebird welcoming, then celebrating a mutual interest.",
284
+ "path": "examples/mascots/native/lovebird.tsx",
285
+ "module": "@krizaka/ui/registry/examples/mascots/native/lovebird",
286
+ "code": "import { Mascot, Txt } from \"@krizaka/ui/native\";\nimport { View } from \"react-native\";\n\n// The lovebird introduces two people: welcoming, then celebrating a mutual interest.\nexport default function NativeMascotLovebird() {\n return (\n <View style={{ flexDirection: \"row\", gap: 16, alignItems: \"flex-end\" }}>\n <View style={{ alignItems: \"center\", gap: 4 }}>\n <Mascot bird=\"lovebird\" mood=\"welcome\" size={80} />\n <Txt variant=\"caption\">welcome</Txt>\n </View>\n <View style={{ alignItems: \"center\", gap: 4 }}>\n <Mascot bird=\"lovebird\" mood=\"success\" size={80} />\n <Txt variant=\"caption\">mutual</Txt>\n </View>\n </View>\n );\n}\n",
287
+ "screenshots": {
288
+ "dark": "examples/mascots/native/lovebird.dark.png",
289
+ "light": "examples/mascots/native/lovebird.light.png"
290
+ }
291
+ },
292
+ {
293
+ "name": "phoenix",
294
+ "title": "The phoenix",
295
+ "description": "The comeback, in its own colours and in the app's accent.",
296
+ "path": "examples/mascots/native/phoenix.tsx",
297
+ "module": "@krizaka/ui/registry/examples/mascots/native/phoenix",
298
+ "code": "import { Phoenix, Txt } from \"@krizaka/ui/native\";\nimport { View } from \"react-native\";\n\n// The comeback: the phoenix in its own colours, then in the app's accent.\nexport default function NativeMascotPhoenix() {\n return (\n <View style={{ flexDirection: \"row\", gap: 20, alignItems: \"flex-end\" }}>\n <View style={{ alignItems: \"center\", gap: 4 }}>\n <Phoenix size={88} animated={false} />\n <Txt variant=\"caption\">own</Txt>\n </View>\n <View style={{ alignItems: \"center\", gap: 4 }}>\n <Phoenix size={88} tone=\"brand\" animated={false} />\n <Txt variant=\"caption\">brand</Txt>\n </View>\n </View>\n );\n}\n",
299
+ "screenshots": {
300
+ "dark": "examples/mascots/native/phoenix.dark.png",
301
+ "light": "examples/mascots/native/phoenix.light.png"
302
+ }
228
303
  }
229
304
  ]
230
305
  },
231
306
  "files": [
232
307
  {
233
308
  "path": "mascots/geometry.ts",
234
- "content": "/**\n * The Krizaka flock — the geometry of the mascots, drawn by the web `Mascot` (`./mascot.tsx`) and the native one\n * (`../native/mascots.tsx`). Five birds that have carried the Krizaka story since the first site (the sentry falcon,\n * the owl that reads, the developer duck, the flamingo that answers, the carrier pigeon), redrawn on one 120 × 120\n * grid so that any bird can wear any mood.\n *\n * Colours are roles, never fixed: `brand` (the default) paints a bird in the host's accent (`--kz-accent`,\n * `--kz-accent-2`), so the same falcon is blue on krizaka.com, orange in Orazaka and violet in Orochia; `own` gives\n * each bird the signature palette it has in the Krizaka story (the brand layer's exception, like the marks). Shades\n * and highlights are black and white at a low opacity over the role, so they hold in both themes.\n */\n\nexport const MASCOT_BIRDS = [\"falcon\", \"owl\", \"duck\", \"flamingo\", \"pigeon\"] as const;\nexport type MascotBird = (typeof MASCOT_BIRDS)[number];\n\nexport const MASCOT_MOODS = [\"welcome\", \"thinking\", \"success\", \"empty\", \"error\"] as const;\nexport type MascotMood = (typeof MASCOT_MOODS)[number];\n\nexport type MascotTone = \"brand\" | \"own\";\n\ntype Point = readonly [number, number];\n\nexport interface BirdGeometry {\n /** The ground shadow: centre x, y and radii. */\n readonly shadow: readonly [number, number, number, number];\n /** Legs and feet, stroked in the beak colour. */\n readonly legs: readonly string[];\n /** Drawn behind the body (tail, parcel strings). */\n readonly back?: readonly string[];\n readonly body: string;\n readonly belly: string;\n /** Left then right wing, and the point each one turns around. */\n readonly wings: readonly [{ readonly d: string; readonly pivot: Point }, { readonly d: string; readonly pivot: Point }];\n /** A neck, stroked in the body colour with this width (the flamingo). */\n readonly neck?: { readonly d: string; readonly width: number };\n /** The head: a circle. */\n readonly head: { readonly cx: number; readonly cy: number; readonly r: number };\n /** Ear tufts, a crest, a visor's frame: filled with the detail colour. */\n readonly details: readonly string[];\n /** A band behind the eyes (the falcon's visor), in the page's surface. */\n readonly visor?: { readonly x: number; readonly y: number; readonly w: number; readonly h: number };\n /** Glasses (the duck): stroked rings around the eyes. */\n readonly glasses?: boolean;\n readonly beak: string;\n /** The eyes: centres, the radius of the white, the radius of the pupil (no white when `white` is 0). */\n readonly eyes: { readonly at: readonly Point[]; readonly white: number; readonly pupil: number };\n /** The parcel the pigeon carries: its three faces. */\n readonly parcel?: readonly [string, string, string];\n}\n\nconst mirror = (d: string) => d.replace(/(-?\\d+(?:\\.\\d+)?) (-?\\d+(?:\\.\\d+)?)/g, (_, x: string, y: string) => `${120 - Number(x)} ${y}`);\n\nconst falconWing = \"M29 58 C17 68 19 88 37 86 C31 76 31 66 29 58 Z\";\nconst pigeonWing = \"M36 58 C20 50 16 80 38 84 C34 74 34 66 36 58 Z\";\n\nexport const BIRDS: Readonly<Record<MascotBird, BirdGeometry>> = {\n falcon: {\n shadow: [60, 110, 26, 3.5],\n legs: [\"M51 94 L48 108 M44 108 L52 108\", \"M69 94 L72 108 M68 108 L76 108\"],\n body: \"M30 62 C30 42 90 42 90 62 C90 88 78 98 60 98 C42 98 30 88 30 62 Z\",\n belly: \"M41 66 C41 55 79 55 79 66 C79 82 71 90 60 90 C49 90 41 82 41 66 Z\",\n wings: [\n { d: falconWing, pivot: [29, 58] },\n { d: mirror(falconWing), pivot: [91, 58] },\n ],\n head: { cx: 60, cy: 38, r: 22 },\n details: [\"M46 20 L52 10 L56 19 Z\", \"M74 20 L68 10 L64 19 Z\"],\n visor: { x: 41, y: 29, w: 38, h: 13 },\n beak: \"M54 45 L66 45 L60 57 Z\",\n eyes: { at: [[50, 35.5], [70, 35.5]], white: 0, pupil: 3.2 },\n },\n owl: {\n shadow: [60, 110, 24, 3.5],\n legs: [\"M53 100 L51 107 M47 107 L55 107\", \"M67 100 L69 107 M65 107 L73 107\"],\n body: \"M28 52 C28 20 92 20 92 52 C92 86 82 102 60 102 C38 102 28 86 28 52 Z\",\n belly: \"M42 66 C42 52 78 52 78 66 C78 86 72 94 60 94 C48 94 42 86 42 66 Z\",\n wings: [\n { d: \"M29 58 C18 68 20 88 36 90 C32 78 32 68 29 58 Z\", pivot: [29, 58] },\n { d: mirror(\"M29 58 C18 68 20 88 36 90 C32 78 32 68 29 58 Z\"), pivot: [91, 58] },\n ],\n head: { cx: 60, cy: 42, r: 0 },\n details: [\"M31 20 L47 31 L31 41 Z\", \"M89 20 L73 31 L89 41 Z\"],\n beak: \"M55 50 L65 50 L60 59 Z\",\n eyes: { at: [[47, 41], [73, 41]], white: 10, pupil: 4 },\n },\n duck: {\n shadow: [60, 110, 30, 3.5],\n legs: [],\n back: [\"M88 80 C100 76 104 66 96 62 C98 70 94 74 88 74 Z\"],\n body: \"M26 80 C26 62 94 62 94 80 C94 100 82 106 60 106 C38 106 26 100 26 80 Z\",\n belly: \"M38 84 C38 72 82 72 82 84 C82 96 74 101 60 101 C46 101 38 96 38 84 Z\",\n wings: [\n { d: \"M27 74 C14 80 18 98 35 96 C31 88 29 80 27 74 Z\", pivot: [27, 74] },\n { d: mirror(\"M27 74 C14 80 18 98 35 96 C31 88 29 80 27 74 Z\"), pivot: [93, 74] },\n ],\n head: { cx: 60, cy: 42, r: 23 },\n details: [\"M57 20 C54 11 63 8 66 14 C62 13 60 16 61 20 Z\"],\n glasses: true,\n beak: \"M43 51 C43 46 55 45 60 45 C65 45 77 46 77 51 C77 56 66 60 60 60 C54 60 43 56 43 51 Z\",\n eyes: { at: [[51, 37], [69, 37]], white: 0, pupil: 2.6 },\n },\n flamingo: {\n shadow: [60, 110, 20, 3],\n legs: [\"M56 86 L55 97 L58 108 M53 108 L62 108\", \"M65 86 L66 108 M62 108 L71 108\"],\n body: \"M38 70 C38 54 88 54 88 70 C88 86 76 90 62 90 C48 90 38 86 38 70 Z\",\n belly: \"M46 72 C46 63 80 63 80 72 C80 82 72 85 63 85 C54 85 46 82 46 72 Z\",\n wings: [\n { d: \"M42 64 C30 68 32 84 47 84 C43 76 43 70 42 64 Z\", pivot: [42, 64] },\n { d: \"M84 64 C96 68 94 84 79 84 C83 76 83 70 84 64 Z\", pivot: [84, 64] },\n ],\n neck: { d: \"M58 60 C58 42 40 34 46 22\", width: 7 },\n head: { cx: 48, cy: 18, r: 11 },\n details: [],\n beak: \"M38 16 C28 18 29 28 24 33 C33 28 38 23 40 20 Z\",\n eyes: { at: [[46, 15]], white: 3.2, pupil: 1.8 },\n },\n pigeon: {\n shadow: [60, 112, 22, 3],\n legs: [\"M52 94 L51 99 M48 99 L54 99\", \"M68 94 L69 99 M66 99 L72 99\"],\n back: [\"M53 98 L52 103\", \"M67 98 L68 103\", \"M30 68 C16 72 14 62 28 56 C17 57 15 49 32 48 Z\"],\n body: \"M34 64 C34 44 86 44 86 64 C86 86 74 96 60 96 C46 96 34 86 34 64 Z\",\n belly: \"M43 68 C43 57 77 57 77 68 C77 82 70 88 60 88 C50 88 43 82 43 68 Z\",\n wings: [\n { d: pigeonWing, pivot: [36, 58] },\n { d: mirror(pigeonWing), pivot: [84, 58] },\n ],\n head: { cx: 60, cy: 36, r: 19 },\n details: [\"M54 18 C56 12 64 12 66 18 C63 16 57 16 54 18 Z\"],\n beak: \"M55 41 L65 41 L60 50 Z\",\n eyes: { at: [[52, 33], [68, 33]], white: 5, pupil: 2.4 },\n parcel: [\"M60 100 L71 105 L60 110 L49 105 Z\", \"M49 105 L60 110 L60 118 L49 113 Z\", \"M71 105 L60 110 L60 118 L71 113 Z\"],\n },\n};\n\n/** What a mood adds around the bird: a badge, a thought, an empty tray. */\nexport const MOOD_PARTS = {\n /** The badge of `success` and `error`, top right. */\n badge: { cx: 98, cy: 22, r: 11 },\n check: \"M92.5 22.5 L96.8 26.6 L103.6 18.4\",\n bang: { line: \"M98 15.5 L98 23.5\", dot: [98, 28] as Point },\n /** `thinking`: three dots rising to the right of the head. */\n dots: [\n [88, 26, 2.6],\n [96, 19, 3.2],\n [105, 11, 3.8],\n ] as const,\n /** `empty`: an open tray beside the bird. */\n tray: \"M90 96 L110 96 L107 108 L93 108 Z\",\n /** `success`: two sparkles. */\n sparkles: [\"M16 26 L18 31 L23 33 L18 35 L16 40 L14 35 L9 33 L14 31 Z\", \"M104 52 L105.2 55 L108 56 L105.2 57 L104 60 L102.8 57 L100 56 L102.8 55 Z\"],\n} as const;\n\n/** The happy eye of `success`: an arch. */\nexport const archEye = ([x, y]: Point, r: number) => `M${x - r} ${y + r * 0.45} Q${x} ${y - r * 1.15} ${x + r} ${y + r * 0.45}`;\n/** The lowered lid of `empty`: a line across the eye. */\nexport const lidEye = ([x, y]: Point, r: number) => `M${x - r} ${y + 0.4} L${x + r} ${y + 0.4}`;\n/** The heavy lid of `empty` over an eye with a white: its upper half. */\nexport const upperLid = ([x, y]: Point, r: number) => `M${x - r} ${y} A${r} ${r} 0 0 1 ${x + r} ${y} Z`;\n/** The worried brow of `error`, above one eye (`side` -1 left, 1 right). */\nexport const brow = ([x, y]: Point, r: number, side: number) =>\n `M${x - r * 1.1} ${y - r * 1.25 - (side < 0 ? 0 : r * 0.7)} L${x + r * 1.1} ${y - r * 1.5 - (side < 0 ? r * 0.7 : 0)}`;\n\nexport interface MascotPalette {\n /** Top and bottom of the body gradient. */\n readonly from: string;\n readonly to: string;\n /** Wings and details (tufts, crest). */\n readonly wing: string;\n readonly detail: string;\n /** Beak and feet. */\n readonly beak: string;\n /** The pupils (and the falcon's visor lights). */\n readonly eye: string;\n}\n\n/** The signature palettes of the Krizaka story (`tone=\"own\"`), from the first drawings of each bird. */\nexport const OWN_PALETTES: Readonly<Record<MascotBird, MascotPalette>> = {\n falcon: { from: \"#ff9f00\", to: \"#ff2a00\", wing: \"#d84315\", detail: \"#b4320d\", beak: \"#ffb300\", eye: \"#00c8d4\" },\n owl: { from: \"#a78bfa\", to: \"#6d28d9\", wing: \"#7c3aed\", detail: \"#5b21b6\", beak: \"#fbbf24\", eye: \"#1f2937\" },\n duck: { from: \"#f43f5e\", to: \"#be123c\", wing: \"#db2777\", detail: \"#9f1239\", beak: \"#fbbf24\", eye: \"#1f2937\" },\n flamingo: { from: \"#f9a8d4\", to: \"#db2777\", wing: \"#ec4899\", detail: \"#be185d\", beak: \"#fbbf24\", eye: \"#1f2937\" },\n pigeon: { from: \"#22d3ee\", to: \"#6366f1\", wing: \"#a855f7\", detail: \"#4338ca\", beak: \"#f59e0b\", eye: \"#1f2937\" },\n};\n\n/** Fixed light and shade, laid at a low opacity over a role (they read on both themes). */\nexport const LIGHT = \"#ffffff\";\nexport const SHADE = \"#000000\";\n/** The parcel's faces (pigeon). */\nexport const PARCEL = [\"#f59e0b\", \"#d97706\", \"#b45309\"] as const;\n"
309
+ "content": "/**\n * The Krizaka flock — the geometry of the mascots, drawn by the web `Mascot` (`./mascot.tsx`) and the native one\n * (`../native/mascots.tsx`). Five birds that have carried the Krizaka story since the first site (the sentry falcon,\n * the owl that reads, the developer duck, the flamingo that answers, the carrier pigeon) and the lovebird that\n * introduces (2026-10, with Yazakia: \"l'inséparable présente\"), redrawn on one 120 × 120 grid so that any bird can\n * wear any mood.\n *\n * Colours are roles, never fixed: `brand` (the default) paints a bird in the host's accent (`--kz-accent`,\n * `--kz-accent-2`), so the same falcon is blue on krizaka.com, orange in Orazaka and violet in Orochia; `own` gives\n * each bird the signature palette it has in the Krizaka story (the brand layer's exception, like the marks). Shades\n * and highlights are black and white at a low opacity over the role, so they hold in both themes.\n */\n\nexport const MASCOT_BIRDS = [\"falcon\", \"owl\", \"duck\", \"flamingo\", \"pigeon\", \"lovebird\"] as const;\nexport type MascotBird = (typeof MASCOT_BIRDS)[number];\n\nexport const MASCOT_MOODS = [\"welcome\", \"thinking\", \"success\", \"empty\", \"error\"] as const;\nexport type MascotMood = (typeof MASCOT_MOODS)[number];\n\nexport type MascotTone = \"brand\" | \"own\";\n\ntype Point = readonly [number, number];\n\nexport interface BirdGeometry {\n /** The ground shadow: centre x, y and radii. */\n readonly shadow: readonly [number, number, number, number];\n /** Legs and feet, stroked in the beak colour. */\n readonly legs: readonly string[];\n /** Drawn behind the body (tail, parcel strings). */\n readonly back?: readonly string[];\n readonly body: string;\n readonly belly: string;\n /** Left then right wing, and the point each one turns around. */\n readonly wings: readonly [{ readonly d: string; readonly pivot: Point }, { readonly d: string; readonly pivot: Point }];\n /** A neck, stroked in the body colour with this width (the flamingo). */\n readonly neck?: { readonly d: string; readonly width: number };\n /** The head: a circle. */\n readonly head: { readonly cx: number; readonly cy: number; readonly r: number };\n /** Ear tufts, a crest, a visor's frame: filled with the detail colour. */\n readonly details: readonly string[];\n /** A band behind the eyes (the falcon's visor), in the page's surface. */\n readonly visor?: { readonly x: number; readonly y: number; readonly w: number; readonly h: number };\n /** Glasses (the duck): stroked rings around the eyes. */\n readonly glasses?: boolean;\n readonly beak: string;\n /** The eyes: centres, the radius of the white, the radius of the pupil (no white when `white` is 0). */\n readonly eyes: { readonly at: readonly Point[]; readonly white: number; readonly pupil: number };\n /** The parcel the pigeon carries: its three faces. */\n readonly parcel?: readonly [string, string, string];\n}\n\nconst mirror = (d: string) => d.replace(/(-?\\d+(?:\\.\\d+)?) (-?\\d+(?:\\.\\d+)?)/g, (_, x: string, y: string) => `${120 - Number(x)} ${y}`);\n\nconst falconWing = \"M29 58 C17 68 19 88 37 86 C31 76 31 66 29 58 Z\";\nconst pigeonWing = \"M36 58 C20 50 16 80 38 84 C34 74 34 66 36 58 Z\";\n\nexport const BIRDS: Readonly<Record<MascotBird, BirdGeometry>> = {\n falcon: {\n shadow: [60, 110, 26, 3.5],\n legs: [\"M51 94 L48 108 M44 108 L52 108\", \"M69 94 L72 108 M68 108 L76 108\"],\n body: \"M30 62 C30 42 90 42 90 62 C90 88 78 98 60 98 C42 98 30 88 30 62 Z\",\n belly: \"M41 66 C41 55 79 55 79 66 C79 82 71 90 60 90 C49 90 41 82 41 66 Z\",\n wings: [\n { d: falconWing, pivot: [29, 58] },\n { d: mirror(falconWing), pivot: [91, 58] },\n ],\n head: { cx: 60, cy: 38, r: 22 },\n details: [\"M46 20 L52 10 L56 19 Z\", \"M74 20 L68 10 L64 19 Z\"],\n visor: { x: 41, y: 29, w: 38, h: 13 },\n beak: \"M54 45 L66 45 L60 57 Z\",\n eyes: { at: [[50, 35.5], [70, 35.5]], white: 0, pupil: 3.2 },\n },\n owl: {\n shadow: [60, 110, 24, 3.5],\n legs: [\"M53 100 L51 107 M47 107 L55 107\", \"M67 100 L69 107 M65 107 L73 107\"],\n body: \"M28 52 C28 20 92 20 92 52 C92 86 82 102 60 102 C38 102 28 86 28 52 Z\",\n belly: \"M42 66 C42 52 78 52 78 66 C78 86 72 94 60 94 C48 94 42 86 42 66 Z\",\n wings: [\n { d: \"M29 58 C18 68 20 88 36 90 C32 78 32 68 29 58 Z\", pivot: [29, 58] },\n { d: mirror(\"M29 58 C18 68 20 88 36 90 C32 78 32 68 29 58 Z\"), pivot: [91, 58] },\n ],\n head: { cx: 60, cy: 42, r: 0 },\n details: [\"M31 20 L47 31 L31 41 Z\", \"M89 20 L73 31 L89 41 Z\"],\n beak: \"M55 50 L65 50 L60 59 Z\",\n eyes: { at: [[47, 41], [73, 41]], white: 10, pupil: 4 },\n },\n duck: {\n shadow: [60, 110, 30, 3.5],\n legs: [],\n back: [\"M88 80 C100 76 104 66 96 62 C98 70 94 74 88 74 Z\"],\n body: \"M26 80 C26 62 94 62 94 80 C94 100 82 106 60 106 C38 106 26 100 26 80 Z\",\n belly: \"M38 84 C38 72 82 72 82 84 C82 96 74 101 60 101 C46 101 38 96 38 84 Z\",\n wings: [\n { d: \"M27 74 C14 80 18 98 35 96 C31 88 29 80 27 74 Z\", pivot: [27, 74] },\n { d: mirror(\"M27 74 C14 80 18 98 35 96 C31 88 29 80 27 74 Z\"), pivot: [93, 74] },\n ],\n head: { cx: 60, cy: 42, r: 23 },\n details: [\"M57 20 C54 11 63 8 66 14 C62 13 60 16 61 20 Z\"],\n glasses: true,\n beak: \"M43 51 C43 46 55 45 60 45 C65 45 77 46 77 51 C77 56 66 60 60 60 C54 60 43 56 43 51 Z\",\n eyes: { at: [[51, 37], [69, 37]], white: 0, pupil: 2.6 },\n },\n flamingo: {\n shadow: [60, 110, 20, 3],\n legs: [\"M56 86 L55 97 L58 108 M53 108 L62 108\", \"M65 86 L66 108 M62 108 L71 108\"],\n body: \"M38 70 C38 54 88 54 88 70 C88 86 76 90 62 90 C48 90 38 86 38 70 Z\",\n belly: \"M46 72 C46 63 80 63 80 72 C80 82 72 85 63 85 C54 85 46 82 46 72 Z\",\n wings: [\n { d: \"M42 64 C30 68 32 84 47 84 C43 76 43 70 42 64 Z\", pivot: [42, 64] },\n { d: \"M84 64 C96 68 94 84 79 84 C83 76 83 70 84 64 Z\", pivot: [84, 64] },\n ],\n neck: { d: \"M58 60 C58 42 40 34 46 22\", width: 7 },\n head: { cx: 48, cy: 18, r: 11 },\n details: [],\n beak: \"M38 16 C28 18 29 28 24 33 C33 28 38 23 40 20 Z\",\n eyes: { at: [[46, 15]], white: 3.2, pupil: 1.8 },\n },\n pigeon: {\n shadow: [60, 112, 22, 3],\n legs: [\"M52 94 L51 99 M48 99 L54 99\", \"M68 94 L69 99 M66 99 L72 99\"],\n back: [\"M53 98 L52 103\", \"M67 98 L68 103\", \"M30 68 C16 72 14 62 28 56 C17 57 15 49 32 48 Z\"],\n body: \"M34 64 C34 44 86 44 86 64 C86 86 74 96 60 96 C46 96 34 86 34 64 Z\",\n belly: \"M43 68 C43 57 77 57 77 68 C77 82 70 88 60 88 C50 88 43 82 43 68 Z\",\n wings: [\n { d: pigeonWing, pivot: [36, 58] },\n { d: mirror(pigeonWing), pivot: [84, 58] },\n ],\n head: { cx: 60, cy: 36, r: 19 },\n details: [\"M54 18 C56 12 64 12 66 18 C63 16 57 16 54 18 Z\"],\n beak: \"M55 41 L65 41 L60 50 Z\",\n eyes: { at: [[52, 33], [68, 33]], white: 5, pupil: 2.4 },\n parcel: [\"M60 100 L71 105 L60 110 L49 105 Z\", \"M49 105 L60 110 L60 118 L49 113 Z\", \"M71 105 L60 110 L60 118 L71 113 Z\"],\n },\n /**\n * The lovebird — the inseparable that introduces two people. A plump little parrot: a short tail, a round head\n * with its coloured face (the detail colour), the white eye-rings of the species and a short hooked beak.\n */\n lovebird: {\n shadow: [60, 110, 23, 3.5],\n legs: [\"M53 96 L52 106 M48 106 L56 106\", \"M67 96 L68 106 M64 106 L72 106\"],\n back: [\"M74 90 C82 98 88 104 95 103 C91 98 86 91 82 84 Z\"],\n body: \"M32 66 C32 46 88 46 88 66 C88 90 76 100 60 100 C44 100 32 90 32 66 Z\",\n belly: \"M42 70 C42 59 78 59 78 70 C78 85 70 92 60 92 C50 92 42 85 42 70 Z\",\n wings: [\n { d: \"M33 60 C20 68 22 88 40 90 C35 80 34 70 33 60 Z\", pivot: [33, 60] },\n { d: mirror(\"M33 60 C20 68 22 88 40 90 C35 80 34 70 33 60 Z\"), pivot: [87, 60] },\n ],\n head: { cx: 60, cy: 38, r: 21 },\n details: [\"M43 40 C42 28 51 22 60 22 C69 22 78 28 77 40 C76 50 68 55 60 55 C52 55 44 50 43 40 Z\"],\n beak: \"M55 44 C55 40 65 40 65 44 C65 50 62 54 60 55 C58 54 55 50 55 44 Z\",\n eyes: { at: [[51, 36], [69, 36]], white: 4.6, pupil: 2.4 },\n },\n};\n\n/** What a mood adds around the bird: a badge, a thought, an empty tray. */\nexport const MOOD_PARTS = {\n /** The badge of `success` and `error`, top right. */\n badge: { cx: 98, cy: 22, r: 11 },\n check: \"M92.5 22.5 L96.8 26.6 L103.6 18.4\",\n bang: { line: \"M98 15.5 L98 23.5\", dot: [98, 28] as Point },\n /** `thinking`: three dots rising to the right of the head. */\n dots: [\n [88, 26, 2.6],\n [96, 19, 3.2],\n [105, 11, 3.8],\n ] as const,\n /** `empty`: an open tray beside the bird. */\n tray: \"M90 96 L110 96 L107 108 L93 108 Z\",\n /** `success`: two sparkles. */\n sparkles: [\"M16 26 L18 31 L23 33 L18 35 L16 40 L14 35 L9 33 L14 31 Z\", \"M104 52 L105.2 55 L108 56 L105.2 57 L104 60 L102.8 57 L100 56 L102.8 55 Z\"],\n} as const;\n\n/** The happy eye of `success`: an arch. */\nexport const archEye = ([x, y]: Point, r: number) => `M${x - r} ${y + r * 0.45} Q${x} ${y - r * 1.15} ${x + r} ${y + r * 0.45}`;\n/** The lowered lid of `empty`: a line across the eye. */\nexport const lidEye = ([x, y]: Point, r: number) => `M${x - r} ${y + 0.4} L${x + r} ${y + 0.4}`;\n/** The heavy lid of `empty` over an eye with a white: its upper half. */\nexport const upperLid = ([x, y]: Point, r: number) => `M${x - r} ${y} A${r} ${r} 0 0 1 ${x + r} ${y} Z`;\n/** The worried brow of `error`, above one eye (`side` -1 left, 1 right). */\nexport const brow = ([x, y]: Point, r: number, side: number) =>\n `M${x - r * 1.1} ${y - r * 1.25 - (side < 0 ? 0 : r * 0.7)} L${x + r * 1.1} ${y - r * 1.5 - (side < 0 ? r * 0.7 : 0)}`;\n\nexport interface MascotPalette {\n /** Top and bottom of the body gradient. */\n readonly from: string;\n readonly to: string;\n /** Wings and details (tufts, crest). */\n readonly wing: string;\n readonly detail: string;\n /** Beak and feet. */\n readonly beak: string;\n /** The pupils (and the falcon's visor lights). */\n readonly eye: string;\n}\n\n/** The signature palettes of the Krizaka story (`tone=\"own\"`), from the first drawings of each bird. */\nexport const OWN_PALETTES: Readonly<Record<MascotBird, MascotPalette>> = {\n falcon: { from: \"#ff9f00\", to: \"#ff2a00\", wing: \"#d84315\", detail: \"#b4320d\", beak: \"#ffb300\", eye: \"#00c8d4\" },\n owl: { from: \"#a78bfa\", to: \"#6d28d9\", wing: \"#7c3aed\", detail: \"#5b21b6\", beak: \"#fbbf24\", eye: \"#1f2937\" },\n duck: { from: \"#f43f5e\", to: \"#be123c\", wing: \"#db2777\", detail: \"#9f1239\", beak: \"#fbbf24\", eye: \"#1f2937\" },\n flamingo: { from: \"#f9a8d4\", to: \"#db2777\", wing: \"#ec4899\", detail: \"#be185d\", beak: \"#fbbf24\", eye: \"#1f2937\" },\n pigeon: { from: \"#22d3ee\", to: \"#6366f1\", wing: \"#a855f7\", detail: \"#4338ca\", beak: \"#f59e0b\", eye: \"#1f2937\" },\n lovebird: { from: \"#6ee7b7\", to: \"#059669\", wing: \"#047857\", detail: \"#fb7185\", beak: \"#f97316\", eye: \"#1f2937\" },\n};\n\n/** Fixed light and shade, laid at a low opacity over a role (they read on both themes). */\nexport const LIGHT = \"#ffffff\";\nexport const SHADE = \"#000000\";\n/** The parcel's faces (pigeon). */\nexport const PARCEL = [\"#f59e0b\", \"#d97706\", \"#b45309\"] as const;\n"
235
310
  },
236
311
  {
237
312
  "path": "mascots/index.ts",
238
- "content": "/** @krizaka/ui/mascots — `Mascot`, the Krizaka flock (five birds, five moods). Client: an id per instance. */\nexport { MASCOT_BIRDS, MASCOT_MOODS, type MascotBird, type MascotMood, type MascotTone } from \"./geometry\";\nexport { Mascot, type MascotProps } from \"./mascot\";\n"
313
+ "content": "/** @krizaka/ui/mascots — `Mascot`, the Krizaka flock (six birds, five moods), and `Phoenix`, the comeback. Client: an id per instance. */\nexport { MASCOT_BIRDS, MASCOT_MOODS, type MascotBird, type MascotMood, type MascotTone } from \"./geometry\";\nexport { Mascot, type MascotProps } from \"./mascot\";\nexport { Phoenix, type PhoenixProps } from \"./phoenix\";\n"
239
314
  },
240
315
  {
241
316
  "path": "mascots/mascot.tsx",
242
- "content": "// The Krizaka flock on the web: one bird, one mood, drawn from ./geometry in the host's brand colours.\nimport type * as React from \"react\";\nimport { useId } from \"react\";\n\nimport { archEye, BIRDS, brow, lidEye, LIGHT, type MascotBird, type MascotMood, type MascotPalette, type MascotTone, MOOD_PARTS, OWN_PALETTES, PARCEL, SHADE, upperLid } from \"./geometry\";\n\nconst CSS = `\n.kzb-bird { transform-box: view-box; transform-origin: 60px 108px; }\n.kzb-eye { transform-box: fill-box; transform-origin: center; }\n.kzb-wing { transform-box: view-box; }\n[data-animated] .kzb-bird { animation: kzb-bob 3.6s ease-in-out infinite; }\n[data-animated] .kzb-blink { animation: kzb-blink 5.2s ease-in-out infinite; }\n[data-animated][data-mood=\"welcome\"] .kzb-wing-r { animation: kzb-wave 1.1s ease-in-out infinite alternate; }\n[data-animated][data-mood=\"thinking\"] .kzb-dot { animation: kzb-dot 1.5s ease-in-out infinite; }\n[data-animated][data-mood=\"thinking\"] .kzb-dot:nth-child(2) { animation-delay: .2s; }\n[data-animated][data-mood=\"thinking\"] .kzb-dot:nth-child(3) { animation-delay: .4s; }\n[data-animated][data-mood=\"success\"] .kzb-bird { animation: kzb-hop 1.6s cubic-bezier(.3,1.6,.5,1) 1, kzb-bob 3.6s ease-in-out 1.6s infinite; }\n[data-animated][data-mood=\"success\"] .kzb-badge { animation: kzb-pop .5s cubic-bezier(.3,1.6,.5,1) .3s both; }\n[data-animated][data-mood=\"success\"] .kzb-sparkle { animation: kzb-twinkle 2.4s ease-in-out infinite; }\n[data-animated][data-mood=\"error\"] .kzb-bird { animation: kzb-shake .5s ease-in-out .2s 2, kzb-bob 3.6s ease-in-out 1.2s infinite; }\n[data-animated][data-mood=\"empty\"] .kzb-bird { animation: kzb-sway 5s ease-in-out infinite; }\n.kzb-badge, .kzb-sparkle, .kzb-dot { transform-box: fill-box; transform-origin: center; }\n@keyframes kzb-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.5px); } }\n@keyframes kzb-blink { 0%, 92%, 100% { transform: scaleY(1); } 95% { transform: scaleY(.1); } }\n@keyframes kzb-wave { from { transform: rotate(-8deg); } to { transform: rotate(-38deg); } }\n@keyframes kzb-dot { 0%, 100% { opacity: .35; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-2px); } }\n@keyframes kzb-hop { 0% { transform: translateY(0); } 30% { transform: translateY(-9px); } 60% { transform: translateY(0); } 100% { transform: translateY(0); } }\n@keyframes kzb-pop { from { transform: scale(0); } to { transform: scale(1); } }\n@keyframes kzb-twinkle { 0%, 100% { opacity: .25; transform: scale(.7); } 50% { opacity: 1; transform: scale(1); } }\n@keyframes kzb-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }\n@keyframes kzb-sway { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(-2deg); } }\n@media (prefers-reduced-motion: reduce) {\n .kzb-bird, .kzb-blink, .kzb-wing-r, .kzb-dot, .kzb-badge, .kzb-sparkle { animation: none !important; }\n}\n`;\n\n/** The host's roles (`tone=\"brand\"`): the accent paints the bird, so it wears the product it is shown in. */\nconst BRAND: MascotPalette = {\n from: \"var(--kz-accent-2, var(--kz-accent))\",\n to: \"var(--kz-accent)\",\n wing: \"var(--kz-accent)\",\n detail: \"var(--kz-accent-2, var(--kz-accent))\",\n beak: \"var(--kz-warning)\",\n eye: \"var(--kz-text-primary)\",\n};\nconst OUTLINE = \"var(--kz-border-strong, color-mix(in srgb, currentColor 35%, transparent))\";\nconst SURFACE = \"var(--kz-surface-0)\";\n\nexport type MascotProps = Omit<React.ComponentProps<\"svg\">, \"children\"> & {\n /** Which bird of the flock: `falcon` (watch, security), `owl` (reading, documents), `duck` (building, code), `flamingo` (answers, support), `pigeon` (delivery, notifications). */\n bird?: MascotBird;\n /** What the moment is: `welcome` (onboarding), `thinking` (a long task running), `success` (it is done), `empty` (nothing here yet), `error` (it failed). */\n mood?: MascotMood;\n /** `brand` (default): the host's accent, so the bird wears the product it is in. `own`: the bird's signature colours of the Krizaka story. */\n tone?: MascotTone;\n /** Width and height in pixels. */\n size?: number;\n /** A light idle motion and the mood's gesture. Off with `false`; always off under `prefers-reduced-motion`. */\n animated?: boolean;\n /** An accessible name (the bird becomes an image); without it the mascot is decorative. */\n title?: string;\n};\n\n/**\n * One of the five Krizaka birds in one mood — for the moments a product speaks to a person: a welcome, a long wait,\n * a success, an empty list, an error. Drawn in the host's brand colours by default (`tone=\"own\"` for the story's).\n */\nexport function Mascot({ bird = \"owl\", mood = \"welcome\", tone = \"brand\", size = 96, animated = true, title, className, ...props }: MascotProps) {\n const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n const g = BIRDS[bird];\n const p = tone === \"own\" ? OWN_PALETTES[bird] : BRAND;\n const body = `url(#kzb-${uid})`;\n const line = { stroke: OUTLINE, strokeWidth: 1.4, strokeLinejoin: \"round\" as const };\n const eyeR = g.eyes.white || g.eyes.pupil * 1.2;\n const eyes = g.eyes.at.map((at, i) => {\n const look = mood === \"thinking\" ? 1.4 : 0;\n if (mood === \"success\") return <path key={i} d={archEye(at, eyeR * 0.85)} stroke={g.visor ? p.eye : OUTLINE} strokeWidth={2.2} strokeLinecap=\"round\" fill=\"none\" />;\n if (mood === \"empty\" && g.eyes.white > 0)\n return (\n <g key={i}>\n <circle cx={at[0]} cy={at[1]} r={g.eyes.white} fill={LIGHT} {...line} />\n <circle cx={at[0]} cy={at[1] + g.eyes.pupil * 0.7} r={g.eyes.pupil} fill={SHADE} />\n <path d={upperLid(at, g.eyes.white)} fill={p.detail} {...line} />\n </g>\n );\n if (mood === \"empty\") return <path key={i} d={lidEye(at, eyeR * 0.8)} stroke={g.visor ? p.eye : OUTLINE} strokeWidth={2.2} strokeLinecap=\"round\" fill=\"none\" />;\n return (\n <g key={i} className=\"kzb-eye kzb-blink\">\n {g.eyes.white > 0 && <circle cx={at[0]} cy={at[1]} r={g.eyes.white} fill={LIGHT} {...line} />}\n <circle cx={at[0] + look} cy={at[1] - look} r={g.eyes.pupil} fill={g.visor ? p.eye : SHADE} />\n {g.eyes.white > 0 && <circle cx={at[0] + look - g.eyes.pupil * 0.35} cy={at[1] - look - g.eyes.pupil * 0.35} r={g.eyes.pupil * 0.35} fill={LIGHT} />}\n </g>\n );\n });\n const [wl, wr] = g.wings;\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 120 120\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n data-bird={bird}\n data-mood={mood}\n data-animated={animated ? \"\" : undefined}\n className={className}\n {...(title ? { role: \"img\", \"aria-label\": title } : { \"aria-hidden\": true })}\n {...props}\n >\n <style>{CSS}</style>\n <defs>\n <linearGradient id={`kzb-${uid}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" style={{ stopColor: p.from }} />\n <stop offset=\"100%\" style={{ stopColor: p.to }} />\n </linearGradient>\n </defs>\n <ellipse cx={g.shadow[0]} cy={g.shadow[1]} rx={g.shadow[2]} ry={g.shadow[3]} fill={SHADE} opacity={0.14} />\n {mood === \"empty\" && <path d={MOOD_PARTS.tray} stroke={OUTLINE} strokeWidth={1.6} strokeDasharray=\"3 3\" strokeLinejoin=\"round\" />}\n <g className=\"kzb-bird\">\n {g.legs.map((d) => (\n <path key={d} d={d} stroke={p.beak} strokeWidth={2.6} strokeLinecap=\"round\" />\n ))}\n {g.back?.map((d) => (d.endsWith(\"Z\") ? <path key={d} d={d} fill={p.detail} {...line} /> : <path key={d} d={d} stroke={OUTLINE} strokeWidth={1.4} />))}\n {g.parcel && (\n <g {...line}>\n {g.parcel.map((d, i) => (\n <path key={d} d={d} fill={PARCEL[i]} />\n ))}\n </g>\n )}\n <path d={g.body} fill={body} {...line} />\n <path d={g.belly} fill={LIGHT} opacity={0.28} />\n {[wl, wr].map((w, i) => {\n const turn = mood === \"success\" ? (i === 0 ? 28 : -28) : mood === \"welcome\" && i === 1 ? -20 : 0;\n return (\n <g key={i} className={i === 1 ? \"kzb-wing kzb-wing-r\" : \"kzb-wing\"} style={{ transformOrigin: `${w.pivot[0]}px ${w.pivot[1]}px`, transform: turn ? `rotate(${turn}deg)` : undefined }}>\n <path d={w.d} fill={p.wing} {...line} />\n <path d={w.d} fill={SHADE} opacity={0.16} />\n </g>\n );\n })}\n {g.neck && <path d={g.neck.d} stroke={body} strokeWidth={g.neck.width} strokeLinecap=\"round\" />}\n {g.head.r > 0 && <circle cx={g.head.cx} cy={g.head.cy} r={g.head.r} fill={body} {...line} />}\n {g.details.map((d) => (\n <path key={d} d={d} fill={p.detail} {...line} />\n ))}\n {g.visor && <rect x={g.visor.x} y={g.visor.y} width={g.visor.w} height={g.visor.h} rx={4} fill={SURFACE} {...line} />}\n {eyes}\n {g.glasses && (\n <g stroke={OUTLINE} strokeWidth={1.3}>\n {g.eyes.at.map(([x, y]) => (\n <circle key={x} cx={x} cy={y} r={6.5} fill={LIGHT} fillOpacity={0.12} />\n ))}\n <path d={`M${g.eyes.at[0][0] + 6.5} ${g.eyes.at[0][1]} L${g.eyes.at[1][0] - 6.5} ${g.eyes.at[1][1]}`} />\n </g>\n )}\n {mood === \"error\" &&\n g.eyes.at.map((at, i) => <path key={i} d={brow(at, eyeR, g.eyes.at.length === 1 ? 1 : i === 0 ? -1 : 1)} stroke={OUTLINE} strokeWidth={2} strokeLinecap=\"round\" />)}\n <path d={g.beak} fill={p.beak} {...line} />\n </g>\n {mood === \"thinking\" && (\n <g>\n {MOOD_PARTS.dots.map(([cx, cy, r]) => (\n <circle key={cx} className=\"kzb-dot\" cx={cx} cy={cy} r={r} fill=\"var(--kz-text-secondary)\" />\n ))}\n </g>\n )}\n {mood === \"success\" && (\n <>\n {MOOD_PARTS.sparkles.map((d) => (\n <path key={d} className=\"kzb-sparkle\" d={d} fill={p.from} />\n ))}\n <g className=\"kzb-badge\">\n <circle cx={MOOD_PARTS.badge.cx} cy={MOOD_PARTS.badge.cy} r={MOOD_PARTS.badge.r} fill=\"var(--kz-success)\" />\n <path d={MOOD_PARTS.check} stroke={SURFACE} strokeWidth={2.6} strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </g>\n </>\n )}\n {mood === \"error\" && (\n <g className=\"kzb-badge\">\n <circle cx={MOOD_PARTS.badge.cx} cy={MOOD_PARTS.badge.cy} r={MOOD_PARTS.badge.r} fill=\"var(--kz-danger)\" />\n <path d={MOOD_PARTS.bang.line} stroke={SURFACE} strokeWidth={2.8} strokeLinecap=\"round\" />\n <circle cx={MOOD_PARTS.bang.dot[0]} cy={MOOD_PARTS.bang.dot[1]} r={1.6} fill={SURFACE} />\n </g>\n )}\n </svg>\n );\n}\n"
317
+ "content": "// The Krizaka flock on the web: one bird, one mood, drawn from ./geometry in the host's brand colours.\nimport type * as React from \"react\";\nimport { useId } from \"react\";\n\nimport { archEye, BIRDS, brow, lidEye, LIGHT, type MascotBird, type MascotMood, type MascotPalette, type MascotTone, MOOD_PARTS, OWN_PALETTES, PARCEL, SHADE, upperLid } from \"./geometry\";\n\nconst CSS = `\n.kzb-bird { transform-box: view-box; transform-origin: 60px 108px; }\n.kzb-eye { transform-box: fill-box; transform-origin: center; }\n.kzb-wing { transform-box: view-box; }\n[data-animated] .kzb-bird { animation: kzb-bob 3.6s ease-in-out infinite; }\n[data-animated] .kzb-blink { animation: kzb-blink 5.2s ease-in-out infinite; }\n[data-animated][data-mood=\"welcome\"] .kzb-wing-r { animation: kzb-wave 1.1s ease-in-out infinite alternate; }\n[data-animated][data-mood=\"thinking\"] .kzb-dot { animation: kzb-dot 1.5s ease-in-out infinite; }\n[data-animated][data-mood=\"thinking\"] .kzb-dot:nth-child(2) { animation-delay: .2s; }\n[data-animated][data-mood=\"thinking\"] .kzb-dot:nth-child(3) { animation-delay: .4s; }\n[data-animated][data-mood=\"success\"] .kzb-bird { animation: kzb-hop 1.6s cubic-bezier(.3,1.6,.5,1) 1, kzb-bob 3.6s ease-in-out 1.6s infinite; }\n[data-animated][data-mood=\"success\"] .kzb-badge { animation: kzb-pop .5s cubic-bezier(.3,1.6,.5,1) .3s both; }\n[data-animated][data-mood=\"success\"] .kzb-sparkle { animation: kzb-twinkle 2.4s ease-in-out infinite; }\n[data-animated][data-mood=\"error\"] .kzb-bird { animation: kzb-shake .5s ease-in-out .2s 2, kzb-bob 3.6s ease-in-out 1.2s infinite; }\n[data-animated][data-mood=\"empty\"] .kzb-bird { animation: kzb-sway 5s ease-in-out infinite; }\n.kzb-badge, .kzb-sparkle, .kzb-dot { transform-box: fill-box; transform-origin: center; }\n@keyframes kzb-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.5px); } }\n@keyframes kzb-blink { 0%, 92%, 100% { transform: scaleY(1); } 95% { transform: scaleY(.1); } }\n@keyframes kzb-wave { from { transform: rotate(-8deg); } to { transform: rotate(-38deg); } }\n@keyframes kzb-dot { 0%, 100% { opacity: .35; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-2px); } }\n@keyframes kzb-hop { 0% { transform: translateY(0); } 30% { transform: translateY(-9px); } 60% { transform: translateY(0); } 100% { transform: translateY(0); } }\n@keyframes kzb-pop { from { transform: scale(0); } to { transform: scale(1); } }\n@keyframes kzb-twinkle { 0%, 100% { opacity: .25; transform: scale(.7); } 50% { opacity: 1; transform: scale(1); } }\n@keyframes kzb-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }\n@keyframes kzb-sway { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(-2deg); } }\n@media (prefers-reduced-motion: reduce) {\n .kzb-bird, .kzb-blink, .kzb-wing-r, .kzb-dot, .kzb-badge, .kzb-sparkle { animation: none !important; }\n}\n`;\n\n/** The host's roles (`tone=\"brand\"`): the accent paints the bird, so it wears the product it is shown in. */\nconst BRAND: MascotPalette = {\n from: \"var(--kz-accent-2, var(--kz-accent))\",\n to: \"var(--kz-accent)\",\n wing: \"var(--kz-accent)\",\n detail: \"var(--kz-accent-2, var(--kz-accent))\",\n beak: \"var(--kz-warning)\",\n eye: \"var(--kz-text-primary)\",\n};\nconst OUTLINE = \"var(--kz-border-strong, color-mix(in srgb, currentColor 35%, transparent))\";\nconst SURFACE = \"var(--kz-surface-0)\";\n\nexport type MascotProps = Omit<React.ComponentProps<\"svg\">, \"children\"> & {\n /** Which bird of the flock: `falcon` (watch, security), `owl` (reading, documents), `duck` (building, code), `flamingo` (answers, support), `pigeon` (delivery, notifications), `lovebird` (introductions, two people who meet). */\n bird?: MascotBird;\n /** What the moment is: `welcome` (onboarding), `thinking` (a long task running), `success` (it is done), `empty` (nothing here yet), `error` (it failed). */\n mood?: MascotMood;\n /** `brand` (default): the host's accent, so the bird wears the product it is in. `own`: the bird's signature colours of the Krizaka story. */\n tone?: MascotTone;\n /** Width and height in pixels. */\n size?: number;\n /** A light idle motion and the mood's gesture. Off with `false`; always off under `prefers-reduced-motion`. */\n animated?: boolean;\n /** An accessible name (the bird becomes an image); without it the mascot is decorative. */\n title?: string;\n};\n\n/**\n * One of the six Krizaka birds in one mood — for the moments a product speaks to a person: a welcome, a long wait,\n * a success, an empty list, an error. Drawn in the host's brand colours by default (`tone=\"own\"` for the story's).\n */\nexport function Mascot({ bird = \"owl\", mood = \"welcome\", tone = \"brand\", size = 96, animated = true, title, className, ...props }: MascotProps) {\n const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n const g = BIRDS[bird];\n const p = tone === \"own\" ? OWN_PALETTES[bird] : BRAND;\n const body = `url(#kzb-${uid})`;\n const line = { stroke: OUTLINE, strokeWidth: 1.4, strokeLinejoin: \"round\" as const };\n const eyeR = g.eyes.white || g.eyes.pupil * 1.2;\n const eyes = g.eyes.at.map((at, i) => {\n const look = mood === \"thinking\" ? 1.4 : 0;\n if (mood === \"success\") return <path key={i} d={archEye(at, eyeR * 0.85)} stroke={g.visor ? p.eye : OUTLINE} strokeWidth={2.2} strokeLinecap=\"round\" fill=\"none\" />;\n if (mood === \"empty\" && g.eyes.white > 0)\n return (\n <g key={i}>\n <circle cx={at[0]} cy={at[1]} r={g.eyes.white} fill={LIGHT} {...line} />\n <circle cx={at[0]} cy={at[1] + g.eyes.pupil * 0.7} r={g.eyes.pupil} fill={SHADE} />\n <path d={upperLid(at, g.eyes.white)} fill={p.detail} {...line} />\n </g>\n );\n if (mood === \"empty\") return <path key={i} d={lidEye(at, eyeR * 0.8)} stroke={g.visor ? p.eye : OUTLINE} strokeWidth={2.2} strokeLinecap=\"round\" fill=\"none\" />;\n return (\n <g key={i} className=\"kzb-eye kzb-blink\">\n {g.eyes.white > 0 && <circle cx={at[0]} cy={at[1]} r={g.eyes.white} fill={LIGHT} {...line} />}\n <circle cx={at[0] + look} cy={at[1] - look} r={g.eyes.pupil} fill={g.visor ? p.eye : SHADE} />\n {g.eyes.white > 0 && <circle cx={at[0] + look - g.eyes.pupil * 0.35} cy={at[1] - look - g.eyes.pupil * 0.35} r={g.eyes.pupil * 0.35} fill={LIGHT} />}\n </g>\n );\n });\n const [wl, wr] = g.wings;\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 120 120\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n data-bird={bird}\n data-mood={mood}\n data-animated={animated ? \"\" : undefined}\n className={className}\n {...(title ? { role: \"img\", \"aria-label\": title } : { \"aria-hidden\": true })}\n {...props}\n >\n <style>{CSS}</style>\n <defs>\n <linearGradient id={`kzb-${uid}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" style={{ stopColor: p.from }} />\n <stop offset=\"100%\" style={{ stopColor: p.to }} />\n </linearGradient>\n </defs>\n <ellipse cx={g.shadow[0]} cy={g.shadow[1]} rx={g.shadow[2]} ry={g.shadow[3]} fill={SHADE} opacity={0.14} />\n {mood === \"empty\" && <path d={MOOD_PARTS.tray} stroke={OUTLINE} strokeWidth={1.6} strokeDasharray=\"3 3\" strokeLinejoin=\"round\" />}\n <g className=\"kzb-bird\">\n {g.legs.map((d) => (\n <path key={d} d={d} stroke={p.beak} strokeWidth={2.6} strokeLinecap=\"round\" />\n ))}\n {g.back?.map((d) => (d.endsWith(\"Z\") ? <path key={d} d={d} fill={p.detail} {...line} /> : <path key={d} d={d} stroke={OUTLINE} strokeWidth={1.4} />))}\n {g.parcel && (\n <g {...line}>\n {g.parcel.map((d, i) => (\n <path key={d} d={d} fill={PARCEL[i]} />\n ))}\n </g>\n )}\n <path d={g.body} fill={body} {...line} />\n <path d={g.belly} fill={LIGHT} opacity={0.28} />\n {[wl, wr].map((w, i) => {\n const turn = mood === \"success\" ? (i === 0 ? 28 : -28) : mood === \"welcome\" && i === 1 ? -20 : 0;\n return (\n <g key={i} className={i === 1 ? \"kzb-wing kzb-wing-r\" : \"kzb-wing\"} style={{ transformOrigin: `${w.pivot[0]}px ${w.pivot[1]}px`, transform: turn ? `rotate(${turn}deg)` : undefined }}>\n <path d={w.d} fill={p.wing} {...line} />\n <path d={w.d} fill={SHADE} opacity={0.16} />\n </g>\n );\n })}\n {g.neck && <path d={g.neck.d} stroke={body} strokeWidth={g.neck.width} strokeLinecap=\"round\" />}\n {g.head.r > 0 && <circle cx={g.head.cx} cy={g.head.cy} r={g.head.r} fill={body} {...line} />}\n {g.details.map((d) => (\n <path key={d} d={d} fill={p.detail} {...line} />\n ))}\n {g.visor && <rect x={g.visor.x} y={g.visor.y} width={g.visor.w} height={g.visor.h} rx={4} fill={SURFACE} {...line} />}\n {eyes}\n {g.glasses && (\n <g stroke={OUTLINE} strokeWidth={1.3}>\n {g.eyes.at.map(([x, y]) => (\n <circle key={x} cx={x} cy={y} r={6.5} fill={LIGHT} fillOpacity={0.12} />\n ))}\n <path d={`M${g.eyes.at[0][0] + 6.5} ${g.eyes.at[0][1]} L${g.eyes.at[1][0] - 6.5} ${g.eyes.at[1][1]}`} />\n </g>\n )}\n {mood === \"error\" &&\n g.eyes.at.map((at, i) => <path key={i} d={brow(at, eyeR, g.eyes.at.length === 1 ? 1 : i === 0 ? -1 : 1)} stroke={OUTLINE} strokeWidth={2} strokeLinecap=\"round\" />)}\n <path d={g.beak} fill={p.beak} {...line} />\n </g>\n {mood === \"thinking\" && (\n <g>\n {MOOD_PARTS.dots.map(([cx, cy, r]) => (\n <circle key={cx} className=\"kzb-dot\" cx={cx} cy={cy} r={r} fill=\"var(--kz-text-secondary)\" />\n ))}\n </g>\n )}\n {mood === \"success\" && (\n <>\n {MOOD_PARTS.sparkles.map((d) => (\n <path key={d} className=\"kzb-sparkle\" d={d} fill={p.from} />\n ))}\n <g className=\"kzb-badge\">\n <circle cx={MOOD_PARTS.badge.cx} cy={MOOD_PARTS.badge.cy} r={MOOD_PARTS.badge.r} fill=\"var(--kz-success)\" />\n <path d={MOOD_PARTS.check} stroke={SURFACE} strokeWidth={2.6} strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </g>\n </>\n )}\n {mood === \"error\" && (\n <g className=\"kzb-badge\">\n <circle cx={MOOD_PARTS.badge.cx} cy={MOOD_PARTS.badge.cy} r={MOOD_PARTS.badge.r} fill=\"var(--kz-danger)\" />\n <path d={MOOD_PARTS.bang.line} stroke={SURFACE} strokeWidth={2.8} strokeLinecap=\"round\" />\n <circle cx={MOOD_PARTS.bang.dot[0]} cy={MOOD_PARTS.bang.dot[1]} r={1.6} fill={SURFACE} />\n </g>\n )}\n </svg>\n );\n}\n"
243
318
  },
244
319
  {
245
320
  "path": "mascots/meta.ts",
246
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Mascots\",\n summary: \"The Krizaka flock: five birds (falcon, owl, duck, flamingo, pigeon) in five moods, drawn in the product's brand colours.\",\n status: \"beta\",\n category: \"feedback\",\n platforms: \"both\",\n whenToUse: [\n \"To welcome a person on a first screen or an onboarding step (`welcome`).\",\n \"While a long task runs and the person waits for it (`thinking`), beside its progress.\",\n \"When a long task has just finished well (`success`): a generation, an import, a verification.\",\n \"In an empty state the person reaches for the first time (`empty`), above its title.\",\n \"On a full-page or panel error, beside what went wrong and what to do (`error`).\",\n ],\n whenNotToUse: [\n { when: \"In the middle of working content (a list row, a toolbar, a form): a mascot is for a moment, not decoration.\", use: \"badge\" },\n { when: \"For a short wait of a second or two.\", use: \"spinner\" },\n { when: \"To announce a result that needs no celebration.\", use: \"toast\" },\n ],\n bestPractices: [\n \"One mascot per screen, and only at a moment that matters: a welcome, a wait, a success, an empty list, an error.\",\n \"Keep the bird's role: the owl reads (documents), the falcon watches (security), the duck builds (code), the flamingo answers (support), the pigeon delivers (notifications, packages).\",\n \"Leave `tone=\\\"brand\\\"` in a product: the bird wears the product's accent; `own` is for the Krizaka story.\",\n \"The words stay beside it (a title, a line, an action): the mascot never carries the message alone.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"Decorative by default (`aria-hidden`): the text next to it carries the meaning. With `title` it becomes an image with that name.\",\n \"Every gesture (idle bob, blink, wave, dots, hop) stops under `prefers-reduced-motion`, and with `animated={false}`.\",\n \"The success and error badges are not the only signal: say it in words beside the bird.\",\n ],\n },\n related: [\"empty-state\", \"progress\", \"alert\", \"spinner\"],\n web: {\n imports: [\"Mascot\", \"MASCOT_BIRDS\", \"MASCOT_MOODS\"],\n examples: [\n { name: \"moods\", title: \"Moods\", description: \"One bird in its five moods: welcome, thinking, success, empty, error.\" },\n { name: \"flock\", title: \"The flock\", description: \"The five birds in the brand colours of the host.\" },\n { name: \"own-tone\", title: \"Signature colours\", description: \"`tone=\\\"own\\\"`: each bird in its colours of the Krizaka story.\" },\n { name: \"empty-state\", title: \"In an empty state\", description: \"The owl above an empty list, the words and the action beside it.\" },\n { name: \"long-task\", title: \"During a long task\", description: \"The pigeon thinking beside the progress of a job.\" },\n ],\n },\n native: {\n imports: [\"Mascot\"],\n differences: [\"Only the idle bob and the badges: the wave, the dots and the hop are web gestures.\", \"Style with `style`, not `className`.\"],\n examples: [{ name: \"default\", title: \"Default\", description: \"The owl welcoming, the falcon done, the duck with nothing yet.\" }],\n },\n} satisfies ComponentMeta;\n"
321
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Mascots\",\n summary: \"The Krizaka flock: six birds (falcon, owl, duck, flamingo, pigeon, lovebird) in five moods, drawn in the product's brand colours — and the phoenix, the comeback.\",\n status: \"beta\",\n category: \"feedback\",\n platforms: \"both\",\n whenToUse: [\n \"To welcome a person on a first screen or an onboarding step (`welcome`).\",\n \"While a long task runs and the person waits for it (`thinking`), beside its progress.\",\n \"When a long task has just finished well (`success`): a generation, an import, a verification.\",\n \"In an empty state the person reaches for the first time (`empty`), above its title.\",\n \"On a full-page or panel error, beside what went wrong and what to do (`error`).\",\n ],\n whenNotToUse: [\n { when: \"In the middle of working content (a list row, a toolbar, a form): a mascot is for a moment, not decoration.\", use: \"badge\" },\n { when: \"For a short wait of a second or two.\", use: \"spinner\" },\n { when: \"To announce a result that needs no celebration.\", use: \"toast\" },\n ],\n bestPractices: [\n \"One mascot per screen, and only at a moment that matters: a welcome, a wait, a success, an empty list, an error.\",\n \"Keep the bird's role: the owl reads (documents), the falcon watches (security), the duck builds (code), the flamingo answers (support), the pigeon delivers (notifications, packages), the lovebird introduces (matches, two people who meet).\",\n \"The phoenix (`Phoenix`) does no job: keep it for a comeback — a relaunch, a recovered account, a second chance — never as one more helper.\",\n \"Leave `tone=\\\"brand\\\"` in a product: the bird wears the product's accent; `own` is for the Krizaka story.\",\n \"The words stay beside it (a title, a line, an action): the mascot never carries the message alone.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"Decorative by default (`aria-hidden`): the text next to it carries the meaning. With `title` it becomes an image with that name.\",\n \"Every gesture (idle bob, blink, wave, dots, hop) stops under `prefers-reduced-motion`, and with `animated={false}`.\",\n \"The success and error badges are not the only signal: say it in words beside the bird.\",\n ],\n },\n related: [\"empty-state\", \"progress\", \"alert\", \"spinner\"],\n web: {\n imports: [\"Mascot\", \"MASCOT_BIRDS\", \"MASCOT_MOODS\", \"Phoenix\"],\n examples: [\n { name: \"moods\", title: \"Moods\", description: \"One bird in its five moods: welcome, thinking, success, empty, error.\" },\n { name: \"flock\", title: \"The flock\", description: \"The six birds in the brand colours of the host.\" },\n { name: \"own-tone\", title: \"Signature colours\", description: \"`tone=\\\"own\\\"`: each bird in its colours of the Krizaka story.\" },\n { name: \"empty-state\", title: \"In an empty state\", description: \"The owl above an empty list, the words and the action beside it.\" },\n { name: \"long-task\", title: \"During a long task\", description: \"The pigeon thinking beside the progress of a job.\" },\n { name: \"lovebird\", title: \"An introduction\", description: \"The lovebird celebrates a mutual interest: two people who chose each other meet.\" },\n { name: \"phoenix\", title: \"The phoenix\", description: \"The sixth figure: a comeback — a relaunch, a recovered account — in its own colours or the brand's.\" },\n ],\n },\n native: {\n imports: [\"Mascot\", \"Phoenix\"],\n differences: [\"Only the idle bob and the badges: the wave, the dots and the hop are web gestures.\", \"Style with `style`, not `className`.\"],\n examples: [\n { name: \"default\", title: \"Default\", description: \"The owl welcoming, the falcon done, the duck with nothing yet.\" },\n { name: \"lovebird\", title: \"The lovebird\", description: \"The lovebird welcoming, then celebrating a mutual interest.\" },\n { name: \"phoenix\", title: \"The phoenix\", description: \"The comeback, in its own colours and in the app's accent.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
322
+ },
323
+ {
324
+ "path": "mascots/phoenix-geometry.ts",
325
+ "content": "/** The phoenix's geometry (120 × 120), drawn by the web `Phoenix` and the native one. */\nexport const PHOENIX = {\n wingL: \"M56 62 C40 40 22 36 8 40 C22 46 30 56 34 70 C40 64 48 62 56 62 Z\",\n wingR: \"M64 62 C80 40 98 36 112 40 C98 46 90 56 86 70 C80 64 72 62 64 62 Z\",\n tailL: \"M52 66 C46 82 40 94 30 104 C44 98 52 90 58 78 Z\",\n tailR: \"M68 66 C74 82 80 94 90 104 C76 98 68 90 62 78 Z\",\n body: \"M60 34 L68 60 L60 82 L52 60 Z\",\n crest: \"M60 34 L56 24 M60 34 L64 24\",\n} as const;\n\nexport interface PhoenixPalette {\n /** The left wing's gradient (top → bottom). */\n readonly left: readonly [string, string];\n /** The right wing's gradient. */\n readonly right: readonly [string, string];\n /** The core's glow: centre, edge. */\n readonly core: readonly [string, string];\n /** The body and its edge. */\n readonly body: string;\n readonly edge: string;\n /** The crest. */\n readonly crest: string;\n}\n\n/** The phoenix of the Krizaka story: a violet wing, a gold wing (the brand layer's own colours, like the marks). */\nexport const PHOENIX_PALETTES: { readonly own: PhoenixPalette } = {\n own: {\n left: [\"#c084fc\", \"#7c3aed\"],\n right: [\"#fde68a\", \"#f59e0b\"],\n core: [\"#fff7ed\", \"#fb923c\"],\n body: \"#1f1530\",\n edge: \"#e9d5ff\",\n crest: \"#fbbf24\",\n },\n};\n"
326
+ },
327
+ {
328
+ "path": "mascots/phoenix.tsx",
329
+ "content": "// The phoenix of the Krizaka landscape, on its own: two wings — two rival flames — around one bright core. The sixth\n// figure of the flock: it does no job, it stands for the comeback (a relaunch, a recovered account, a second chance).\nimport type * as React from \"react\";\nimport { useId } from \"react\";\n\nimport { PHOENIX, PHOENIX_PALETTES,type PhoenixPalette } from \"./phoenix-geometry\";\n\nconst CSS = `\n@keyframes kzp-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }\n@keyframes kzp-core { 0%, 100% { opacity: .8; } 50% { opacity: 1; } }\n.kzp[data-animated] { animation: kzp-float 5s ease-in-out infinite; }\n.kzp[data-animated] .kzp-core { animation: kzp-core 2.8s ease-in-out infinite; }\n@media (prefers-reduced-motion: reduce) { .kzp, .kzp .kzp-core { animation: none !important; } }\n`;\n\nexport type PhoenixProps = Omit<React.ComponentProps<\"svg\">, \"children\"> & {\n /** `own` (default): the violet wing and the gold wing of the Krizaka story. `brand`: the host's accent and accent-2. */\n tone?: \"own\" | \"brand\";\n /** Width and height in pixels. */\n size?: number;\n /** A slow float and a breathing core. Off with `false`; always off under `prefers-reduced-motion`. */\n animated?: boolean;\n /** An accessible name (the phoenix becomes an image); without it, it is decorative. */\n title?: string;\n};\n\nconst BRAND: PhoenixPalette = {\n left: [\"var(--kz-accent-2, var(--kz-accent))\", \"var(--kz-accent)\"],\n right: [\"var(--kz-accent)\", \"var(--kz-accent-2, var(--kz-accent))\"],\n core: [\"#ffffff\", \"var(--kz-accent)\"],\n body: \"var(--kz-surface-3)\",\n edge: \"var(--kz-border-strong)\",\n crest: \"var(--kz-accent)\",\n};\n\n/** The phoenix: the comeback, beside the five birds of the flock (`Mascot`). Decorative unless titled. */\nexport function Phoenix({ tone = \"own\", size = 120, animated = true, title, className, ...props }: PhoenixProps) {\n const id = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n const p = tone === \"brand\" ? BRAND : PHOENIX_PALETTES.own;\n const a11y = title ? { role: \"img\", \"aria-label\": title } : { \"aria-hidden\": true };\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 120 120\"\n fill=\"none\"\n data-animated={animated ? \"\" : undefined}\n className={[\"kzp\", className].filter(Boolean).join(\" \")}\n {...a11y}\n {...props}\n >\n <style>{CSS}</style>\n <defs>\n <linearGradient id={`kzp-l-${id}`} x1=\"1\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={p.left[0]} />\n <stop offset=\"100%\" stopColor={p.left[1]} />\n </linearGradient>\n <linearGradient id={`kzp-r-${id}`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n <stop offset=\"0%\" stopColor={p.right[0]} />\n <stop offset=\"100%\" stopColor={p.right[1]} />\n </linearGradient>\n <radialGradient id={`kzp-c-${id}`}>\n <stop offset=\"0%\" stopColor={p.core[0]} />\n <stop offset=\"100%\" stopColor={p.core[1]} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n <path d={PHOENIX.wingL} fill={`url(#kzp-l-${id})`} />\n <path d={PHOENIX.wingR} fill={`url(#kzp-r-${id})`} />\n <path d={PHOENIX.tailL} fill={`url(#kzp-l-${id})`} opacity=\".7\" />\n <path d={PHOENIX.tailR} fill={`url(#kzp-r-${id})`} opacity=\".7\" />\n <path d={PHOENIX.body} fill={p.body} stroke={p.edge} strokeOpacity=\".5\" />\n <circle cx=\"60\" cy=\"58\" r=\"12\" fill={`url(#kzp-c-${id})`} className=\"kzp-core\" />\n <circle cx=\"60\" cy=\"58\" r=\"3.5\" fill=\"#fff\" />\n <path d={PHOENIX.crest} stroke={p.crest} strokeWidth=\"2\" strokeLinecap=\"round\" />\n </svg>\n );\n}\n"
247
330
  },
248
331
  {
249
332
  "path": "meta.ts",
250
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which krizaka.com renders as\n// /docs/ui/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"action-bar\"\n | \"alert\"\n | \"argument-card\"\n | \"audio-player\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"confirm-button\"\n | \"countdown\"\n | \"cta-panel\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"dialog\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"glow-card\"\n | \"image-cropper\"\n | \"iso\"\n | \"kbd\"\n | \"link-card\"\n | \"loader\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascots\"\n | \"media-capture\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"pointer-light\"\n | \"popover\"\n | \"progress\"\n | \"radio-group\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"skeleton\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"stat\"\n | \"stepper\"\n | \"switch\"\n | \"tabs\"\n | \"tag-input\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"txt\"\n | \"video-player\"\n | \"video-trimmer\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/** `stable`: its API only changes in a major. `beta`: it may still change in a minor. */\nexport type Status = \"stable\" | \"beta\";\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layout\" | \"foundations\";\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\nexport interface Example {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
333
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which krizaka.com renders as\n// /docs/ui/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"confirm-button\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"image-cropper\"\n | \"iso\"\n | \"kbd\"\n | \"landscape\"\n | \"link-card\"\n | \"loader\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"media-capture\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"pointer-light\"\n | \"popover\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"radio-group\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"skeleton\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"stepper\"\n | \"switch\"\n | \"tabs\"\n | \"tag-input\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"txt\"\n | \"video-player\"\n | \"video-trimmer\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/** `stable`: its API only changes in a major. `beta`: it may still change in a minor. */\nexport type Status = \"stable\" | \"beta\";\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its templates.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"layout\" | \"foundations\";\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\nexport interface Example {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its phone\n * version thought out), copied as a starting point — at most 160 lines, listed on krizaka.com/docs/ui/templates.\n */\n readonly template?: boolean;\n}\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
251
334
  }
252
335
  ],
253
336
  "dependencies": [],
@@ -259,12 +342,12 @@
259
342
  "props": [
260
343
  {
261
344
  "component": "MASCOT_BIRDS",
262
- "description": "The Krizaka flock — the geometry of the mascots, drawn by the web `Mascot` (`./mascot.tsx`) and the native one\n(`../native/mascots.tsx`). Five birds that have carried the Krizaka story since the first site (the sentry falcon,\nthe owl that reads, the developer duck, the flamingo that answers, the carrier pigeon), redrawn on one 120 × 120\ngrid so that any bird can wear any mood.\n\nColours are roles, never fixed: `brand` (the default) paints a bird in the host's accent (`--kz-accent`,\n`--kz-accent-2`), so the same falcon is blue on krizaka.com, orange in Orazaka and violet in Orochia; `own` gives\neach bird the signature palette it has in the Krizaka story (the brand layer's exception, like the marks). Shades\nand highlights are black and white at a low opacity over the role, so they hold in both themes.",
345
+ "description": "The Krizaka flock — the geometry of the mascots, drawn by the web `Mascot` (`./mascot.tsx`) and the native one\n(`../native/mascots.tsx`). Five birds that have carried the Krizaka story since the first site (the sentry falcon,\nthe owl that reads, the developer duck, the flamingo that answers, the carrier pigeon) and the lovebird that\nintroduces (2026-10, with Yazakia: \"l'inséparable présente\"), redrawn on one 120 × 120 grid so that any bird can\nwear any mood.\n\nColours are roles, never fixed: `brand` (the default) paints a bird in the host's accent (`--kz-accent`,\n`--kz-accent-2`), so the same falcon is blue on krizaka.com, orange in Orazaka and violet in Orochia; `own` gives\neach bird the signature palette it has in the Krizaka story (the brand layer's exception, like the marks). Shades\nand highlights are black and white at a low opacity over the role, so they hold in both themes.",
263
346
  "props": []
264
347
  },
265
348
  {
266
349
  "component": "Mascot",
267
- "description": "One of the five Krizaka birds in one mood — for the moments a product speaks to a person: a welcome, a long wait,\na success, an empty list, an error. Drawn in the host's brand colours by default (`tone=\"own\"` for the story's).",
350
+ "description": "One of the six Krizaka birds in one mood — for the moments a product speaks to a person: a welcome, a long wait,\na success, an empty list, an error. Drawn in the host's brand colours by default (`tone=\"own\"` for the story's).",
268
351
  "props": [
269
352
  {
270
353
  "name": "animated",
@@ -275,9 +358,9 @@
275
358
  },
276
359
  {
277
360
  "name": "bird",
278
- "type": "\"falcon\" | \"owl\" | \"duck\" | \"flamingo\" | \"pigeon\"",
361
+ "type": "\"falcon\" | \"owl\" | \"duck\" | \"flamingo\" | \"pigeon\" | \"lovebird\"",
279
362
  "default": "owl",
280
- "description": "Which bird of the flock: `falcon` (watch, security), `owl` (reading, documents), `duck` (building, code), `flamingo` (answers, support), `pigeon` (delivery, notifications).",
363
+ "description": "Which bird of the flock: `falcon` (watch, security), `owl` (reading, documents), `duck` (building, code), `flamingo` (answers, support), `pigeon` (delivery, notifications), `lovebird` (introductions, two people who meet).",
281
364
  "required": false
282
365
  },
283
366
  {
@@ -309,6 +392,40 @@
309
392
  "required": false
310
393
  }
311
394
  ]
395
+ },
396
+ {
397
+ "component": "Phoenix",
398
+ "description": "The phoenix: the comeback, beside the five birds of the flock (`Mascot`). Decorative unless titled.",
399
+ "props": [
400
+ {
401
+ "name": "animated",
402
+ "type": "boolean",
403
+ "default": "true",
404
+ "description": "A slow float and a breathing core. Off with `false`; always off under `prefers-reduced-motion`.",
405
+ "required": false
406
+ },
407
+ {
408
+ "name": "size",
409
+ "type": "number",
410
+ "default": "120",
411
+ "description": "Width and height in pixels.",
412
+ "required": false
413
+ },
414
+ {
415
+ "name": "title",
416
+ "type": "string",
417
+ "default": null,
418
+ "description": "An accessible name (the phoenix becomes an image); without it, it is decorative.",
419
+ "required": false
420
+ },
421
+ {
422
+ "name": "tone",
423
+ "type": "\"brand\" | \"own\"",
424
+ "default": "own",
425
+ "description": "`own` (default): the violet wing and the gold wing of the Krizaka story. `brand`: the host's accent and accent-2.",
426
+ "required": false
427
+ }
428
+ ]
312
429
  }
313
430
  ]
314
431
  }
@@ -255,7 +255,7 @@
255
255
  },
256
256
  {
257
257
  "path": "meta.ts",
258
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which krizaka.com renders as\n// /docs/ui/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"action-bar\"\n | \"alert\"\n | \"argument-card\"\n | \"audio-player\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"confirm-button\"\n | \"countdown\"\n | \"cta-panel\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"dialog\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"glow-card\"\n | \"image-cropper\"\n | \"iso\"\n | \"kbd\"\n | \"link-card\"\n | \"loader\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascots\"\n | \"media-capture\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"pointer-light\"\n | \"popover\"\n | \"progress\"\n | \"radio-group\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"skeleton\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"stat\"\n | \"stepper\"\n | \"switch\"\n | \"tabs\"\n | \"tag-input\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"txt\"\n | \"video-player\"\n | \"video-trimmer\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/** `stable`: its API only changes in a major. `beta`: it may still change in a minor. */\nexport type Status = \"stable\" | \"beta\";\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layout\" | \"foundations\";\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\nexport interface Example {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
258
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which krizaka.com renders as\n// /docs/ui/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"confirm-button\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"image-cropper\"\n | \"iso\"\n | \"kbd\"\n | \"landscape\"\n | \"link-card\"\n | \"loader\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"media-capture\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"pointer-light\"\n | \"popover\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"radio-group\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"skeleton\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"stepper\"\n | \"switch\"\n | \"tabs\"\n | \"tag-input\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"txt\"\n | \"video-player\"\n | \"video-trimmer\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/** `stable`: its API only changes in a major. `beta`: it may still change in a minor. */\nexport type Status = \"stable\" | \"beta\";\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its templates.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"layout\" | \"foundations\";\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\nexport interface Example {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its phone\n * version thought out), copied as a starting point — at most 160 lines, listed on krizaka.com/docs/ui/templates.\n */\n readonly template?: boolean;\n}\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
259
259
  }
260
260
  ],
261
261
  "dependencies": [
@@ -186,7 +186,7 @@
186
186
  "files": [
187
187
  {
188
188
  "path": "meta.ts",
189
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which krizaka.com renders as\n// /docs/ui/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"action-bar\"\n | \"alert\"\n | \"argument-card\"\n | \"audio-player\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"confirm-button\"\n | \"countdown\"\n | \"cta-panel\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"dialog\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"glow-card\"\n | \"image-cropper\"\n | \"iso\"\n | \"kbd\"\n | \"link-card\"\n | \"loader\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascots\"\n | \"media-capture\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"pointer-light\"\n | \"popover\"\n | \"progress\"\n | \"radio-group\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"skeleton\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"stat\"\n | \"stepper\"\n | \"switch\"\n | \"tabs\"\n | \"tag-input\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"txt\"\n | \"video-player\"\n | \"video-trimmer\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/** `stable`: its API only changes in a major. `beta`: it may still change in a minor. */\nexport type Status = \"stable\" | \"beta\";\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layout\" | \"foundations\";\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\nexport interface Example {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
189
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which krizaka.com renders as\n// /docs/ui/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"confirm-button\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"image-cropper\"\n | \"iso\"\n | \"kbd\"\n | \"landscape\"\n | \"link-card\"\n | \"loader\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"media-capture\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"pointer-light\"\n | \"popover\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"radio-group\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"skeleton\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"stepper\"\n | \"switch\"\n | \"tabs\"\n | \"tag-input\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"txt\"\n | \"video-player\"\n | \"video-trimmer\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/** `stable`: its API only changes in a major. `beta`: it may still change in a minor. */\nexport type Status = \"stable\" | \"beta\";\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its templates.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"layout\" | \"foundations\";\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\nexport interface Example {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its phone\n * version thought out), copied as a starting point — at most 160 lines, listed on krizaka.com/docs/ui/templates.\n */\n readonly template?: boolean;\n}\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
190
190
  },
191
191
  {
192
192
  "path": "meter/index.ts",