@krizaka/ui 3.20.0 → 3.22.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 (263) hide show
  1. package/README.md +1 -0
  2. package/dist/action-bar.js +4 -4
  3. package/dist/app-shell.js +1 -1
  4. package/dist/argument-card.js +4 -4
  5. package/dist/blocks.css +1 -1
  6. package/dist/button.js +3 -2
  7. package/dist/card.js +3 -3
  8. package/dist/{chunk-5MH64NIL.js → chunk-2L67YTNV.js} +2 -2
  9. package/dist/{chunk-2NB27LM7.js → chunk-3IN6TOPM.js} +4 -4
  10. package/dist/{chunk-VDR2EP6K.js → chunk-4QN5YJ3D.js} +17 -5
  11. package/dist/chunk-4QN5YJ3D.js.map +1 -0
  12. package/dist/chunk-AQSZJJAJ.js +126 -0
  13. package/dist/chunk-AQSZJJAJ.js.map +1 -0
  14. package/dist/{chunk-3GODX3D6.js → chunk-QDXV544C.js} +2 -2
  15. package/dist/chunk-QDXV544C.js.map +1 -0
  16. package/dist/chunk-R6NK6ZNY.js +1080 -0
  17. package/dist/chunk-R6NK6ZNY.js.map +1 -0
  18. package/dist/chunk-TDG7ED2W.js +1 -0
  19. package/dist/chunk-TDG7ED2W.js.map +1 -0
  20. package/dist/{chunk-LIPK4UCS.js → chunk-YCIBJ6OQ.js} +140 -209
  21. package/dist/chunk-YCIBJ6OQ.js.map +1 -0
  22. package/dist/code-block.js +17 -5
  23. package/dist/code-block.js.map +1 -1
  24. package/dist/comparison-table.d.ts +44 -0
  25. package/dist/comparison-table.js +84 -0
  26. package/dist/comparison-table.js.map +1 -0
  27. package/dist/data-table.js +19 -1066
  28. package/dist/data-table.js.map +1 -1
  29. package/dist/date-picker.js +5 -5
  30. package/dist/date-picker.js.map +1 -1
  31. package/dist/force-update-screen.d.ts +80 -0
  32. package/dist/force-update-screen.js +38 -0
  33. package/dist/force-update-screen.js.map +1 -0
  34. package/dist/genui.d.ts +219 -0
  35. package/dist/genui.js +506 -0
  36. package/dist/genui.js.map +1 -0
  37. package/dist/index.d.ts +17 -24
  38. package/dist/index.js +1 -1
  39. package/dist/maintenance-screen.d.ts +68 -0
  40. package/dist/maintenance-screen.js +35 -0
  41. package/dist/maintenance-screen.js.map +1 -0
  42. package/dist/marketing-layout.js +1 -1
  43. package/dist/marketing-layout.js.map +1 -1
  44. package/dist/media-grid.js +44 -152
  45. package/dist/media-grid.js.map +1 -1
  46. package/dist/meta-row.d.ts +54 -0
  47. package/dist/meta-row.js +35 -0
  48. package/dist/meta-row.js.map +1 -0
  49. package/dist/native.d.ts +92 -16
  50. package/dist/native.js +873 -814
  51. package/dist/native.js.map +1 -1
  52. package/dist/nav-bar.d.ts +30 -7
  53. package/dist/nav-bar.js +71 -17
  54. package/dist/nav-bar.js.map +1 -1
  55. package/dist/page-hero.js +5 -5
  56. package/dist/radio-group.js +1 -1
  57. package/dist/settings-layout.js +1 -1
  58. package/dist/settings-layout.js.map +1 -1
  59. package/dist/site-footer.js +2 -2
  60. package/dist/skip-link.js +1 -1
  61. package/dist/step-showcase.d.ts +62 -0
  62. package/dist/step-showcase.js +227 -0
  63. package/dist/step-showcase.js.map +1 -0
  64. package/dist/tabs.js +2 -2
  65. package/dist/tabs.js.map +1 -1
  66. package/dist/update-prompt.d.ts +86 -0
  67. package/dist/update-prompt.js +41 -0
  68. package/dist/update-prompt.js.map +1 -0
  69. package/dist/video-player.d.ts +1 -1
  70. package/dist/video-player.js +16 -3
  71. package/dist/video-player.js.map +1 -1
  72. package/package.json +108 -9
  73. package/registry/achievement.json +1 -1
  74. package/registry/action-bar.json +1 -1
  75. package/registry/activity-feed.json +1 -1
  76. package/registry/alert.json +1 -1
  77. package/registry/app-shell.json +1 -1
  78. package/registry/argument-card.json +1 -1
  79. package/registry/audio-player.json +1 -1
  80. package/registry/auth-layout.json +1 -1
  81. package/registry/avatar.json +1 -1
  82. package/registry/badge.json +1 -1
  83. package/registry/button.json +1 -1
  84. package/registry/card-grid.json +1 -1
  85. package/registry/card.json +1 -1
  86. package/registry/catalogue-page.json +1 -1
  87. package/registry/changelog-page.json +1 -1
  88. package/registry/chat.json +1 -1
  89. package/registry/checkbox.json +1 -1
  90. package/registry/chip-link.json +1 -1
  91. package/registry/chip.json +1 -1
  92. package/registry/choice-grid.json +1 -1
  93. package/registry/code-block.json +1 -1
  94. package/registry/combobox.json +1 -1
  95. package/registry/command.json +1 -1
  96. package/registry/comparison-table.json +207 -0
  97. package/registry/component-page.json +1 -1
  98. package/registry/confirm-button.json +1 -1
  99. package/registry/contact-page.json +1 -1
  100. package/registry/continue-prompt.json +1 -1
  101. package/registry/countdown.json +1 -1
  102. package/registry/cta-panel.json +1 -1
  103. package/registry/dashboard-layout.json +1 -1
  104. package/registry/data-table.json +1 -1
  105. package/registry/date-picker.json +3 -3
  106. package/registry/decision-list.json +1 -1
  107. package/registry/description-list.json +1 -1
  108. package/registry/detail-layout.json +1 -1
  109. package/registry/dialog.json +1 -1
  110. package/registry/docs-article-page.json +1 -1
  111. package/registry/docs-hub-page.json +1 -1
  112. package/registry/docs-layout.json +1 -1
  113. package/registry/document-viewer.json +1 -1
  114. package/registry/dropdown-menu.json +1 -1
  115. package/registry/empty-state.json +1 -1
  116. package/registry/error-page.json +1 -1
  117. package/registry/examples/comparison-table/versus.d.ts +4 -0
  118. package/registry/examples/comparison-table/versus.js +28 -0
  119. package/registry/examples/comparison-table/versus.tsx +20 -0
  120. package/registry/examples/dialog/native/sheet.dark.png +0 -0
  121. package/registry/examples/dialog/native/sheet.light.png +0 -0
  122. package/registry/examples/force-update-screen/default.d.ts +4 -0
  123. package/registry/examples/force-update-screen/default.js +25 -0
  124. package/registry/examples/force-update-screen/default.tsx +19 -0
  125. package/registry/examples/force-update-screen/native/default.d.ts +4 -0
  126. package/registry/examples/force-update-screen/native/default.dark.png +0 -0
  127. package/registry/examples/force-update-screen/native/default.js +24 -0
  128. package/registry/examples/force-update-screen/native/default.light.png +0 -0
  129. package/registry/examples/force-update-screen/native/default.tsx +18 -0
  130. package/registry/examples/genui/streaming.d.ts +4 -0
  131. package/registry/examples/genui/streaming.js +18 -0
  132. package/registry/examples/genui/streaming.tsx +14 -0
  133. package/registry/examples/genui/table.d.ts +4 -0
  134. package/registry/examples/genui/table.js +19 -0
  135. package/registry/examples/genui/table.tsx +14 -0
  136. package/registry/examples/maintenance-screen/default.d.ts +4 -0
  137. package/registry/examples/maintenance-screen/default.js +22 -0
  138. package/registry/examples/maintenance-screen/default.tsx +16 -0
  139. package/registry/examples/maintenance-screen/native/default.d.ts +4 -0
  140. package/registry/examples/maintenance-screen/native/default.dark.png +0 -0
  141. package/registry/examples/maintenance-screen/native/default.js +21 -0
  142. package/registry/examples/maintenance-screen/native/default.light.png +0 -0
  143. package/registry/examples/maintenance-screen/native/default.tsx +15 -0
  144. package/registry/examples/meta-row/native/video.d.ts +4 -0
  145. package/registry/examples/meta-row/native/video.dark.png +0 -0
  146. package/registry/examples/meta-row/native/video.js +11 -0
  147. package/registry/examples/meta-row/native/video.light.png +0 -0
  148. package/registry/examples/meta-row/native/video.tsx +5 -0
  149. package/registry/examples/meta-row/status.d.ts +4 -0
  150. package/registry/examples/meta-row/status.js +12 -0
  151. package/registry/examples/meta-row/status.tsx +7 -0
  152. package/registry/examples/meta-row/video.d.ts +4 -0
  153. package/registry/examples/meta-row/video.js +12 -0
  154. package/registry/examples/meta-row/video.tsx +7 -0
  155. package/registry/examples/nav-bar/family-product.js +16 -13
  156. package/registry/examples/nav-bar/family-product.tsx +19 -19
  157. package/registry/examples/step-showcase/desktop.d.ts +4 -0
  158. package/registry/examples/step-showcase/desktop.js +24 -0
  159. package/registry/examples/step-showcase/desktop.tsx +19 -0
  160. package/registry/examples/step-showcase/phone.d.ts +4 -0
  161. package/registry/examples/step-showcase/phone.js +24 -0
  162. package/registry/examples/step-showcase/phone.tsx +19 -0
  163. package/registry/examples/update-prompt/default.d.ts +4 -0
  164. package/registry/examples/update-prompt/default.js +24 -0
  165. package/registry/examples/update-prompt/default.tsx +18 -0
  166. package/registry/examples/update-prompt/native/default.d.ts +4 -0
  167. package/registry/examples/update-prompt/native/default.dark.png +0 -0
  168. package/registry/examples/update-prompt/native/default.js +23 -0
  169. package/registry/examples/update-prompt/native/default.light.png +0 -0
  170. package/registry/examples/update-prompt/native/default.tsx +17 -0
  171. package/registry/field.json +1 -1
  172. package/registry/file-upload.json +1 -1
  173. package/registry/filter-bar.json +1 -1
  174. package/registry/force-update-screen.json +323 -0
  175. package/registry/form.json +1 -1
  176. package/registry/gallery-page.json +1 -1
  177. package/registry/genui.json +409 -0
  178. package/registry/glow-card.json +1 -1
  179. package/registry/goal-gauge.json +1 -1
  180. package/registry/how-it-works-page.json +1 -1
  181. package/registry/image-cropper.json +1 -1
  182. package/registry/index.json +223 -4
  183. package/registry/iso.json +1 -1
  184. package/registry/kanban.json +1 -1
  185. package/registry/kbd.json +1 -1
  186. package/registry/landing-page.json +1 -1
  187. package/registry/landscape.json +1 -1
  188. package/registry/legal-page.json +1 -1
  189. package/registry/link-card.json +1 -1
  190. package/registry/list.json +1 -1
  191. package/registry/loader.json +1 -1
  192. package/registry/longform-page.json +1 -1
  193. package/registry/maintenance-screen.json +259 -0
  194. package/registry/map.json +2 -2
  195. package/registry/markdown.json +1 -1
  196. package/registry/marketing-layout.json +1 -1
  197. package/registry/marquee.json +1 -1
  198. package/registry/mascot-quote.json +1 -1
  199. package/registry/mascot-state.json +1 -1
  200. package/registry/mascots.json +1 -1
  201. package/registry/match-dialog.json +1 -1
  202. package/registry/media-capture.json +1 -1
  203. package/registry/media-grid.json +2 -2
  204. package/registry/meta-row.json +215 -0
  205. package/registry/meter.json +1 -1
  206. package/registry/nav-bar.json +58 -25
  207. package/registry/number-ticker.json +1 -1
  208. package/registry/page-header.json +1 -1
  209. package/registry/page-hero.json +1 -1
  210. package/registry/page.json +1 -1
  211. package/registry/pagination.json +1 -1
  212. package/registry/photo-grid.json +1 -1
  213. package/registry/pointer-light.json +1 -1
  214. package/registry/popover.json +1 -1
  215. package/registry/pricing-page.json +1 -1
  216. package/registry/product-page.json +1 -1
  217. package/registry/profile-card.json +1 -1
  218. package/registry/progress.json +1 -1
  219. package/registry/promise-card.json +1 -1
  220. package/registry/radio-group.json +2 -2
  221. package/registry/recipes.json +1 -1
  222. package/registry/scroll-stage.json +1 -1
  223. package/registry/search-field.json +1 -1
  224. package/registry/section-backdrop.json +1 -1
  225. package/registry/separator.json +1 -1
  226. package/registry/settings-layout.json +2 -2
  227. package/registry/showcase.json +1 -1
  228. package/registry/site-footer.json +13 -13
  229. package/registry/sites.json +1 -1
  230. package/registry/skeleton.json +1 -1
  231. package/registry/skip-link.json +2 -2
  232. package/registry/slider.json +1 -1
  233. package/registry/slot.json +1 -1
  234. package/registry/spinner.json +1 -1
  235. package/registry/split-view.json +1 -1
  236. package/registry/stage-banner.json +1 -1
  237. package/registry/stat.json +1 -1
  238. package/registry/status-page.json +1 -1
  239. package/registry/step-progress.json +1 -1
  240. package/registry/step-showcase.json +296 -0
  241. package/registry/stepper.json +1 -1
  242. package/registry/swipe-deck.json +1 -1
  243. package/registry/switch.json +1 -1
  244. package/registry/tabs.json +3 -3
  245. package/registry/tag-input.json +1 -1
  246. package/registry/tag-selector.json +1 -1
  247. package/registry/terminal-replay.json +1 -1
  248. package/registry/theme.json +1 -1
  249. package/registry/toast.json +1 -1
  250. package/registry/toc.json +1 -1
  251. package/registry/tooltip.json +1 -1
  252. package/registry/tree-view.json +1 -1
  253. package/registry/update-prompt.json +316 -0
  254. package/registry/verified-badge.json +1 -1
  255. package/registry/vertical-video.json +1 -1
  256. package/registry/video-player.json +4 -4
  257. package/registry/video-trimmer.json +1 -1
  258. package/registry/virtual.json +1 -1
  259. package/dist/chunk-3GODX3D6.js.map +0 -1
  260. package/dist/chunk-LIPK4UCS.js.map +0 -1
  261. package/dist/chunk-VDR2EP6K.js.map +0 -1
  262. /package/dist/{chunk-5MH64NIL.js.map → chunk-2L67YTNV.js.map} +0 -0
  263. /package/dist/{chunk-2NB27LM7.js.map → chunk-3IN6TOPM.js.map} +0 -0
