@krizaka/ui 3.28.0 → 3.29.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 (171) hide show
  1. package/README.md +2 -0
  2. package/dist/blocks.css +2 -0
  3. package/dist/docs-hub-page.d.ts +7 -1
  4. package/dist/docs-hub-page.js +2 -2
  5. package/dist/docs-hub-page.js.map +1 -1
  6. package/dist/engine-flow.d.ts +97 -0
  7. package/dist/engine-flow.js +214 -0
  8. package/dist/engine-flow.js.map +1 -0
  9. package/dist/motion.css +121 -1
  10. package/dist/native.d.ts +21 -5
  11. package/dist/native.js +65 -32
  12. package/dist/native.js.map +1 -1
  13. package/package.json +20 -6
  14. package/registry/account-menu.json +1 -1
  15. package/registry/achievement.json +1 -1
  16. package/registry/action-bar.json +1 -1
  17. package/registry/activity-feed.json +1 -1
  18. package/registry/alert.json +1 -1
  19. package/registry/app-header.json +1 -1
  20. package/registry/app-shell.json +1 -1
  21. package/registry/argument-card.json +1 -1
  22. package/registry/audio-player.json +1 -1
  23. package/registry/auth-layout.json +1 -1
  24. package/registry/avatar.json +1 -1
  25. package/registry/badge.json +1 -1
  26. package/registry/button.json +1 -1
  27. package/registry/captcha.json +1 -1
  28. package/registry/card-grid.json +1 -1
  29. package/registry/card.json +1 -1
  30. package/registry/catalogue-page.json +1 -1
  31. package/registry/changelog-page.json +1 -1
  32. package/registry/chat.json +55 -8
  33. package/registry/checkbox.json +1 -1
  34. package/registry/chip-link.json +1 -1
  35. package/registry/chip-row.json +1 -1
  36. package/registry/chip.json +1 -1
  37. package/registry/choice-grid.json +1 -1
  38. package/registry/code-block.json +1 -1
  39. package/registry/combobox.json +1 -1
  40. package/registry/command.json +1 -1
  41. package/registry/comparison-table.json +1 -1
  42. package/registry/component-page.json +1 -1
  43. package/registry/confirm-button.json +1 -1
  44. package/registry/contact-page.json +1 -1
  45. package/registry/continue-prompt.json +1 -1
  46. package/registry/countdown.json +1 -1
  47. package/registry/cover-hero.json +1 -1
  48. package/registry/cta-panel.json +1 -1
  49. package/registry/dashboard-layout.json +1 -1
  50. package/registry/data-table.json +1 -1
  51. package/registry/date-picker.json +1 -1
  52. package/registry/decision-list.json +1 -1
  53. package/registry/description-list.json +1 -1
  54. package/registry/detail-layout.json +1 -1
  55. package/registry/dialog.json +1 -1
  56. package/registry/docs-article-page.json +1 -1
  57. package/registry/docs-hub-page.json +16 -2
  58. package/registry/docs-layout.json +1 -1
  59. package/registry/docs-nav.json +1 -1
  60. package/registry/document-viewer.json +1 -1
  61. package/registry/dropdown-menu.json +1 -1
  62. package/registry/empty-state.json +1 -1
  63. package/registry/engine-flow.json +280 -0
  64. package/registry/error-page.json +1 -1
  65. package/registry/examples/chat/native/assistant.d.ts +4 -0
  66. package/registry/examples/chat/native/assistant.dark.png +0 -0
  67. package/registry/examples/chat/native/assistant.js +32 -0
  68. package/registry/examples/chat/native/assistant.light.png +0 -0
  69. package/registry/examples/chat/native/assistant.tsx +28 -0
  70. package/registry/examples/engine-flow/compact.d.ts +4 -0
  71. package/registry/examples/engine-flow/compact.js +33 -0
  72. package/registry/examples/engine-flow/compact.tsx +30 -0
  73. package/registry/examples/engine-flow/radial.d.ts +4 -0
  74. package/registry/examples/engine-flow/radial.js +37 -0
  75. package/registry/examples/engine-flow/radial.tsx +32 -0
  76. package/registry/field.json +1 -1
  77. package/registry/file-upload.json +1 -1
  78. package/registry/filter-bar.json +1 -1
  79. package/registry/force-update-screen.json +1 -1
  80. package/registry/form.json +1 -1
  81. package/registry/gallery-page.json +1 -1
  82. package/registry/genui.json +1 -1
  83. package/registry/glow-card.json +1 -1
  84. package/registry/goal-gauge.json +1 -1
  85. package/registry/how-it-works-page.json +1 -1
  86. package/registry/image-cropper.json +1 -1
  87. package/registry/index.json +37 -6
  88. package/registry/iso.json +3 -3
  89. package/registry/kanban.json +1 -1
  90. package/registry/kbd.json +1 -1
  91. package/registry/landing-page.json +1 -1
  92. package/registry/landscape.json +1 -1
  93. package/registry/language-switch.json +1 -1
  94. package/registry/legal-page.json +1 -1
  95. package/registry/link-card.json +1 -1
  96. package/registry/list.json +1 -1
  97. package/registry/loader.json +1 -1
  98. package/registry/longform-page.json +1 -1
  99. package/registry/maintenance-screen.json +1 -1
  100. package/registry/map.json +2 -2
  101. package/registry/markdown.json +1 -1
  102. package/registry/marketing-layout.json +1 -1
  103. package/registry/marquee.json +1 -1
  104. package/registry/mascot-quote.json +1 -1
  105. package/registry/mascot-state.json +1 -1
  106. package/registry/mascots.json +1 -1
  107. package/registry/match-dialog.json +1 -1
  108. package/registry/media-capture.json +1 -1
  109. package/registry/media-gallery.json +1 -1
  110. package/registry/media-grid.json +1 -1
  111. package/registry/meta-row.json +1 -1
  112. package/registry/meter.json +1 -1
  113. package/registry/nav-bar.json +2 -2
  114. package/registry/number-ticker.json +1 -1
  115. package/registry/opening-hours.json +1 -1
  116. package/registry/page-header.json +1 -1
  117. package/registry/page-hero.json +1 -1
  118. package/registry/page.json +1 -1
  119. package/registry/pagination.json +1 -1
  120. package/registry/photo-grid.json +1 -1
  121. package/registry/pointer-light.json +1 -1
  122. package/registry/popover.json +1 -1
  123. package/registry/pricing-page.json +1 -1
  124. package/registry/product-page.json +1 -1
  125. package/registry/profile-card.json +1 -1
  126. package/registry/progress.json +1 -1
  127. package/registry/promise-card.json +1 -1
  128. package/registry/radio-group.json +1 -1
  129. package/registry/rail-nav.json +2 -2
  130. package/registry/recipes.json +1 -1
  131. package/registry/scroll-stage.json +1 -1
  132. package/registry/search-field.json +1 -1
  133. package/registry/section-backdrop.json +1 -1
  134. package/registry/separator.json +1 -1
  135. package/registry/service-card.json +1 -1
  136. package/registry/settings-layout.json +1 -1
  137. package/registry/showcase.json +1 -1
  138. package/registry/site-footer.json +2 -2
  139. package/registry/sites.json +1 -1
  140. package/registry/skeleton.json +1 -1
  141. package/registry/skip-link.json +1 -1
  142. package/registry/slider.json +1 -1
  143. package/registry/slot-picker.json +1 -1
  144. package/registry/slot.json +1 -1
  145. package/registry/spinner.json +1 -1
  146. package/registry/split-view.json +1 -1
  147. package/registry/stage-banner.json +1 -1
  148. package/registry/stat.json +1 -1
  149. package/registry/status-page.json +1 -1
  150. package/registry/step-progress.json +1 -1
  151. package/registry/step-showcase.json +1 -1
  152. package/registry/stepper.json +1 -1
  153. package/registry/sticky-action.json +1 -1
  154. package/registry/swipe-deck.json +1 -1
  155. package/registry/switch.json +1 -1
  156. package/registry/tabs.json +1 -1
  157. package/registry/tag-input.json +1 -1
  158. package/registry/tag-selector.json +1 -1
  159. package/registry/terminal-replay.json +1 -1
  160. package/registry/theme.json +1 -1
  161. package/registry/toast.json +1 -1
  162. package/registry/toc.json +1 -1
  163. package/registry/tooltip.json +1 -1
  164. package/registry/tree-view.json +1 -1
  165. package/registry/update-prompt.json +1 -1
  166. package/registry/verified-badge.json +1 -1
  167. package/registry/vertical-video.json +1 -1
  168. package/registry/video-player.json +1 -1
  169. package/registry/video-trimmer.json +1 -1
  170. package/registry/virtual.json +1 -1
  171. package/registry/waveform.json +1 -1