@@ -216,11 +216,11 @@
216
216
  },
217
217
  {
218
218
  "path": "marks/AkinazaLogo.tsx",
219
- "content": "import { AKINAZA as G } from \"./akinaza-geometry\";\nimport { a11y, type MarkProps, neutral, useIds, withAlpha } from \"./shared\";\n\nconst CSS = `\n.akm-orbit { transform-origin: 200px 200px; animation: akm-orbit 32s linear infinite; }\n.akm-door { animation: akm-door 3.8s ease-in-out infinite; }\n@keyframes akm-orbit { to { transform: rotate(360deg); } }\n@keyframes akm-door {\n 0%, 100% { filter: drop-shadow(0 0 4px ${withAlpha(G.pulse.color, G.pulse.low)}); }\n 50% { filter: drop-shadow(0 0 14px ${withAlpha(G.pulse.color, G.pulse.high)}); }\n}\n@media (prefers-reduced-motion: reduce) { .akm-orbit, .akm-door { animation: none; } }\n`;\n\n/**\n * The Akinaza mark, \"the open shop\": a filled awning with the Krizaka cut at its corner, scalloped, its stripes in\n * light; under it the shop front, two windows and the open door — a warm light that breathes. In the Akinaza gradient\n * (forest → copper, the brand's mark gradient of @krizaka/tokens, the same in dark and light); one node, a coin, rides\n * the dashed orbit. The family's grammar (orbit, ring, bold emblem, bright core). Below 48 px it crops to the emblem.\n */\nexport function AkinazaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"akm\", [\"gradient\", \"core\", \"glow\"] as const);\n const fill = `url(#${id.gradient})`;\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <linearGradient id={id.gradient} gradientUnits=\"userSpaceOnUse\" x1={G.gradient.x1} y1={G.gradient.y1} x2={G.gradient.x2} y2={G.gradient.y2}>\n {G.gradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <linearGradient id={id.core} x1=\"50%\" y1=\"100%\" x2=\"50%\" y2=\"0%\">\n {G.coreGradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={animated ? \"akm-orbit\" : undefined}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.node.at[0]} cy={G.node.at[1]} r={G.node.r} fill={fill} />\n </g>\n <path d={G.awning} fill={fill} />\n <path d={G.stripes} stroke={G.stripe.color} strokeOpacity={G.stripe.opacity} strokeWidth={G.stripe.width} strokeLinecap=\"round\" />\n <path d={G.front} fill={fill} />\n <path d={G.windows} fill={G.stripe.color} fillOpacity={G.stripe.opacity} />\n <rect className={animated ? \"akm-door\" : undefined} x={G.door.x} y={G.door.y} width={G.door.w} height={G.door.h} rx={G.door.rx} fill={`url(#${id.core})`} />\n </svg>\n );\n}\n"
219
+ "content": "import { AKINAZA as G } from \"./akinaza-geometry\";\nimport { a11y, familyMotion, type MarkProps, neutral, useIds } from \"./shared\";\n\nconst CSS = familyMotion(\"akm\", { ...G.motion, ...G.pulse });\n\n/**\n * The Akinaza mark, \"the cut log\": a log seen end-on in forest green, its year rings in light, cut at 45° by the beaver\n * (the Krizaka chamfer) with the fresh face in copper, and a copper heart that breathes — a shop built ring by ring.\n * One node, a coin, rides the dashed orbit. Below 48 px it crops to the emblem.\n */\nexport function AkinazaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"akm\", [\"glow\"] as const);\n const m = (cls: string) => (animated ? `akm-${cls}` : undefined);\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={m(\"orbit\")}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.node.at[0]} cy={G.node.at[1]} r={G.node.r} fill={G.copper} />\n </g>\n <path d={G.log} fill={G.forest} />\n <path d={G.cut} fill={G.copper} />\n {G.rings.r.map((r) => (\n <circle key={r} cx={G.rings.cx} cy={G.rings.cy} r={r} stroke={G.highlight} strokeOpacity={G.rings.opacity} strokeWidth={G.rings.width} />\n ))}\n <g className={m(\"core\")}>\n <circle cx={G.heart.cx} cy={G.heart.cy} r={G.heart.r} fill={G.copper} />\n <circle cx={G.heart.cx} cy={G.heart.cy} r={G.heart.inner} fill={G.highlight} />\n </g>\n </svg>\n );\n}\n"
220
220
  },
221
221
  {
222
222
  "path": "marks/KachizakaLogo.tsx",
223
- "content": "import { KACHIZAKA as G } from \"./kachizaka-geometry\";\nimport { a11y, type MarkProps, neutral, useIds, withAlpha } from \"./shared\";\n\nconst CSS = `\n.kzm-orbit { transform-origin: 200px 200px; animation: kzm-orbit 28s linear infinite; }\n.kzm-seal { transform-box: fill-box; transform-origin: center; animation: kzm-seal 3.2s ease-in-out infinite; }\n@keyframes kzm-orbit { to { transform: rotate(360deg); } }\n@keyframes kzm-seal {\n 0%, 100% { transform: scale(1); filter: drop-shadow(0 0 4px ${withAlpha(G.pulse.color, G.pulse.low)}); }\n 50% { transform: scale(1.06); filter: drop-shadow(0 0 12px ${withAlpha(G.pulse.color, G.pulse.high)}); }\n}\n@media (prefers-reduced-motion: reduce) { .kzm-orbit, .kzm-seal { animation: none; } }\n`;\n\n/**\n * The Kachizaka mark, \"the sealed cup\": a filled trophy whose rim wears the Krizaka cut, two handles, a stem and a\n * base, in the Kachizaka gradient (cobalt → gold, the brand's mark gradient of @krizaka/tokens, the same in dark and\n * light); on the bowl a star in light — the seal of the rules, locked at kick-off — breathes; one node, the ball, rides\n * the dashed orbit. The family's grammar (orbit, ring, bold emblem, bright core). Below 48 px it crops to the emblem.\n */\nexport function KachizakaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"kzm\", [\"gradient\", \"glow\"] as const);\n const fill = `url(#${id.gradient})`;\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <linearGradient id={id.gradient} gradientUnits=\"userSpaceOnUse\" x1={G.gradient.x1} y1={G.gradient.y1} x2={G.gradient.x2} y2={G.gradient.y2}>\n {G.gradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={animated ? \"kzm-orbit\" : undefined}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.node.at[0]} cy={G.node.at[1]} r={G.node.r} fill={fill} />\n </g>\n <path d={G.handles} stroke={fill} strokeWidth={G.handleWidth} strokeLinecap=\"round\" />\n <path d={G.base} fill={fill} />\n <path d={G.cup} fill={fill} />\n <path className={animated ? \"kzm-seal\" : undefined} d={G.star} fill={G.starColor} />\n </svg>\n );\n}\n"
223
+ "content": "import { KACHIZAKA as G } from \"./kachizaka-geometry\";\nimport { a11y, familyMotion, type MarkProps, neutral, useIds } from \"./shared\";\n\nconst CSS = familyMotion(\"kcm\", { ...G.motion, ...G.pulse });\n\n/**\n * The Kachizaka mark, \"the podium\": three steps cut at their outer corners, the winner in gold and the others in cobalt\n * (the brand's two colours, kept apart), under a gold diamond — the family's lit node — that breathes: a friendly\n * leaderboard where only points change hands. One node, the ball, rides the dashed orbit. Below 48 px it crops to the\n * emblem.\n */\nexport function KachizakaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"kcm\", [\"glow\"] as const);\n const m = (cls: string) => (animated ? `kcm-${cls}` : undefined);\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={m(\"orbit\")}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.ball.at[0]} cy={G.ball.at[1]} r={G.ball.r} fill={G.gold} />\n </g>\n <path d={G.steps.second} fill={G.cobalt} />\n <path d={G.steps.first} fill={G.gold} />\n <path d={G.steps.third} fill={G.cobalt} />\n <g className={m(\"core\")}>\n <path d={G.node.d} fill={G.gold} stroke={G.gold} strokeWidth={G.node.soften} strokeLinejoin=\"round\" />\n <path d={G.node.light} fill={G.highlight} />\n </g>\n </svg>\n );\n}\n"
224
224
  },
225
225
  {
226
226
  "path": "marks/KrizakaLogo.tsx",
@@ -240,23 +240,23 @@
240
240
  },