@@ -0,0 +1,280 @@
1
+ {
2
+ "name": "engine-flow",
3
+ "type": "primitive",
4
+ "title": "Engine flow",
5
+ "summary": "An engine at the hub and the apps it serves around it, with what travels between them: photons going in (requests) and coming back (results) along each conduit — radial on a wide screen, top to bottom on a phone, planned apps dim and still.",
6
+ "description": "An engine at the hub and the apps it serves around it, with what travels between them: photons going in (requests) and coming back (results) along each conduit — radial on a wide screen, top to bottom on a phone, planned apps dim and still.",
7
+ "status": "beta",
8
+ "deprecated": null,
9
+ "category": "data-display",
10
+ "platforms": "web",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "n/a",
17
+ "reason": "A diagram of a public site's page; an app screen lists the same facts.",
18
+ "use": "list"
19
+ },
20
+ "desktop": {
21
+ "state": "yes"
22
+ }
23
+ },
24
+ "whenToUse": [
25
+ "Showing one platform and the products built on it, in one glance: « one engine, the products it powers ».",
26
+ "Saying which way data goes per app — in, back, or both — only where it really runs; what is planned is drawn planned."
27
+ ],
28
+ "whenNotToUse": [
29
+ {
30
+ "when": "A sequence of steps a reader follows in order.",
31
+ "use": "stepper"
32
+ },
33
+ {
34
+ "when": "Entry points to pages, where the cards are the content.",
35
+ "use": "card-grid"
36
+ },
37
+ {
38
+ "when": "A chart of figures over time."
39
+ }
40
+ ],
41
+ "bestPractices": [
42
+ "Give each node its brand scope (`className=\"brand-<id>\"`) and the figure the hub's: photons going in wear the app's colour, those coming back the engine's.",
43
+ "Draw only what runs: `flow=\"both\"` where results really come back, `planned` (with a `status` word) where nothing is wired yet.",
44
+ "Keep a text version (a list) in the page; give each node a one-line `description` and its `href`.",
45
+ "Five to eight nodes: radial reads at a glance; on a phone the default `compactLayout` puts them in a row above the hub."
46
+ ],
47
+ "accessibility": {
48
+ "keyboard": [
49
+ {
50
+ "keys": "Tab",
51
+ "action": "Goes through the nodes, then the link of the detail line."
52
+ },
53
+ {
54
+ "keys": "Enter / Space",
55
+ "action": "Chooses the node: the detail line says its description and its link."
56
+ }
57
+ ],
58
+ "notes": [
59
+ "The drawing (conduits, photons) is hidden from assistive technology; each node is a button named by its label, `aria-pressed` when chosen.",
60
+ "The detail line is a polite live region: pointing at or focusing a node announces its line.",
61
+ "Motion is CSS (motion.css): paused off screen (MotionObserver), held while the page settles, a still frame under reduced motion. No animation frame, no canvas.",
62
+ "The stage keeps its aspect ratio and the detail line its height: no layout shift."
63
+ ]
64
+ },
65
+ "related": [
66
+ "card-grid",
67
+ "glow-card",
68
+ "iso",
69
+ "number-ticker"
70
+ ],
71
+ "web": {
72
+ "entry": "@krizaka/ui/engine-flow",
73
+ "import": "import { EngineFlow } from \"@krizaka/ui/engine-flow\";",
74
+ "props": [
75
+ {
76
+ "component": "EngineFlow",
77
+ "description": "An engine at the hub and the apps it serves, with what travels between them.",
78
+ "props": [
79
+ {
80
+ "name": "hub",
81
+ "type": "EngineFlowHub",
82
+ "default": null,
83
+ "description": "The engine, at the hub.",
84
+ "required": true
85
+ },
86
+ {
87
+ "name": "label",
88
+ "type": "string",
89
+ "default": null,
90
+ "description": "What the figure shows (\"Orazaka and the products it powers\"), passed translated.",
91
+ "required": true
92
+ },
93
+ {
94
+ "name": "nodes",
95
+ "type": "readonly EngineFlowNode[]",
96
+ "default": null,
97
+ "description": "The apps around it.",
98
+ "required": true
99
+ },
100
+ {
101
+ "name": "compactLayout",
102
+ "type": "false | FlowLayout",
103
+ "default": "top-to-bottom",
104
+ "description": "The layout under a 560 px wide container (a phone); `false` keeps `layout` everywhere.",
105
+ "required": false
106
+ },
107
+ {
108
+ "name": "conduit",
109
+ "type": "FlowConduit",
110
+ "default": "bezier",
111
+ "description": "The conduits' shape.",
112
+ "required": false
113
+ },
114
+ {
115
+ "name": "defaultActive",
116
+ "type": "string",
117
+ "default": null,
118
+ "description": "The node shown in the detail line at first.",
119
+ "required": false
120
+ },
121
+ {
122
+ "name": "hint",
123
+ "type": "ReactNode",
124
+ "default": null,
125
+ "description": "The detail line before a node is chosen (\"Point at a product\").",
126
+ "required": false
127
+ },
128
+ {
129
+ "name": "layout",
130
+ "type": "FlowLayout",
131
+ "default": "radial",
132
+ "description": "How the nodes sit around the hub.",
133
+ "required": false
134
+ },
135
+ {
136
+ "name": "particles",
137
+ "type": "\"none\" | \"photons\"",
138
+ "default": "photons",
139
+ "description": "`photons` travel the conduits; `none` draws them still.",
140
+ "required": false
141
+ },
142
+ {
143
+ "name": "speed",
144
+ "type": "number",
145
+ "default": "16",
146
+ "description": "Photon speed, in units per second (the stage is 100 units wide).",
147
+ "required": false
148
+ }
149
+ ]
150
+ }
151
+ ],
152
+ "examples": [
153
+ {
154
+ "name": "radial",
155
+ "title": "One engine, the products it powers",
156
+ "description": "Radial on a wide screen: both ways where results come back, planned products dim and still.",
157
+ "path": "examples/engine-flow/radial.tsx",
158
+ "module": "@krizaka/ui/registry/examples/engine-flow/radial",
159
+ "code": "import { ProductLogo } from \"@krizaka/ui\";\nimport { EngineFlow } from \"@krizaka/ui/engine-flow\";\n\nconst products = [\n { id: \"orochia\", label: \"Orochia\", flow: \"both\", description: \"Search and recommendations; insights come back.\" },\n { id: \"yomizaka\", label: \"Yomizaka\", flow: \"both\", description: \"Reads the lease and checks each passage.\" },\n { id: \"kachizaka\", label: \"Kachizaka\", flow: \"both\", description: \"Match facts, read through a connector.\" },\n { id: \"akinaza\", label: \"Akinaza\", flow: \"in\", planned: true, description: \"The tax assistant, not wired yet.\" },\n { id: \"yasakia\", label: \"Yasakia\", flow: \"in\", planned: true, description: \"The matching agent, not wired yet.\" },\n { id: \"yororis\", label: \"Yororis\", flow: \"in\", planned: true, description: \"The listening pack, not wired yet.\" },\n] as const;\n\n// One engine at the hub, the products it powers around it: both ways where results come back, planned ones still.\nexport default function EngineFlowRadial() {\n return (\n <EngineFlow\n className=\"brand-orazaka\"\n label=\"Orazaka and the products it powers\"\n hub={{ label: \"Orazaka\", caption: \"the engine\", mark: <ProductLogo id=\"orazaka\" size={56} animated={false} /> }}\n hint=\"Point at a product to see what it uses the engine for.\"\n defaultActive=\"orochia\"\n nodes={products.map((p) => ({\n ...p,\n className: `brand-${p.id}`,\n mark: <ProductLogo id={p.id} size={32} animated={false} />,\n href: `https://dev.${p.id}.com`,\n linkLabel: `dev.${p.id}.com`,\n status: \"planned\" in p ? \"planned\" : undefined,\n }))}\n />\n );\n}\n"
160
+ },
161
+ {
162
+ "name": "compact",
163
+ "title": "On a phone",
164
+ "description": "Top to bottom: the products in a row above, flowing down into the engine; the detail line names the chosen one.",
165
+ "path": "examples/engine-flow/compact.tsx",
166
+ "module": "@krizaka/ui/registry/examples/engine-flow/compact",
167
+ "code": "import { ProductLogo } from \"@krizaka/ui\";\nimport { EngineFlow } from \"@krizaka/ui/engine-flow\";\n\nconst ids = [\"orochia\", \"yomizaka\", \"kachizaka\", \"akinaza\", \"yasakia\", \"yororis\"] as const;\n\n// On a phone: the products in a row above, flowing down into the engine; the detail line names the chosen one.\nexport default function EngineFlowCompact() {\n return (\n <div style={{ maxWidth: 360 }}>\n <EngineFlow\n className=\"brand-orazaka\"\n label=\"Orazaka and the products it powers\"\n layout=\"top-to-bottom\"\n compactLayout={false}\n hub={{ label: \"Orazaka\", mark: <ProductLogo id=\"orazaka\" size={44} animated={false} /> }}\n hint=\"Tap a product.\"\n defaultActive=\"yomizaka\"\n nodes={ids.map((id, i) => ({\n id,\n label: id[0]!.toUpperCase() + id.slice(1),\n className: `brand-${id}`,\n mark: <ProductLogo id={id} size={24} animated={false} />,\n flow: i < 3 ? \"both\" : \"in\",\n planned: i >= 3,\n description: i < 3 ? \"Requests in, results back.\" : \"Planned: not wired yet.\",\n }))}\n />\n </div>\n );\n}\n"
168
+ }
169
+ ]
170
+ },
171
+ "native": null,
172
+ "files": [
173
+ {
174
+ "path": "engine-flow/engine-flow.tsx",
175
+ "content": "// Client (the node a visitor points at or focuses). An engine at the hub and the apps it serves around it: one conduit\n// per app, and photons that travel it — towards the hub (`in`: requests), back (`out`: results) or both — in the app's\n// brand colour going in and the hub's coming back. A planned app is drawn dim, its conduit dashed and still.\n//\n// Motion is the Krizaka signature's (motion.css): CSS animations of `stroke-dashoffset`, no animation frame, no canvas;\n// paused off screen (`data-motion-scope` → MotionObserver's `data-offscreen`), held while the page settles (`kz-loop`),\n// a still frame under reduced motion (the photons rest along their conduit). The stage keeps its aspect ratio and the\n// detail line its height: no layout shift.\n//\n// Accessible: the drawing is hidden from assistive technology; every node is a real button named by its label, and the\n// detail line (polite live region) says the chosen node's description with its link. Pair it with a text version (a\n// list) in the page: the figure is a picture of it.\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { type FlowConduit, flowGeometry, type FlowLayout } from \"./geometry\";\n\nexport interface EngineFlowNode {\n /** A stable id (`orochia`). */\n id: string;\n /** Its name, the button's accessible name (\"Orochia\"). */\n label: string;\n /** Its mark (a `ProductLogo`), decorative. */\n mark?: React.ReactNode;\n /** One line: what it uses the engine for — shown when the node is pointed at or focused. */\n description?: React.ReactNode;\n /** Where it lives (its site). */\n href?: string;\n /** The link's words (\"Visit dev.orochia.com\"); defaults to the label. */\n linkLabel?: React.ReactNode;\n /** Opens the link in a new tab. */\n external?: boolean;\n /** What travels: `in` towards the hub, `out` back from it, `both`.\n * @default \"in\" */\n flow?: \"in\" | \"out\" | \"both\";\n /** Not wired yet: drawn dim, its conduit dashed and still, no photon. */\n planned?: boolean;\n /** A word beside the name in the detail line (\"planned\"), passed translated. */\n status?: React.ReactNode;\n /** Its brand scope (`brand-orochia`): its conduit, photons and ring take its `--kz-accent`. */\n className?: string;\n}\n\nexport interface EngineFlowHub {\n /** The engine's name (\"Orazaka\"). */\n label: string;\n /** Its mark, decorative. */\n mark?: React.ReactNode;\n /** A line under the name (\"the engine\"). */\n caption?: React.ReactNode;\n /** Its brand scope; the photons coming back take its accent. */\n className?: string;\n}\n\nexport type EngineFlowProps = Omit<React.ComponentProps<\"figure\">, \"children\"> & {\n /** What the figure shows (\"Orazaka and the products it powers\"), passed translated. */\n label: string;\n /** The engine, at the hub. */\n hub: EngineFlowHub;\n /** The apps around it. */\n nodes: readonly EngineFlowNode[];\n /** How the nodes sit around the hub.\n * @default \"radial\" */\n layout?: FlowLayout;\n /** The layout under a 560 px wide container (a phone); `false` keeps `layout` everywhere.\n * @default \"top-to-bottom\" */\n compactLayout?: FlowLayout | false;\n /** `photons` travel the conduits; `none` draws them still.\n * @default \"photons\" */\n particles?: \"photons\" | \"none\";\n /** The conduits' shape.\n * @default \"bezier\" */\n conduit?: FlowConduit;\n /** The detail line before a node is chosen (\"Point at a product\"). */\n hint?: React.ReactNode;\n /** The node shown in the detail line at first. */\n defaultActive?: string;\n /** Photon speed, in units per second (the stage is 100 units wide).\n * @default 16 */\n speed?: number;\n};\n\nfunction Stage({\n layout,\n variant,\n nodes,\n hub,\n conduit,\n particles,\n speed,\n active,\n onActive,\n detailId,\n}: {\n layout: FlowLayout;\n variant: \"wide\" | \"compact\";\n nodes: readonly EngineFlowNode[];\n hub: EngineFlowHub;\n conduit: FlowConduit;\n particles: \"photons\" | \"none\";\n speed: number;\n active: string | undefined;\n onActive: (id: string) => void;\n detailId: string;\n}) {\n const g = flowGeometry(layout, nodes.length, conduit);\n const pct = (v: number, of: number) => `${((v / of) * 100).toFixed(3)}%`;\n return (\n <div className=\"kz-flow-stage\" data-variant={variant} data-layout={layout} style={{ aspectRatio: `${g.width} / ${g.height}` }}>\n <svg viewBox={`0 0 ${g.width} ${g.height}`} aria-hidden=\"true\" focusable=\"false\">\n {nodes.map((node, i) => {\n const flow = node.flow ?? \"in\";\n const length = g.lengths[i] ?? 0;\n // Two to four photons per conduit, evenly spaced; the same speed on every conduit.\n const gap = length / Math.min(4, Math.max(2, Math.round(length / 16)));\n const timing = (dir: \"in\" | \"out\") =>\n ({\n \"--kz-flow-gap\": gap.toFixed(3),\n animationDuration: `${(gap / speed).toFixed(2)}s`,\n animationDelay: `${(-((i * 0.37 + (dir === \"out\" ? 0.5 : 0)) % 1) * (gap / speed)).toFixed(2)}s`,\n }) as React.CSSProperties;\n const lanes = node.planned || particles === \"none\" ? [] : flow === \"both\" ? ([\"in\", \"out\"] as const) : [flow];\n return (\n <g\n key={node.id}\n className={cn(\"kz-flow-link\", node.className)}\n data-flow={flow}\n data-planned={node.planned ? \"\" : undefined}\n data-active={active === node.id ? \"\" : undefined}\n >\n <path className=\"kz-flow-wire\" d={g.paths[i]} />\n {lanes.map((dir) => (\n <React.Fragment key={dir}>\n <path className=\"kz-flow-photon kz-loop\" data-dir={dir} data-halo=\"\" d={g.paths[i]} style={timing(dir)} />\n <path className=\"kz-flow-photon kz-loop\" data-dir={dir} d={g.paths[i]} style={timing(dir)} />\n </React.Fragment>\n ))}\n </g>\n );\n })}\n </svg>\n <div className={cn(\"kz-flow-hub\", hub.className)} style={{ left: pct(g.hub.x, g.width), top: pct(g.hub.y, g.height) }}>\n {hub.mark ? <span className=\"kz-flow-hub-mark\">{hub.mark}</span> : null}\n <span className=\"kz-flow-hub-label\">{hub.label}</span>\n {hub.caption ? <span className=\"kz-flow-hub-caption\">{hub.caption}</span> : null}\n </div>\n <ul className=\"kz-flow-nodes\">\n {nodes.map((node, i) => {\n const p = g.nodes[i]!;\n const on = active === node.id;\n return (\n <li key={node.id} style={{ left: pct(p.x, g.width), top: pct(p.y, g.height) }}>\n <button\n type=\"button\"\n className={cn(\"kz-flow-node\", node.className)}\n data-active={on ? \"\" : undefined}\n data-planned={node.planned ? \"\" : undefined}\n aria-pressed={on}\n aria-controls={detailId}\n onPointerEnter={() => onActive(node.id)}\n onFocus={() => onActive(node.id)}\n onClick={() => onActive(node.id)}\n >\n {node.mark ? (\n <span className=\"kz-flow-node-mark\" aria-hidden=\"true\">\n {node.mark}\n </span>\n ) : null}\n <span className=\"kz-flow-node-label\">{node.label}</span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\n/** An engine at the hub and the apps it serves, with what travels between them. */\nexport function EngineFlow({\n label,\n hub,\n nodes,\n layout = \"radial\",\n compactLayout = \"top-to-bottom\",\n particles = \"photons\",\n conduit = \"bezier\",\n hint,\n defaultActive,\n speed = 16,\n className,\n ...props\n}: EngineFlowProps) {\n const [active, setActive] = React.useState<string | undefined>(defaultActive);\n const detailId = `${React.useId()}-detail`;\n const chosen = nodes.find((n) => n.id === active);\n const compact = compactLayout && compactLayout !== layout ? compactLayout : undefined;\n const stage = { nodes, hub, conduit, particles, speed: Math.max(1, speed), active, onActive: setActive, detailId };\n return (\n <figure\n aria-label={label}\n className={cn(\"kz-flow\", className)}\n data-motion-scope=\"\"\n data-active={chosen ? \"\" : undefined}\n data-compact={compact ? \"\" : undefined}\n {...props}\n >\n <Stage layout={layout} variant=\"wide\" {...stage} />\n {compact ? <Stage layout={compact} variant=\"compact\" {...stage} /> : null}\n <figcaption id={detailId} className={cn(\"kz-flow-detail\", chosen?.className)} aria-live=\"polite\">\n {chosen ? (\n <>\n <strong>{chosen.label}</strong>\n {chosen.status ? <span className=\"kz-flow-status\">{chosen.status}</span> : null}\n {chosen.description ? <span> {chosen.description}</span> : null}\n {chosen.href ? (\n <>\n {\" \"}\n <a href={chosen.href} {...(chosen.external ? { target: \"_blank\", rel: \"noopener\" } : {})}>\n {chosen.linkLabel ?? chosen.label}\n <span aria-hidden=\"true\"> →</span>\n </a>\n </>\n ) : null}\n </>\n ) : (\n hint\n )}\n </figcaption>\n </figure>\n );\n}\n"
176
+ },
177
+ {
178
+ "path": "engine-flow/geometry.ts",
179
+ "content": "// Server-safe, pure. The geometry of EngineFlow: where the hub and the nodes sit in a box 100 units wide, and the\n// conduit from each node to the hub (an SVG path drawn node → hub, and its length, so photons travel at one speed on\n// every conduit). No DOM: the same numbers on the server and in the browser.\n\n/** How the nodes are placed around the hub. */\nexport type FlowLayout = \"radial\" | \"top-to-bottom\" | \"left-to-right\";\n/** The shape of a conduit: an S-shaped cubic Bézier, or a straight line. */\nexport type FlowConduit = \"bezier\" | \"straight\";\n\nexport interface FlowPoint {\n x: number;\n y: number;\n}\n\nexport interface FlowGeometry {\n /** The box: 100 units wide, `height` units high (the stage keeps this aspect ratio, so nothing is distorted). */\n width: number;\n height: number;\n hub: FlowPoint;\n nodes: FlowPoint[];\n /** One path per node, from the node to the hub. */\n paths: string[];\n /** The length of each path, in units. */\n lengths: number[];\n}\n\n/** The box height per layout (the width is always 100). */\nexport const FLOW_HEIGHT: Record<FlowLayout, number> = { radial: 60, \"top-to-bottom\": 92, \"left-to-right\": 56 };\n\nconst r2 = (n: number) => Math.round(n * 100) / 100;\nconst pt = (p: FlowPoint) => `${r2(p.x)} ${r2(p.y)}`;\n\nfunction cubicLength(a: FlowPoint, b: FlowPoint, c: FlowPoint, d: FlowPoint): number {\n let length = 0;\n let prev = a;\n for (let i = 1; i <= 32; i++) {\n const t = i / 32;\n const u = 1 - t;\n const p = {\n x: u * u * u * a.x + 3 * u * u * t * b.x + 3 * u * t * t * c.x + t * t * t * d.x,\n y: u * u * u * a.y + 3 * u * u * t * b.y + 3 * u * t * t * c.y + t * t * t * d.y,\n };\n length += Math.hypot(p.x - prev.x, p.y - prev.y);\n prev = p;\n }\n return length;\n}\n\n/** The positions of `count` nodes around the hub. */\nexport function flowPositions(layout: FlowLayout, count: number): { hub: FlowPoint; nodes: FlowPoint[] } {\n const height = FLOW_HEIGHT[layout];\n const n = Math.max(0, count);\n if (layout === \"top-to-bottom\") {\n return { hub: { x: 50, y: 72 }, nodes: Array.from({ length: n }, (_, i) => ({ x: ((i + 0.5) * 100) / n, y: 13 })) };\n }\n if (layout === \"left-to-right\") {\n return { hub: { x: 80, y: height / 2 }, nodes: Array.from({ length: n }, (_, i) => ({ x: 12, y: ((i + 0.5) * height) / n })) };\n }\n // Radial: on an ellipse around the centre; an even count puts two nodes level with the hub (more room for labels).\n const hub = { x: 50, y: height / 2 };\n const start = n % 2 === 0 ? -90 + 180 / Math.max(n, 1) : -90;\n return {\n hub,\n nodes: Array.from({ length: n }, (_, i) => {\n const a = ((start + (360 / n) * i) * Math.PI) / 180;\n return { x: hub.x + 37 * Math.cos(a), y: hub.y + 20 * Math.sin(a) };\n }),\n };\n}\n\n/** The whole geometry of a flow: positions, conduits (node → hub) and their lengths. */\nexport function flowGeometry(layout: FlowLayout, count: number, conduit: FlowConduit = \"bezier\"): FlowGeometry {\n const { hub, nodes } = flowPositions(layout, count);\n const paths: string[] = [];\n const lengths: number[] = [];\n for (const p of nodes) {\n // Each conduit lands on its own point of the hub (a fan), so the lines stay apart where they meet.\n const end =\n layout === \"top-to-bottom\"\n ? { x: hub.x + (p.x - hub.x) * 0.18, y: hub.y }\n : layout === \"left-to-right\"\n ? { x: hub.x, y: hub.y + (p.y - hub.y) * 0.18 }\n : hub;\n if (conduit === \"straight\") {\n paths.push(`M${pt(p)} L${pt(end)}`);\n lengths.push(Math.hypot(end.x - p.x, end.y - p.y));\n continue;\n }\n let c1: FlowPoint;\n let c2: FlowPoint;\n if (layout === \"top-to-bottom\") {\n const mid = (p.y + end.y) / 2;\n c1 = { x: p.x, y: mid };\n c2 = { x: end.x, y: mid };\n } else if (layout === \"left-to-right\") {\n const mid = (p.x + end.x) / 2;\n c1 = { x: mid, y: p.y };\n c2 = { x: mid, y: end.y };\n } else {\n // An S: leave the node bending one way, reach the hub bending the other.\n const dx = end.x - p.x;\n const dy = end.y - p.y;\n const len = Math.hypot(dx, dy) || 1;\n const k = 0.14 * len;\n const nx = -dy / len;\n const ny = dx / len;\n c1 = { x: p.x + dx * 0.35 + nx * k, y: p.y + dy * 0.35 + ny * k };\n c2 = { x: p.x + dx * 0.65 - nx * k, y: p.y + dy * 0.65 - ny * k };\n }\n paths.push(`M${pt(p)} C${pt(c1)} ${pt(c2)} ${pt(end)}`);\n lengths.push(cubicLength(p, c1, c2, end));\n }\n return { width: 100, height: FLOW_HEIGHT[layout], hub, nodes, paths, lengths };\n}\n"
180
+ },
181
+ {
182
+ "path": "engine-flow/index.ts",
183
+ "content": "/** @krizaka/ui/engine-flow — `EngineFlow`: an engine at the hub, the apps it serves around it, what travels between them (SVG + CSS, no animation frame). Client. */\nexport { EngineFlow, type EngineFlowHub, type EngineFlowNode, type EngineFlowProps } from \"./engine-flow\";\nexport { FLOW_HEIGHT, type FlowConduit, type FlowGeometry, flowGeometry, type FlowLayout, type FlowPoint,flowPositions } from \"./geometry\";\n"
184
+ },
185
+ {
186
+ "path": "engine-flow/meta.ts",
187
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Engine flow\",\n summary:\n \"An engine at the hub and the apps it serves around it, with what travels between them: photons going in (requests) and coming back (results) along each conduit — radial on a wide screen, top to bottom on a phone, planned apps dim and still.\",\n status: \"beta\",\n category: \"data-display\",\n platforms: \"web\",\n parity: {\n native: { status: \"n/a\", reason: \"A diagram of a public site's page; an app screen lists the same facts.\" , use: \"list\" },\n desktop: \"web\",\n },\n whenToUse: [\n \"Showing one platform and the products built on it, in one glance: « one engine, the products it powers ».\",\n \"Saying which way data goes per app — in, back, or both — only where it really runs; what is planned is drawn planned.\",\n ],\n whenNotToUse: [\n { when: \"A sequence of steps a reader follows in order.\", use: \"stepper\" },\n { when: \"Entry points to pages, where the cards are the content.\", use: \"card-grid\" },\n { when: \"A chart of figures over time.\" },\n ],\n bestPractices: [\n \"Give each node its brand scope (`className=\\\"brand-<id>\\\"`) and the figure the hub's: photons going in wear the app's colour, those coming back the engine's.\",\n \"Draw only what runs: `flow=\\\"both\\\"` where results really come back, `planned` (with a `status` word) where nothing is wired yet.\",\n \"Keep a text version (a list) in the page; give each node a one-line `description` and its `href`.\",\n \"Five to eight nodes: radial reads at a glance; on a phone the default `compactLayout` puts them in a row above the hub.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Tab\", action: \"Goes through the nodes, then the link of the detail line.\" },\n { keys: \"Enter / Space\", action: \"Chooses the node: the detail line says its description and its link.\" },\n ],\n notes: [\n \"The drawing (conduits, photons) is hidden from assistive technology; each node is a button named by its label, `aria-pressed` when chosen.\",\n \"The detail line is a polite live region: pointing at or focusing a node announces its line.\",\n \"Motion is CSS (motion.css): paused off screen (MotionObserver), held while the page settles, a still frame under reduced motion. No animation frame, no canvas.\",\n \"The stage keeps its aspect ratio and the detail line its height: no layout shift.\",\n ],\n },\n related: [\"card-grid\", \"glow-card\", \"iso\", \"number-ticker\"],\n web: {\n imports: [\"EngineFlow\"],\n examples: [\n { name: \"radial\", title: \"One engine, the products it powers\", description: \"Radial on a wide screen: both ways where results come back, planned products dim and still.\" },\n { name: \"compact\", title: \"On a phone\", description: \"Top to bottom: the products in a row above, flowing down into the engine; the detail line names the chosen one.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
188
+ },
189
+ {
190
+ "path": "meta.ts",
191
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"chip-row\"\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 | \"cover-hero\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"engine-flow\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"language-switch\"\n | \"media-gallery\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"opening-hours\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"service-card\"\n | \"settings-layout\"\n | \"account-menu\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot-picker\"\n | \"slot\"\n | \"spinner\"\n | \"sticky-action\"\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"
192
+ }
193
+ ],
194
+ "dependencies": [],
195
+ "registryDependencies": [
196
+ "cn"
197
+ ],
198
+ "demo": {
199
+ "path": "examples/engine-flow/radial.tsx",
200
+ "content": "import { ProductLogo } from \"@krizaka/ui\";\nimport { EngineFlow } from \"@krizaka/ui/engine-flow\";\n\nconst products = [\n { id: \"orochia\", label: \"Orochia\", flow: \"both\", description: \"Search and recommendations; insights come back.\" },\n { id: \"yomizaka\", label: \"Yomizaka\", flow: \"both\", description: \"Reads the lease and checks each passage.\" },\n { id: \"kachizaka\", label: \"Kachizaka\", flow: \"both\", description: \"Match facts, read through a connector.\" },\n { id: \"akinaza\", label: \"Akinaza\", flow: \"in\", planned: true, description: \"The tax assistant, not wired yet.\" },\n { id: \"yasakia\", label: \"Yasakia\", flow: \"in\", planned: true, description: \"The matching agent, not wired yet.\" },\n { id: \"yororis\", label: \"Yororis\", flow: \"in\", planned: true, description: \"The listening pack, not wired yet.\" },\n] as const;\n\n// One engine at the hub, the products it powers around it: both ways where results come back, planned ones still.\nexport default function EngineFlowRadial() {\n return (\n <EngineFlow\n className=\"brand-orazaka\"\n label=\"Orazaka and the products it powers\"\n hub={{ label: \"Orazaka\", caption: \"the engine\", mark: <ProductLogo id=\"orazaka\" size={56} animated={false} /> }}\n hint=\"Point at a product to see what it uses the engine for.\"\n defaultActive=\"orochia\"\n nodes={products.map((p) => ({\n ...p,\n className: `brand-${p.id}`,\n mark: <ProductLogo id={p.id} size={32} animated={false} />,\n href: `https://dev.${p.id}.com`,\n linkLabel: `dev.${p.id}.com`,\n status: \"planned\" in p ? \"planned\" : undefined,\n }))}\n />\n );\n}\n"
201
+ },
202
+ "props": [
203
+ {
204
+ "component": "EngineFlow",
205
+ "description": "An engine at the hub and the apps it serves, with what travels between them.",
206
+ "props": [
207
+ {
208
+ "name": "hub",
209
+ "type": "EngineFlowHub",
210
+ "default": null,
211
+ "description": "The engine, at the hub.",
212
+ "required": true
213
+ },
214
+ {
215
+ "name": "label",
216
+ "type": "string",
217
+ "default": null,
218
+ "description": "What the figure shows (\"Orazaka and the products it powers\"), passed translated.",
219
+ "required": true
220
+ },
221
+ {
222
+ "name": "nodes",
223
+ "type": "readonly EngineFlowNode[]",
224
+ "default": null,
225
+ "description": "The apps around it.",
226
+ "required": true
227
+ },
228
+ {
229
+ "name": "compactLayout",
230
+ "type": "false | FlowLayout",
231
+ "default": "top-to-bottom",
232
+ "description": "The layout under a 560 px wide container (a phone); `false` keeps `layout` everywhere.",
233
+ "required": false
234
+ },
235
+ {
236
+ "name": "conduit",
237
+ "type": "FlowConduit",
238
+ "default": "bezier",
239
+ "description": "The conduits' shape.",
240
+ "required": false
241
+ },
242
+ {
243
+ "name": "defaultActive",
244
+ "type": "string",
245
+ "default": null,
246
+ "description": "The node shown in the detail line at first.",
247
+ "required": false
248
+ },
249
+ {
250
+ "name": "hint",
251
+ "type": "ReactNode",
252
+ "default": null,
253
+ "description": "The detail line before a node is chosen (\"Point at a product\").",
254
+ "required": false
255
+ },
256
+ {
257
+ "name": "layout",
258
+ "type": "FlowLayout",
259
+ "default": "radial",
260
+ "description": "How the nodes sit around the hub.",
261
+ "required": false
262
+ },
263
+ {
264
+ "name": "particles",
265
+ "type": "\"none\" | \"photons\"",
266
+ "default": "photons",
267
+ "description": "`photons` travel the conduits; `none` draws them still.",
268
+ "required": false
269
+ },
270
+ {
271
+ "name": "speed",
272
+ "type": "number",
273
+ "default": "16",
274
+ "description": "Photon speed, in units per second (the stage is 100 units wide).",
275
+ "required": false
276
+ }
277
+ ]
278
+ }
279
+ ]
280
+ }
@@ -134,7 +134,7 @@
134
134
  },