241
241
  {
242
242
  "path": "marks/YasakiaLogo.tsx",
243
- "content": "import { a11y, type MarkProps, neutral, useIds, withAlpha } from \"./shared\";\nimport { YASAKIA as G } from \"./yasakia-geometry\";\n\nconst CSS = `\n.yzm-orbit { transform-origin: 200px 200px; animation: yzm-orbit 30s linear infinite; }\n.yzm-core { transform-box: fill-box; transform-origin: center; animation: yzm-core 3.2s ease-in-out infinite; }\n@keyframes yzm-orbit { to { transform: rotate(360deg); } }\n@keyframes yzm-core {\n 0%, 100% { filter: drop-shadow(0 0 6px ${withAlpha(G.pulse.color, G.pulse.low)}); opacity: .9; }\n 50% { filter: drop-shadow(0 0 14px ${withAlpha(G.pulse.color, G.pulse.high)}); opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) { .yzm-orbit, .yzm-core { animation: none; } }\n`;\n\n/**\n * The Yasakia mark: two voices — two solid speech bubbles that overlap, the left a shade deeper — in the Yasakia\n * gradient (emerald → turquoise, the brand's mark gradient of @krizaka/tokens, the same in dark and light). Where they\n * overlap, what the two share, is the bright core: a lens lit from above that breathes. The pair, two nodes on the\n * dashed orbit, turns slowly around it — the family's grammar (orbit, ring, bold emblem, bright core). Below 48 px it\n * crops to the emblem.\n */\nexport function YasakiaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"yzm\", [\"gradient\", \"light\", \"glow\"] as const);\n const fill = `url(#${id.gradient})`;\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <linearGradient id={id.gradient} gradientUnits=\"userSpaceOnUse\" x1={G.gradient.x1} y1={G.gradient.y1} x2={G.gradient.x2} y2={G.gradient.y2}>\n {G.gradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <linearGradient id={id.light} gradientUnits=\"userSpaceOnUse\" x1=\"0\" y1={G.lens.y1} x2=\"0\" y2={G.lens.y2}>\n <stop offset=\"0%\" stopColor={G.highlight.color} stopOpacity={G.highlight.from} />\n <stop offset=\"100%\" stopColor={G.highlight.color} stopOpacity={G.highlight.to} />\n </linearGradient>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={animated ? \"yzm-orbit\" : undefined}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n {G.pair.at.map(([cx, cy]) => (\n <circle key={cx} cx={cx} cy={cy} r={G.pair.r} fill={fill} />\n ))}\n </g>\n {G.voices.map((v, i) => (\n <g key={v.body}>\n <path d={v.body} fill={fill} />\n <path d={v.tail} fill={fill} />\n {i === 0 ? <path d={`${v.body} ${v.tail}`} fill={G.shade.color} opacity={G.shade.opacity} /> : null}\n </g>\n ))}\n <g className={animated ? \"yzm-core\" : undefined}>\n <path d={G.lens.d} fill={fill} />\n <path d={G.lens.d} fill={`url(#${id.light})`} />\n </g>\n </svg>\n );\n}\n\n/** @deprecated Yazakia was renamed Yasakia (2026-10): use `YasakiaLogo`. Kept for one minor version. */\nexport const YazakiaLogo = YasakiaLogo;\n"
243
+ "content": "import { a11y, familyMotion, type MarkProps, neutral, useIds } from \"./shared\";\nimport { YASAKIA as G } from \"./yasakia-geometry\";\n\nconst CSS = familyMotion(\"yzm\", { ...G.motion, ...G.pulse });\n\n/**\n * The Yasakia mark, \"one conversation\": one speech bubble with a tail on each side (turned 180°, never mirrored, so it\n * can never read as a face) in the Yasakia gradient (emerald → turquoise, the same in dark and light), and the spark\n * inside it — the family's lit node — that breathes: talk first, both ways. One node rides the dashed orbit. Below 48 px\n * it crops to the emblem.\n */\nexport function YasakiaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"yzm\", [\"gradient\", \"glow\"] as const);\n const fill = `url(#${id.gradient})`;\n const m = (cls: string) => (animated ? `yzm-${cls}` : undefined);\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <linearGradient id={id.gradient} gradientUnits=\"userSpaceOnUse\" x1={G.gradient.x1} y1={G.gradient.y1} x2={G.gradient.x2} y2={G.gradient.y2}>\n {G.gradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={m(\"orbit\")}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.node.at[0]} cy={G.node.at[1]} r={G.node.r} fill={fill} />\n </g>\n <circle cx={G.bubble.cx} cy={G.bubble.cy} r={G.bubble.r} fill={fill} />\n <path d={G.tails.join(\" \")} fill={fill} />\n <g className={m(\"core\")}>\n <path d={G.spark.d} fill={G.highlight} stroke={G.highlight} strokeWidth={G.spark.soften} strokeLinejoin=\"round\" />\n </g>\n </svg>\n );\n}\n\n/** @deprecated Yazakia was renamed Yasakia (2026-10): use `YasakiaLogo`. Kept for one minor version. */\nexport const YazakiaLogo = YasakiaLogo;\n"
244
244
  },
245
245
  {
246
246
  "path": "marks/YomizakaLogo.tsx",
247
- "content": "import { a11y, type MarkProps, neutral, useIds, withAlpha } from \"./shared\";\nimport { YOMIZAKA as G } from \"./yomizaka-geometry\";\n\nconst CSS = `\n.ymm-orbit { transform-origin: 200px 200px; animation: ymm-orbit 32s linear infinite; }\n.ymm-scan { animation: ymm-scan 3.6s cubic-bezier(.45,0,.55,1) infinite; }\n.ymm-lens { transform-box: fill-box; transform-origin: center; animation: ymm-lens 3.6s ease-in-out infinite; }\n@keyframes ymm-orbit { to { transform: rotate(360deg); } }\n@keyframes ymm-scan { 0% { transform: translateY(0); opacity: 0; } 12%, 80% { opacity: 1; } 100% { transform: translateY(${G.scan.travel}px); opacity: 0; } }\n@keyframes ymm-lens {\n 0%, 100% { filter: drop-shadow(0 0 6px ${withAlpha(G.pulse.color, G.pulse.low)}); }\n 50% { filter: drop-shadow(0 0 16px ${withAlpha(G.pulse.color, G.pulse.high)}); }\n}\n@media (prefers-reduced-motion: reduce) { .ymm-orbit, .ymm-scan, .ymm-lens { animation: none; } .ymm-scan { opacity: 0; } }\n`;\n\n/**\n * The Yomizaka mark, \"the reading\": a filled page with the Krizaka cut at its corner, three lines of text in light, and\n * a lens — the eye that reads — resting on its lower half, its handle running out of the page. Page and lens in the\n * Yomizaka gradient (ink → indigo, the brand's mark gradient of @krizaka/tokens, the same in dark and light); the\n * lens' core is a pale indigo light that breathes; a line of light scans the page from top to bottom; one node, a\n * page, rides the dashed orbit. The family's grammar (orbit, ring, bold emblem, bright core). Below 48 px it crops to\n * the emblem.\n */\nexport function YomizakaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"ymm\", [\"gradient\", \"core\", \"glow\", \"page\"] as const);\n const fill = `url(#${id.gradient})`;\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <linearGradient id={id.gradient} gradientUnits=\"userSpaceOnUse\" x1={G.gradient.x1} y1={G.gradient.y1} x2={G.gradient.x2} y2={G.gradient.y2}>\n {G.gradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <linearGradient id={id.core} x1=\"50%\" y1=\"100%\" x2=\"50%\" y2=\"0%\">\n {G.coreGradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n <clipPath id={id.page}>\n <path d={G.page} />\n </clipPath>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={animated ? \"ymm-orbit\" : undefined}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.node.at[0]} cy={G.node.at[1]} r={G.node.r} fill={fill} />\n </g>\n <path d={G.page} fill={fill} />\n <path d={G.lines} stroke={G.line.color} strokeOpacity={G.line.opacity} strokeWidth={G.line.width} strokeLinecap=\"round\" />\n {animated ? (\n <g clipPath={`url(#${id.page})`}>\n <path className=\"ymm-scan\" d={G.scan.d} stroke={G.scan.color} strokeOpacity={G.scan.opacity} strokeWidth={G.scan.width} strokeLinecap=\"round\" />\n </g>\n ) : null}\n <path d={G.handle} stroke={fill} strokeWidth={G.handleWidth} strokeLinecap=\"round\" />\n <g className={animated ? \"ymm-lens\" : undefined}>\n <circle cx={G.lens.cx} cy={G.lens.cy} r={G.lens.r} stroke={fill} strokeWidth={G.lens.width} fill={`url(#${id.core})`} />\n <circle cx={G.lens.glint.cx} cy={G.lens.glint.cy} r={G.lens.glint.r} fill={G.highlight} />\n </g>\n </svg>\n );\n}\n"
247
+ "content": "import { a11y, familyMotion, type MarkProps, neutral, useIds } from \"./shared\";\nimport { YOMIZAKA as G } from \"./yomizaka-geometry\";\n\nconst CSS = familyMotion(\"ymm\", { ...G.motion, ...G.pulse });\n\n/**\n * The Yomizaka mark, \"the watch\": the owl — two lenses at the family stroke under one calm brow, its pupils glancing\n * down the line it reads — in the Yomizaka gradient (ink → indigo, the same in dark and light). The pupils are the lit\n * cores and breathe; one node, a page, rides the dashed orbit. Below 48 px it crops to the emblem.\n */\nexport function YomizakaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"ymm\", [\"gradient\", \"glow\"] as const);\n const fill = `url(#${id.gradient})`;\n const m = (cls: string) => (animated ? `ymm-${cls}` : undefined);\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <linearGradient id={id.gradient} gradientUnits=\"userSpaceOnUse\" x1={G.gradient.x1} y1={G.gradient.y1} x2={G.gradient.x2} y2={G.gradient.y2}>\n {G.gradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={m(\"orbit\")}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.node.at[0]} cy={G.node.at[1]} r={G.node.r} fill={fill} />\n </g>\n <path d={G.brow} stroke={fill} strokeWidth={G.stroke} strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n {G.lenses.map((l) => (\n <circle key={l.cx} cx={l.cx} cy={l.cy} r={l.r} stroke={fill} strokeWidth={G.stroke} />\n ))}\n <g className={m(\"core\")}>\n {G.pupils.map((p) => (\n <g key={p.cx}>\n <circle cx={p.cx} cy={p.cy} r={p.r} fill={fill} />\n <circle cx={p.cx} cy={p.cy} r={p.inner} fill={G.highlight} />\n </g>\n ))}\n </g>\n </svg>\n );\n}\n"
248
248
  },
249
249
  {
250
250
  "path": "marks/YororisLogo.tsx",
251
- "content": "import { a11y, type MarkProps, neutral, useIds, withAlpha } from \"./shared\";\nimport { YORORIS as G } from \"./yororis-geometry\";\n\nconst CSS = `\n.yrm-orbit { transform-origin: 200px 200px; animation: yrm-orbit 36s linear infinite; }\n.yrm-vein { animation: yrm-vein 4.8s linear infinite; }\n.yrm-core { transform-box: fill-box; transform-origin: center; animation: yrm-core 4.2s ease-in-out infinite; }\n@keyframes yrm-orbit { to { transform: rotate(360deg); } }\n@keyframes yrm-vein { to { stroke-dashoffset: 64; } }\n@keyframes yrm-core {\n 0%, 100% { filter: drop-shadow(0 0 6px ${withAlpha(G.pulse.color, G.pulse.low)}); opacity: .9; }\n 50% { filter: drop-shadow(0 0 16px ${withAlpha(G.pulse.color, G.pulse.high)}); opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) { .yrm-orbit, .yrm-vein, .yrm-core { animation: none; } }\n`;\n\n/**\n * The Yororis mark, \"the hold\": a \"Y\" whose arms are two filled leaves — open hands — rising from one stem and cupping\n * a warm core that breathes: held, given room, listened to. Leaves in the Yororis gradient (sand → sage, the brand's\n * mark gradient of @krizaka/tokens, the same in dark and light), a vein of light along each, the core in a warm sand\n * light; the moment, one node on the dashed orbit, comes round slowly. The family's grammar (orbit, ring, bold emblem,\n * bright core) with the slowest breath of the family. Below 48 px it crops to the emblem.\n */\nexport function YororisLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"yrm\", [\"gradient\", \"core\", \"glow\"] as const);\n const fill = `url(#${id.gradient})`;\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <linearGradient id={id.gradient} gradientUnits=\"userSpaceOnUse\" x1={G.gradient.x1} y1={G.gradient.y1} x2={G.gradient.x2} y2={G.gradient.y2}>\n {G.gradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <linearGradient id={id.core} x1=\"50%\" y1=\"100%\" x2=\"50%\" y2=\"0%\">\n {G.coreGradient.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={animated ? \"yrm-orbit\" : undefined}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.moment.at[0]} cy={G.moment.at[1]} r={G.moment.r} fill={fill} />\n </g>\n <path d={G.stem} stroke={fill} strokeWidth={G.stroke} strokeLinecap=\"round\" />\n {G.leaves.map((d) => (\n <path key={d} d={d} fill={fill} />\n ))}\n <path className={animated ? \"yrm-vein\" : undefined} d={G.veins} stroke={G.vein.color} strokeOpacity={G.vein.opacity} strokeWidth={G.vein.width} strokeLinecap=\"round\" strokeDasharray={G.vein.dash} />\n <g className={animated ? \"yrm-core\" : undefined}>\n <circle cx={G.core.cx} cy={G.core.cy} r={G.core.r} fill={`url(#${id.core})`} />\n <circle cx={G.core.cx} cy={G.core.cy} r={G.core.inner} fill={G.highlight} />\n </g>\n </svg>\n );\n}\n"
251
+ "content": "import { a11y, familyMotion, type MarkProps, neutral, useIds } from \"./shared\";\nimport { YORORIS as G } from \"./yororis-geometry\";\n\nconst CSS = familyMotion(\"yrm\", { ...G.motion, ...G.pulse });\n\n/**\n * The Yororis mark, \"held\": two open hands, two sage arcs of one circle, cup a warm sand core without closing on it —\n * held, given room, free to go. The core breathes, the slowest breath of the family; one node, the moment, rides the\n * dashed orbit. Below 48 px it crops to the emblem.\n */\nexport function YororisLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"yrm\", [\"glow\"] as const);\n const m = (cls: string) => (animated ? `yrm-${cls}` : undefined);\n return (\n <svg width={size} height={size} viewBox={size < 48 ? G.croppedViewBox : G.viewBox} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" className={className} {...a11y(title)}>\n <style>{CSS}</style>\n <defs>\n <radialGradient id={id.glow} cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stopColor={G.glow.color} stopOpacity={G.glow.opacity} />\n <stop offset=\"100%\" stopColor={G.glow.color} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n {size >= 48 ? <circle cx={G.center} cy={G.center} r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle cx={G.center} cy={G.center} r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g className={m(\"orbit\")}>\n <circle cx={G.center} cy={G.center} r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx={G.node.at[0]} cy={G.node.at[1]} r={G.node.r} fill={G.sage} />\n </g>\n <path d={G.hands} stroke={G.sage} strokeWidth={G.stroke} strokeLinecap=\"round\" />\n <g className={m(\"core\")}>\n <circle cx={G.core.cx} cy={G.core.cy} r={G.core.r} fill={G.sand} />\n <circle cx={G.core.cx} cy={G.core.cy} r={G.core.inner} fill={G.highlight} />\n </g>\n </svg>\n );\n}\n"
252
252
  },
253
253
  {
254
254
  "path": "marks/akinaza-geometry.ts",
255
- "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Akinaza mark's geometry — drawn by the web mark (AkinazaLogo) and the native one (AkinazaMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold filled emblem in the\n * brand gradient, a bright core; below 48 px the view box crops to the emblem.\n *\n * The emblem is **the open shop** (商い, *akinai*: trade): a filled awning whose upper-right corner wears the Krizaka\n * cut, scalloped below, its stripes in light; under it the shop front and its open door — the bright core, a warm\n * light that breathes (the shop is open). The gradient is the brand's mark gradient (`marks.akinaza` of\n * @krizaka/tokens): forest at the foot → copper at the awning, mid-tones that read ≥ 3:1 on the page surfaces of both\n * themes (tested in @krizaka/tokens). One node, a coin, rides the dashed orbit.\n */\n/** The copper end of the gradient: the halo. */\nconst copper = marks.akinaza[marks.akinaza.length - 1]![1];\n\nexport const AKINAZA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n /** The awning: 124 → 276 at y 108, cut 28 at its upper-right corner, four scallops below (y 150, 19 deep). */\n awning: \"M124 108 H248 L276 136 V150 A19 19 0 0 1 238 150 A19 19 0 0 1 200 150 A19 19 0 0 1 162 150 A19 19 0 0 1 124 150 Z\",\n /** The awning's stripes, in light. */\n stripes: \"M162 116 V146 M200 116 V146 M238 116 V140\",\n stripe: { color: \"#fff7ed\", opacity: 0.55, width: 8 },\n /** The shop front: walls 140 → 260 from y 178 to the ground, the door cut out (178 → 222, from y 222). */\n front: \"M140 178 H260 V300 H222 V222 H178 V300 H140 Z\",\n /** The open door: the bright core, a warm light. */\n door: { x: 182, y: 226, w: 36, h: 74, rx: 4 },\n coreGradient: { stops: [[\"0%\", \"#fde7c7\"], [\"100%\", \"#fffaf2\"]] as const },\n /** A window each side, in light. */\n windows: \"M154 196 H168 V212 H154 Z M232 196 H246 V212 H232 Z\",\n gradient: { x1: 140, y1: 300, x2: 276, y2: 108, stops: marks.akinaza },\n /** A coin: one node on the orbit, that turns with it. */\n node: { r: 9, at: [200, 40] as const },\n glow: { color: copper, opacity: 0.16 },\n /** The door's breathing light (web), in the copper. */\n pulse: { color: copper, low: 0.3, high: 0.65 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
255
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Akinaza mark's geometry — drawn by the web mark (AkinazaLogo) and the native one (AkinazaMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold solid emblem, a lit core;\n * below 48 px the view box crops to the emblem.\n *\n * The emblem is **the cut log** (2026-10-17): a log seen end-on, its year rings in light, cut at 45° by the beaver —\n * the cut is the Krizaka chamfer, its fresh face in copper — and a copper heart that breathes: a shop built ring by\n * ring, year after year. Forest and copper stay **apart** (blended they turn brown): they are the two stops of the\n * brand's mark gradient (`marks.akinaza` of @krizaka/tokens), each ≥ 3:1 on the page surfaces of both themes.\n *\n * Literal geometry (no clip, so web and native draw the same thing): the log is a circle r 96 centred (194, 206) — set\n * off the centre so the cut log sits optically centred — minus the segment beyond the 45° chord at 70 from its centre;\n * the copper face is the band between that chord and a parallel one at 58, bounded by the circle.\n */\nconst forest = marks.akinaza[0]![1];\n/** The copper end: the cut, the heart, the halo and its breath. */\nconst copper = marks.akinaza[marks.akinaza.length - 1]![1];\n\nexport const AKINAZA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n forest,\n copper,\n log: \"M197 110 L290 203 A96 96 0 1 1 197 110 Z\",\n /** The fresh face of the cut: a copper band along the chord. */\n cut: \"M197 110 L290 203 A96 96 0 0 1 289.1 219.1 L180.9 110.9 A96 96 0 0 1 197 110 Z\",\n /** The year rings, in light, inside the band. */\n rings: { cx: 194, cy: 206, r: [52, 28], width: 7, opacity: 0.42 },\n heart: { cx: 194, cy: 206, r: 16, inner: 5.4 },\n highlight: \"#fff7ed\",\n gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks.akinaza },\n /** A coin: one node on the orbit, that turns with it. */\n node: { r: 9, at: [200, 40] as const },\n glow: { color: copper, opacity: 0.16 },\n pulse: { color: copper, low: 0.3, high: 0.65 },\n motion: { spin: 32, breath: 3.8 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
256
256
  },