135
135
  {
136
136
  "path": "meta.ts",
137
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"chip-row\"\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 | \"cover-hero\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"language-switch\"\n | \"media-gallery\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"opening-hours\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"service-card\"\n | \"settings-layout\"\n | \"account-menu\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot-picker\"\n | \"slot\"\n | \"spinner\"\n | \"sticky-action\"\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"
137
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"chip-row\"\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 | \"cover-hero\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"engine-flow\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"language-switch\"\n | \"media-gallery\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"opening-hours\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"service-card\"\n | \"settings-layout\"\n | \"account-menu\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot-picker\"\n | \"slot\"\n | \"spinner\"\n | \"sticky-action\"\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"
138
138
  }
139
139
  ],
140
140
  "dependencies": [],
@@ -0,0 +1,4 @@
1
+ import type { ComponentType } from "react";
2
+ /** A named example of the @krizaka/ui registry; `defaultOpen` opens what it opens (a dialog, a menu, toasts) at load. */
3
+ declare const Example: ComponentType<{ defaultOpen?: boolean }>;
4
+ export default Example;
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ // registry/examples/chat/native/assistant.tsx
4
+ import { ComposerMeta, MessageBubble, MessageComposer } from "@krizaka/ui/native";
5
+ import { Markdown } from "@krizaka/ui/native/markdown";
6
+ import { useState } from "react";
7
+ import { View } from "react-native";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ function NativeChatAssistant() {
10
+ const [draft, setDraft] = useState("");
11
+ return /* @__PURE__ */ jsxs(View, { style: { width: 360, gap: 4 }, children: [
12
+ /* @__PURE__ */ jsxs(View, { style: { padding: 12, gap: 4 }, children: [
13
+ /* @__PURE__ */ jsx(MessageBubble, { mine: true, text: "Summarise my lease", time: "19:05" }),
14
+ /* @__PURE__ */ jsx(MessageBubble, { time: "19:05", text: "Summary of the lease", children: /* @__PURE__ */ jsx(Markdown, { children: "**Rent** is $1,150 a month.\n\n- Term: 12 months\n- Deposit: not allowed in Qu\xE9bec" }) })
15
+ ] }),
16
+ /* @__PURE__ */ jsx(
17
+ MessageComposer,
18
+ {
19
+ value: draft,
20
+ onValueChange: setDraft,
21
+ onSend: () => setDraft(""),
22
+ placeholder: "Message",
23
+ sendLabel: "Send",
24
+ meta: /* @__PURE__ */ jsx(ComposerMeta, { model: { label: "Local model", onPress: () => {
25
+ }, accessibilityLabel: "Model: Local model" }, status: "0 bytes left your network" })
26
+ }
27
+ )
28
+ ] });
29
+ }
30
+ export {
31
+ NativeChatAssistant as default
32
+ };
@@ -0,0 +1,28 @@
1
+ import { ComposerMeta, MessageBubble, MessageComposer } from "@krizaka/ui/native";
2
+ import { Markdown } from "@krizaka/ui/native/markdown";
3
+ import { useState } from "react";
4
+ import { View } from "react-native";
5
+
6
+ // An assistant's answer: Markdown in the same bubble as any message, and above the composer the model in use and, only
7
+ // because the app computed it true, a privacy line.
8
+ export default function NativeChatAssistant() {
9
+ const [draft, setDraft] = useState("");
10
+ return (
11
+ <View style={{ width: 360, gap: 4 }}>
12
+ <View style={{ padding: 12, gap: 4 }}>
13
+ <MessageBubble mine text="Summarise my lease" time="19:05" />
14
+ <MessageBubble time="19:05" text="Summary of the lease">
15
+ <Markdown>{"**Rent** is $1,150 a month.\n\n- Term: 12 months\n- Deposit: not allowed in Québec"}</Markdown>
16
+ </MessageBubble>
17
+ </View>
18
+ <MessageComposer
19
+ value={draft}
20
+ onValueChange={setDraft}
21
+ onSend={() => setDraft("")}
22
+ placeholder="Message"
23
+ sendLabel="Send"
24
+ meta={<ComposerMeta model={{ label: "Local model", onPress: () => {}, accessibilityLabel: "Model: Local model" }} status="0 bytes left your network" />}
25
+ />
26
+ </View>
27
+ );
28
+ }
@@ -0,0 +1,4 @@
1
+ import type { ComponentType } from "react";
2
+ /** A named example of the @krizaka/ui registry; `defaultOpen` opens what it opens (a dialog, a menu, toasts) at load. */
3
+ declare const Example: ComponentType<{ defaultOpen?: boolean }>;
4
+ export default Example;
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ // registry/examples/engine-flow/compact.tsx
4
+ import { ProductLogo } from "@krizaka/ui";
5
+ import { EngineFlow } from "@krizaka/ui/engine-flow";
6
+ import { jsx } from "react/jsx-runtime";
7
+ var ids = ["orochia", "yomizaka", "kachizaka", "akinaza", "yasakia", "yororis"];
8
+ function EngineFlowCompact() {
9
+ return /* @__PURE__ */ jsx("div", { style: { maxWidth: 360 }, children: /* @__PURE__ */ jsx(
10
+ EngineFlow,
11
+ {
12
+ className: "brand-orazaka",
13
+ label: "Orazaka and the products it powers",
14
+ layout: "top-to-bottom",
15
+ compactLayout: false,
16
+ hub: { label: "Orazaka", mark: /* @__PURE__ */ jsx(ProductLogo, { id: "orazaka", size: 44, animated: false }) },
17
+ hint: "Tap a product.",
18
+ defaultActive: "yomizaka",
19
+ nodes: ids.map((id, i) => ({
20
+ id,
21
+ label: id[0].toUpperCase() + id.slice(1),
22
+ className: `brand-${id}`,
23
+ mark: /* @__PURE__ */ jsx(ProductLogo, { id, size: 24, animated: false }),
24
+ flow: i < 3 ? "both" : "in",
25
+ planned: i >= 3,
26
+ description: i < 3 ? "Requests in, results back." : "Planned: not wired yet."
27
+ }))
28
+ }
29
+ ) });
30
+ }
31
+ export {
32
+ EngineFlowCompact as default
33
+ };
@@ -0,0 +1,30 @@
1
+ import { ProductLogo } from "@krizaka/ui";
2
+ import { EngineFlow } from "@krizaka/ui/engine-flow";
3
+
4
+ const ids = ["orochia", "yomizaka", "kachizaka", "akinaza", "yasakia", "yororis"] as const;
5
+
6
+ // On a phone: the products in a row above, flowing down into the engine; the detail line names the chosen one.
7
+ export default function EngineFlowCompact() {
8
+ return (
9
+ <div style={{ maxWidth: 360 }}>
10
+ <EngineFlow
11
+ className="brand-orazaka"
12
+ label="Orazaka and the products it powers"
13
+ layout="top-to-bottom"
14
+ compactLayout={false}
15
+ hub={{ label: "Orazaka", mark: <ProductLogo id="orazaka" size={44} animated={false} /> }}
16
+ hint="Tap a product."
17
+ defaultActive="yomizaka"
18
+ nodes={ids.map((id, i) => ({
19
+ id,
20
+ label: id[0]!.toUpperCase() + id.slice(1),
21
+ className: `brand-${id}`,
22
+ mark: <ProductLogo id={id} size={24} animated={false} />,
23
+ flow: i < 3 ? "both" : "in",
24
+ planned: i >= 3,
25
+ description: i < 3 ? "Requests in, results back." : "Planned: not wired yet.",
26
+ }))}
27
+ />
28
+ </div>
29
+ );
30
+ }
@@ -0,0 +1,4 @@
1
+ import type { ComponentType } from "react";
2
+ /** A named example of the @krizaka/ui registry; `defaultOpen` opens what it opens (a dialog, a menu, toasts) at load. */
3
+ declare const Example: ComponentType<{ defaultOpen?: boolean }>;
4
+ export default Example;
@@ -0,0 +1,37 @@
1
+ "use client";
2
+
3
+ // registry/examples/engine-flow/radial.tsx
4
+ import { ProductLogo } from "@krizaka/ui";
5
+ import { EngineFlow } from "@krizaka/ui/engine-flow";
6
+ import { jsx } from "react/jsx-runtime";
7
+ var products = [
8
+ { id: "orochia", label: "Orochia", flow: "both", description: "Search and recommendations; insights come back." },
9
+ { id: "yomizaka", label: "Yomizaka", flow: "both", description: "Reads the lease and checks each passage." },
10
+ { id: "kachizaka", label: "Kachizaka", flow: "both", description: "Match facts, read through a connector." },
11
+ { id: "akinaza", label: "Akinaza", flow: "in", planned: true, description: "The tax assistant, not wired yet." },
12
+ { id: "yasakia", label: "Yasakia", flow: "in", planned: true, description: "The matching agent, not wired yet." },
13
+ { id: "yororis", label: "Yororis", flow: "in", planned: true, description: "The listening pack, not wired yet." }
14
+ ];
15
+ function EngineFlowRadial() {
16
+ return /* @__PURE__ */ jsx(
17
+ EngineFlow,
18
+ {
19
+ className: "brand-orazaka",
20
+ label: "Orazaka and the products it powers",
21
+ hub: { label: "Orazaka", caption: "the engine", mark: /* @__PURE__ */ jsx(ProductLogo, { id: "orazaka", size: 56, animated: false }) },
22
+ hint: "Point at a product to see what it uses the engine for.",
23
+ defaultActive: "orochia",
24
+ nodes: products.map((p) => ({
25
+ ...p,
26
+ className: `brand-${p.id}`,
27
+ mark: /* @__PURE__ */ jsx(ProductLogo, { id: p.id, size: 32, animated: false }),
28
+ href: `https://dev.${p.id}.com`,
29
+ linkLabel: `dev.${p.id}.com`,
30
+ status: "planned" in p ? "planned" : void 0
31
+ }))
32
+ }
33
+ );
34
+ }
35
+ export {
36
+ EngineFlowRadial as default
37
+ };