257
257
  {
258
258
  "path": "marks/kachizaka-geometry.ts",
259
- "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Kachizaka mark's geometry — drawn by the web mark (KachizakaLogo) and the native one (KachizakaMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold filled emblem in the\n * brand gradient, a bright core; below 48 px the view box crops to the emblem.\n *\n * The emblem is **the sealed cup**: a filled trophy whose rim wears the Krizaka cut at its upper-right corner, two\n * handles, a stem and a base; on the bowl a star in light — the seal, the rules locked at kick-off — breathes. The\n * gradient is the brand's mark gradient (`marks.kachizaka` of @krizaka/tokens): cobalt at the base → gold at the rim,\n * mid-tones that read ≥ 3:1 on the page surfaces of both themes (tested in @krizaka/tokens), so one mark serves dark\n * and light. One node, the ball, rides the dashed orbit.\n */\n/** The gold end of the gradient: the halo and the seal's breath. */\nconst gold = marks.kachizaka[marks.kachizaka.length - 1]![1];\n\nexport const KACHIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n /** The bowl: a rim 136 → 264 at y 104, cut 32 at its upper-right corner (the Krizaka chamfer), rounding down to the stem. */\n cup: \"M136 104 H232 L264 136 V164 C264 214 236 242 200 246 C164 242 136 214 136 164 Z\",\n /** The stem and the base, one shape. */\n base: \"M190 244 H210 V272 H240 C244 272 248 276 248 280 V300 H152 V280 C152 276 156 272 160 272 H190 Z\",\n /** The two handles, stroked in the gradient. */\n handles: \"M138 128 C100 124 100 196 150 202 M262 128 C300 124 300 196 250 202\",\n handleWidth: 14,\n /** The seal: a five-pointed star in light on the bowl, centred (200, 172), radii 30 / 13. */\n star: \"M200 142 L207.6 161.5 L228.5 162.7 L212.4 176 L217.6 196.3 L200 185 L182.4 196.3 L187.6 176 L171.5 162.7 L192.4 161.5 Z\",\n starColor: \"#fffbea\",\n gradient: { x1: 136, y1: 300, x2: 264, y2: 104, stops: marks.kachizaka },\n /** The ball: one node on the orbit, that turns with it. */\n node: { r: 9, at: [200, 40] as const },\n glow: { color: gold, opacity: 0.18 },\n /** The seal's breathing shadow (web), in the gold. */\n pulse: { color: gold, low: 0.35, high: 0.75 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
259
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Kachizaka mark's geometry — drawn by the web mark (KachizakaLogo) and the native one (KachizakaMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold solid emblem, a lit core;\n * below 48 px the view box crops to the emblem.\n *\n * The emblem is **the podium** (2026-10-17): three steps, their outer corners wearing the Krizaka cut, the winner in\n * gold and the others in cobalt, under the family's lit node (a diamond) that breathes — a friendly leaderboard where\n * only points change hands. The two colours of the brand stay **apart** (cobalt plays, gold wins): blended, cobalt →\n * gold turns grey. They are the two stops of the brand's mark gradient (`marks.kachizaka` of @krizaka/tokens), each\n * ≥ 3:1 on the page surfaces of both themes.\n */\nconst cobalt = marks.kachizaka[0]![1];\n/** The gold end: the winner, the node, the halo and its breath. */\nconst gold = marks.kachizaka[marks.kachizaka.length - 1]![1];\n\nexport const KACHIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n cobalt,\n gold,\n /** The steps: second (left, cut top-left), first (centre, both top corners cut, in gold), third (right, cut top-right). */\n steps: {\n second: \"M102 240 L122 220 H162 V296 H102 Z\",\n first: \"M170 192 L188 174 H212 L230 192 V296 H170 Z\",\n third: \"M238 248 H278 L298 268 V296 H238 Z\",\n },\n /** The lit node over the winner: a gold diamond (half-diagonal 22, softened by a 6-unit round stroke), its light inside. */\n node: { d: \"M200 102 L222 124 L200 146 L178 124 Z\", light: \"M200 116.1 L207.9 124 L200 131.9 L192.1 124 Z\", soften: 6 },\n highlight: \"#fffbea\",\n gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks.kachizaka },\n /** The ball: one node on the orbit, that turns with it. */\n ball: { r: 9, at: [200, 40] as const },\n glow: { color: gold, opacity: 0.18 },\n pulse: { color: gold, low: 0.35, high: 0.75 },\n motion: { spin: 28, breath: 3.2 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
260
260
  },
261
261
  {
262
262
  "path": "marks/krizaka-geometry.ts",
@@ -272,23 +272,23 @@
272
272
  },
273
273
  {
274
274
  "path": "marks/shared.ts",
275
- "content": "import { useId } from \"react\";\n\nexport interface MarkProps {\n /** Width and height in pixels. */\n size?: number;\n /** Orbit, pulse and shimmer. Off with `false`; always off under prefers-reduced-motion. */\n animated?: boolean;\n /** An accessible name (the mark becomes an image); without it the mark is decorative. */\n title?: string;\n className?: string;\n}\n\n/** A neutral stroke: the host's `--kz-*` token when it has one, else the text colour at a low opacity. */\nexport const neutral = (token: string, percent: number) => `var(${token}, color-mix(in srgb, currentColor ${percent}%, transparent))`;\n\n/** Ids unique per instance, so several marks on a page never share a gradient. */\nexport function useIds<K extends string>(prefix: string, keys: readonly K[]): Record<K, string> {\n const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n return Object.fromEntries(keys.map((k) => [k, `${prefix}-${k}-${uid}`])) as Record<K, string>;\n}\n\nexport const a11y = (title?: string) => (title ? { role: \"img\" as const, \"aria-label\": title } : { \"aria-hidden\": true as const });\n\n/** `#rrggbb` + an opacity → `#rrggbbaa` (a brand colour in a CSS shadow). */\nexport const withAlpha = (hex: string, opacity: number) => `${hex}${Math.round(opacity * 255).toString(16).padStart(2, \"0\")}`;\n"
275
+ "content": "import { useId } from \"react\";\n\nexport interface MarkProps {\n /** Width and height in pixels. */\n size?: number;\n /** Orbit, pulse and shimmer. Off with `false`; always off under prefers-reduced-motion. */\n animated?: boolean;\n /** An accessible name (the mark becomes an image); without it the mark is decorative. */\n title?: string;\n className?: string;\n}\n\n/** A neutral stroke: the host's `--kz-*` token when it has one, else the text colour at a low opacity. */\nexport const neutral = (token: string, percent: number) => `var(${token}, color-mix(in srgb, currentColor ${percent}%, transparent))`;\n\n/** Ids unique per instance, so several marks on a page never share a gradient. */\nexport function useIds<K extends string>(prefix: string, keys: readonly K[]): Record<K, string> {\n const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n return Object.fromEntries(keys.map((k) => [k, `${prefix}-${k}-${uid}`])) as Record<K, string>;\n}\n\nexport const a11y = (title?: string) => (title ? { role: \"img\" as const, \"aria-label\": title } : { \"aria-hidden\": true as const });\n\n/** `#rrggbb` + an opacity → `#rrggbbaa` (a brand colour in a CSS shadow). */\nexport const withAlpha = (hex: string, opacity: number) => `${hex}${Math.round(opacity * 255).toString(16).padStart(2, \"0\")}`;\n\n/**\n * The family's motion (web), for a mark whose classes start with `prefix`: `<prefix>-orbit` turns the dashed orbit and\n * its node, `<prefix>-core` makes the lit core breathe (a shadow in the brand colour). Still under prefers-reduced-motion.\n */\nexport const familyMotion = (prefix: string, { spin, breath, color, low, high }: { spin: number; breath: number; color: string; low: number; high: number }) => `\n.${prefix}-orbit { transform-origin: 200px 200px; animation: ${prefix}-orbit ${spin}s linear infinite; }\n.${prefix}-core { transform-box: fill-box; transform-origin: center; animation: ${prefix}-core ${breath}s ease-in-out infinite; }\n@keyframes ${prefix}-orbit { to { transform: rotate(360deg); } }\n@keyframes ${prefix}-core {\n 0%, 100% { filter: drop-shadow(0 0 6px ${withAlpha(color, low)}); opacity: .92; }\n 50% { filter: drop-shadow(0 0 16px ${withAlpha(color, high)}); opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) { .${prefix}-orbit, .${prefix}-core { animation: none; } }\n`;\n"
276
276
  },
277
277
  {
278
278
  "path": "marks/yasakia-geometry.ts",
279
- "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Yasakia mark's geometry — drawn by the web mark (YasakiaLogo) and the native one (YasakiaMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold emblem in the brand\n * gradient, a bright core; below 48 px the view box crops to the emblem.\n *\n * The emblem is **two voices**: two solid speech bubbles, mirrored, that overlap. Where they overlap — what the two\n * people share once they have talked — is the bright core, a lens lit from above that breathes. The left voice sits a\n * shade deeper than the right, so the pair reads as two, with depth, at 16 px as at 128 px. A thin gap (the page shows\n * through) separates each bubble from the lens: the paths are cut here, no mask, so web and native draw the same thing.\n * Two nodes, the pair, ride the dashed orbit. The gradient is the brand's mark gradient (`marks.yasakia` of\n * @krizaka/tokens): emerald → turquoise, mid-tones that read ≥ 3:1 on the page surfaces of both themes (tested in\n * @krizaka/tokens), so one mark serves dark and light.\n */\n/** The bright end of the gradient (turquoise): the halo and the core's glow. */\nconst turquoise = marks.yasakia[marks.yasakia.length - 1]![1];\n\n/*\n * How the paths are drawn (literal, so the native bundle carries no geometry code): each bubble is a circle of radius 80\n * centred at y 184, 36 on either side of x = 200 (164 and 236); they overlap in the lens (x = 200, y 112.6 → 255.4).\n * Each bubble is cut by the other's circle grown by the gap (r 87): the cut runs from (191.9, 109) to (191.9, 259) on\n * the left, mirrored on the right. Each tail is a curved point from the bubble's lower outer side, down and out to\n * (88, 294) — mirrored (312, 294).\n */\nexport const YASAKIA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n /** The two voices, left then right: each a bubble (cut around the lens) and its tail. */\n voices: [\n { body: \"M191.9 109 A80 80 0 1 0 191.9 259 A87 87 0 0 1 191.9 109 Z\", tail: \"M98 230 Q104 268 88 294 Q122 288 144 262 L130 200 Z\" },\n { body: \"M208.1 109 A80 80 0 1 1 208.1 259 A87 87 0 0 0 208.1 109 Z\", tail: \"M302 230 Q296 268 312 294 Q278 288 256 262 L270 200 Z\" },\n ] as const,\n /** The left voice, a shade deeper: the depth between the two. */\n shade: { color: \"#000\", opacity: 0.22 },\n /** Where the bubbles overlap: the core, lit from above. */\n lens: { d: \"M200 112.6 A80 80 0 0 1 200 255.4 A80 80 0 0 1 200 112.6 Z\", y1: 112.6, y2: 255.4 },\n gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks.yasakia },\n /** The light on the lens: bright at the top, fading down. */\n highlight: { color: \"#f0fdfa\", from: 0.75, to: 0.2 },\n /** The pair: two nodes on the orbit, opposite each other, that turn with it. */\n pair: { r: 8, at: [[40, 200], [360, 200]] as const },\n glow: { color: turquoise, opacity: 0.16 },\n /** The core's breathing shadow (web), in the mark's turquoise. */\n pulse: { color: turquoise, low: 0.3, high: 0.6 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
279
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Yasakia mark's geometry — drawn by the web mark (YasakiaLogo) and the native one (YasakiaMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold solid emblem in the\n * brand gradient, a lit core; below 48 px the view box crops to the emblem.\n *\n * The emblem is **one conversation** (2026-10-17): one speech bubble with a tail on each side — the conversation\n * belongs to both people, talk first — and the spark inside it, the family's lit node, breathes. The tails turn about\n * the centre (180°): the mark is symmetric by rotation, never by mirror, so it can never read as a face. The gradient\n * is the brand's mark gradient (`marks.yasakia` of @krizaka/tokens): emerald → turquoise, ≥ 3:1 on both themes.\n */\n/** The turquoise end of the gradient: the halo and the spark's breath. */\nconst turquoise = marks.yasakia[marks.yasakia.length - 1]![1];\n\nexport const YASAKIA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n bubble: { cx: 200, cy: 200, r: 86 },\n /** The two tails, out of the bubble at 115°–160° and, turned 180° about the centre, at 295°–340°. */\n tails: [\n \"M163.7 277.9 Q138 294 106 304 Q124 270 119.2 229.4 Z\",\n \"M236.3 122.1 Q262 106 294 96 Q276 130 280.8 170.6 Z\",\n ],\n /** The spark: a diamond of light (half-diagonal 24, softened by a 6-unit round stroke). */\n spark: { d: \"M200 176 L224 200 L200 224 L176 200 Z\", soften: 6 },\n highlight: \"#f0fdfa\",\n gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks.yasakia },\n /** The two people: one node on the orbit, that turns with it. */\n node: { r: 9, at: [200, 40] as const },\n glow: { color: turquoise, opacity: 0.16 },\n pulse: { color: turquoise, low: 0.3, high: 0.6 },\n motion: { spin: 30, breath: 3.2 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
280
280
  },
281
281
  {
282
282
  "path": "marks/yomizaka-geometry.ts",
283
- "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Yomizaka mark's geometry — drawn by the web mark (YomizakaLogo) and the native one (YomizakaMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold filled emblem in the\n * brand gradient, a bright core; below 48 px the view box crops to the emblem.\n *\n * The emblem is **the reading**: a filled page whose upper-right corner wears the Krizaka cut, three lines of text in\n * light, and a lens — the eye that reads, the owl's — resting on its lower half, its handle running out of the page.\n * The lens is the bright core: it breathes. A thin gap (the page shows through) separates the page from the lens: the\n * page is cut here, no mask, so web and native draw the same thing. On the web a line of light scans the page from top\n * to bottom (reading); one node, a page, rides the dashed orbit. The gradient is the brand's mark gradient\n * (`marks.yomizaka` of @krizaka/tokens): ink → indigo, mid-tones that read ≥ 3:1 on the page surfaces of both themes\n * (tested in @krizaka/tokens), so one mark serves dark and light.\n */\n/** The indigo end of the gradient: the halo. */\nconst indigo = marks.yomizaka[marks.yomizaka.length - 1]![1];\n\n/*\n * How the page is drawn (literal, so the native bundle carries no geometry code): a page x 128 → 264, y 92 → 300, its\n * upper-right corner cut 36 (228, 92) → (264, 128). The lens is centred at (240, 232); the page is cut by the lens'\n * circle grown by the gap (r 52), which crosses the right edge at y 232 ± 46.13 — the large arc runs inside the page.\n */\nexport const YOMIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n /** The page, cut at its corner (the Krizaka chamfer) and around the lens. */\n page: \"M128 92 H228 L264 128 V185.87 A52 52 0 1 0 264 278.13 V300 H128 Z\",\n /** Three lines of text and the start of a fourth, in light: they wrap around the lens. */\n lines: \"M156 140 H226 M156 170 H236 M156 200 H184 M156 230 H176\",\n line: { color: \"#f4f5ff\", opacity: 0.6, width: 10 },\n /** The lens: a ring in the brand gradient, its pale core lit from above, a glint; the handle leaves the page. */\n lens: { cx: 240, cy: 232, r: 40, width: 12, core: 28, glint: { cx: 230, cy: 222, r: 8 } },\n handle: \"M276 268 L304 296\",\n handleWidth: 20,\n gradient: { x1: 128, y1: 300, x2: 304, y2: 92, stops: marks.yomizaka },\n /** The lens' core: a pale indigo light, brighter at the top. */\n coreGradient: { stops: [[\"0%\", \"#c9ceff\"], [\"100%\", \"#f4f5ff\"]] as const },\n highlight: \"#ffffff\",\n /** The reading (web): a line of light that scans the page from top to bottom, clipped to the page. */\n scan: { d: \"M136 100 H236\", travel: 192, color: \"#f4f5ff\", opacity: 0.7, width: 3 },\n /** A page: one node on the orbit, that turns with it. */\n node: { r: 9, at: [200, 40] as const },\n glow: { color: indigo, opacity: 0.18 },\n /** The lens' breathing shadow (web), in the indigo. */\n pulse: { color: indigo, low: 0.3, high: 0.6 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
283
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Yomizaka mark's geometry — drawn by the web mark (YomizakaLogo) and the native one (YomizakaMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold emblem at the family\n * stroke (22) in the brand gradient, a lit core; below 48 px the view box crops to the emblem.\n *\n * The emblem is **the watch** (2026-10-17): the owl — the bird that reads documents and knows faces — drawn as two\n * lenses under one calm brow, its pupils glancing down the line it reads. The brow is a soft double curve (never a\n * sharp V: the owl is attentive, not stern). The pupils are the lit cores: they breathe. The gradient is the brand's\n * mark gradient (`marks.yomizaka` of @krizaka/tokens): ink → indigo, ≥ 3:1 on the page surfaces of both themes.\n */\n/** The indigo end of the gradient: the halo and the pupils' breath. */\nconst indigo = marks.yomizaka[marks.yomizaka.length - 1]![1];\n\nexport const YOMIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n stroke: 22,\n /** The brow: two soft arcs that meet over the beak — calm, never a sharp V. */\n brow: \"M106 153 C130 125 176 129 200 159 C224 129 270 125 294 153\",\n /** The two lenses (rings at the family stroke). */\n lenses: [\n { cx: 146, cy: 223, r: 40 },\n { cx: 254, cy: 223, r: 40 },\n ],\n /** The pupils, glancing down-right (reading): the lit cores. */\n pupils: [\n { cx: 154, cy: 229, r: 15, inner: 5.1 },\n { cx: 262, cy: 229, r: 15, inner: 5.1 },\n ],\n highlight: \"#f4f5ff\",\n gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks.yomizaka },\n /** One node, a page, rides the orbit. */\n node: { r: 9, at: [200, 40] as const },\n glow: { color: indigo, opacity: 0.18 },\n pulse: { color: indigo, low: 0.3, high: 0.6 },\n motion: { spin: 32, breath: 3.6 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
284
284
  },
285
285
  {
286
286
  "path": "marks/yororis-geometry.ts",
287
- "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Yororis mark's geometry — drawn by the web mark (YororisLogo) and the native one (YororisMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold filled emblem in the\n * brand gradient, a bright core; below 48 px the view box crops to the emblem.\n *\n * The emblem is \"the hold\": a \"Y\" whose two arms are two filled leaves — open hands — that rise from one stem and cup\n * a warm core. The core is not where the arms join, it rests *inside* them: held, given room, listened to. A vein of\n * light runs along each leaf (depth, like the Orochia serpent's scales). One node, the moment, rides the dashed orbit:\n * it comes round, then it goes (a presence that stops). The leaves wear the brand's mark gradient (`marks.yororis` of\n * @krizaka/tokens): sand → sage, mid-tones that read ≥ 3:1 on the page surfaces of both themes (tested in\n * @krizaka/tokens), so one mark serves dark and light; the core is a warm sand light that rises from the mark's sand.\n */\nconst sand = marks.yororis[0]![1];\n/** The calm end of the gradient (sage): the halo and the core's glow. */\nconst sage = marks.yororis[marks.yororis.length - 1]![1];\n\nexport const YORORIS = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n /** The two leaves — open hands, mirrored around x = 200 — filled, rising from the stem. */\n leaves: [\n \"M200 262 C142 256 104 206 110 122 C138 130 154 152 160 180 C168 218 182 238 204 246 Z\",\n \"M200 262 C258 256 296 206 290 122 C262 130 246 152 240 180 C232 218 218 238 196 246 Z\",\n ],\n /** A vein of light along each leaf (dashed, it drifts slowly when animated). */\n veins: \"M122 142 C128 196 156 232 192 248 M278 142 C272 196 244 232 208 248\",\n vein: { color: \"#fbf6ea\", opacity: 0.45, width: 3, dash: \"2 14\" },\n /** The stem, from the junction down. */\n stem: \"M200 244 L200 312\",\n stroke: 30,\n gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks.yororis },\n /** The core's warm light: from the mark's sand (below) to a light sand (above). */\n coreGradient: { stops: [[\"0%\", sand], [\"100%\", \"#e6c993\"]] as const },\n /** The core, held in the hollow of the leaves (not on the junction): the one who is listened to. It breathes. */\n core: { cx: 200, cy: 184, r: 32, inner: 11 },\n highlight: \"#fbf6ea\",\n /** The moment: one node on the orbit, that turns with it. */\n moment: { r: 9, at: [200, 40] as const },\n glow: { color: sage, opacity: 0.18 },\n /** The core's breathing shadow (web), in the core's warm sand. */\n pulse: { color: sand, low: 0.3, high: 0.6 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
287
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Yororis mark's geometry — drawn by the web mark (YororisLogo) and the native one (YororisMark). The family's\n * grammar (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold emblem at the family\n * stroke (22), a lit core; below 48 px the view box crops to the emblem.\n *\n * The emblem is **held** (2026-10-17): two open hands, drawn as two arcs of one circle, cup a warm core without\n * closing on it — held, given room, free to go. The arcs are sage, the core sand: the two stops of the brand's mark\n * gradient (`marks.yororis` of @krizaka/tokens), each ≥ 3:1 on the page surfaces of both themes. The slowest breath of\n * the family (4.2 s).\n */\nconst sand = marks.yororis[0]![1];\n/** The sage end: the hands, the halo. */\nconst sage = marks.yororis[marks.yororis.length - 1]![1];\n\nexport const YORORIS = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"80 80 240 240\",\n center: 200,\n stroke: 22,\n sand,\n sage,\n /** The two hands: arcs of a circle r 92 centred (200, 162), from 196° to 104° and from 76° to −16°; open at the bottom and the top. */\n hands: \"M111.6 136.6 A92 92 0 0 0 177.7 251.3 M222.3 251.3 A92 92 0 0 0 288.4 136.6\",\n /** The warm core, held inside the hands. */\n core: { cx: 200, cy: 176, r: 30, inner: 10.2 },\n highlight: \"#fbf6ea\",\n gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks.yororis },\n /** The moment: one node on the orbit — it comes round, then it goes. */\n node: { r: 9, at: [200, 40] as const },\n glow: { color: sage, opacity: 0.18 },\n pulse: { color: sand, low: 0.3, high: 0.6 },\n motion: { spin: 36, breath: 4.2 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
288
288
  },
289
289
  {
290
290
  "path": "meta.ts",
291
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
291
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
292
292
  },
293
293
  {
294
294
  "path": "site-footer/index.ts",
@@ -304,7 +304,7 @@
304
304
  }
305
305
  ],
306
306
  "dependencies": [
307
- "@krizaka/tokens@^3.20.0",
307
+ "@krizaka/tokens@^3.22.0",
308
308
  "radix-ui@^1.7.0",
309
309
  "tailwind-variants@^3.3.1"
310
310
  ],
@@ -81,7 +81,7 @@
81
81
  "files": [
82
82
  {
83
83
  "path": "meta.ts",
84
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
84
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
85
85
  },
86
86
  {
87
87
  "path": "sites/index.ts",
@@ -210,7 +210,7 @@
210
210
  "files": [
211
211
  {
212
212
  "path": "meta.ts",
213
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
213
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
214
214
  },
215
215
  {
216
216
  "path": "skeleton/index.ts",