@krizaka/ui 3.9.0 → 3.11.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 (195) hide show
  1. package/README.md +3 -1
  2. package/dist/blocks.css +9 -2
  3. package/dist/chunk-HG65YAV4.js +379 -0
  4. package/dist/chunk-HG65YAV4.js.map +1 -0
  5. package/dist/chunk-OT6HX3PN.js +85 -0
  6. package/dist/chunk-OT6HX3PN.js.map +1 -0
  7. package/dist/index.js +9 -367
  8. package/dist/index.js.map +1 -1
  9. package/dist/link-card.d.ts +7 -1
  10. package/dist/link-card.js +8 -5
  11. package/dist/link-card.js.map +1 -1
  12. package/dist/native.d.ts +48 -1
  13. package/dist/native.js +374 -257
  14. package/dist/native.js.map +1 -1
  15. package/dist/nav-bar.d.ts +86 -3
  16. package/dist/nav-bar.js +88 -2
  17. package/dist/nav-bar.js.map +1 -1
  18. package/dist/showcase.d.ts +69 -0
  19. package/dist/showcase.js +218 -0
  20. package/dist/showcase.js.map +1 -0
  21. package/dist/site-footer.d.ts +172 -0
  22. package/dist/site-footer.js +105 -0
  23. package/dist/site-footer.js.map +1 -0
  24. package/dist/sites-CgJrxejx.d.ts +32 -0
  25. package/dist/sites.d.ts +64 -0
  26. package/dist/sites.js +69 -0
  27. package/dist/sites.js.map +1 -0
  28. package/package.json +47 -9
  29. package/registry/achievement.json +1 -1
  30. package/registry/action-bar.json +1 -1
  31. package/registry/activity-feed.json +1 -1
  32. package/registry/alert.json +1 -1
  33. package/registry/app-shell.json +1 -1
  34. package/registry/argument-card.json +1 -1
  35. package/registry/audio-player.json +1 -1
  36. package/registry/auth-layout.json +1 -1
  37. package/registry/avatar.json +1 -1
  38. package/registry/badge.json +1 -1
  39. package/registry/button.json +1 -1
  40. package/registry/card-grid.json +11 -2
  41. package/registry/card.json +1 -1
  42. package/registry/catalogue-page.json +1 -1
  43. package/registry/changelog-page.json +1 -1
  44. package/registry/chat.json +1 -1
  45. package/registry/checkbox.json +1 -1
  46. package/registry/chip-link.json +1 -1
  47. package/registry/chip.json +1 -1
  48. package/registry/choice-grid.json +1 -1
  49. package/registry/code-block.json +1 -1
  50. package/registry/combobox.json +1 -1
  51. package/registry/command.json +1 -1
  52. package/registry/component-page.json +1 -1
  53. package/registry/confirm-button.json +1 -1
  54. package/registry/contact-page.json +1 -1
  55. package/registry/continue-prompt.json +1 -1
  56. package/registry/countdown.json +1 -1
  57. package/registry/cta-panel.json +1 -1
  58. package/registry/dashboard-layout.json +1 -1
  59. package/registry/data-table.json +1 -1
  60. package/registry/date-picker.json +1 -1
  61. package/registry/decision-list.json +1 -1
  62. package/registry/description-list.json +1 -1
  63. package/registry/detail-layout.json +1 -1
  64. package/registry/dialog.json +1 -1
  65. package/registry/docs-article-page.json +1 -1
  66. package/registry/docs-hub-page.json +1 -1
  67. package/registry/docs-layout.json +1 -1
  68. package/registry/dropdown-menu.json +1 -1
  69. package/registry/empty-state.json +1 -1
  70. package/registry/error-page.json +1 -1
  71. package/registry/examples/card-grid/aligned.d.ts +4 -0
  72. package/registry/examples/card-grid/aligned.js +18 -0
  73. package/registry/examples/card-grid/aligned.tsx +16 -0
  74. package/registry/examples/nav-bar/family-admin.d.ts +4 -0
  75. package/registry/examples/nav-bar/family-admin.js +21 -0
  76. package/registry/examples/nav-bar/family-admin.tsx +20 -0
  77. package/registry/examples/nav-bar/family-java.d.ts +4 -0
  78. package/registry/examples/nav-bar/family-java.js +33 -0
  79. package/registry/examples/nav-bar/family-java.tsx +34 -0
  80. package/registry/examples/nav-bar/family-product.d.ts +4 -0
  81. package/registry/examples/nav-bar/family-product.js +35 -0
  82. package/registry/examples/nav-bar/family-product.tsx +39 -0
  83. package/registry/examples/nav-bar/family-ui.d.ts +4 -0
  84. package/registry/examples/nav-bar/family-ui.js +34 -0
  85. package/registry/examples/nav-bar/family-ui.tsx +35 -0
  86. package/registry/examples/nav-bar/family-www.d.ts +4 -0
  87. package/registry/examples/nav-bar/family-www.js +34 -0
  88. package/registry/examples/nav-bar/family-www.tsx +36 -0
  89. package/registry/examples/showcase/native/slides.d.ts +4 -0
  90. package/registry/examples/showcase/native/slides.dark.png +0 -0
  91. package/registry/examples/showcase/native/slides.js +27 -0
  92. package/registry/examples/showcase/native/slides.light.png +0 -0
  93. package/registry/examples/showcase/native/slides.tsx +28 -0
  94. package/registry/examples/showcase/prompt-image.d.ts +4 -0
  95. package/registry/examples/showcase/prompt-image.js +29 -0
  96. package/registry/examples/showcase/prompt-image.tsx +28 -0
  97. package/registry/examples/showcase/slides.d.ts +4 -0
  98. package/registry/examples/showcase/slides.js +29 -0
  99. package/registry/examples/showcase/slides.tsx +24 -0
  100. package/registry/examples/site-footer/company.d.ts +4 -0
  101. package/registry/examples/site-footer/company.js +26 -0
  102. package/registry/examples/site-footer/company.tsx +23 -0
  103. package/registry/examples/site-footer/open-source-fr.d.ts +4 -0
  104. package/registry/examples/site-footer/open-source-fr.js +17 -0
  105. package/registry/examples/site-footer/open-source-fr.tsx +17 -0
  106. package/registry/examples/site-footer/product.d.ts +4 -0
  107. package/registry/examples/site-footer/product.js +22 -0
  108. package/registry/examples/site-footer/product.tsx +19 -0
  109. package/registry/examples/sites/family.d.ts +4 -0
  110. package/registry/examples/sites/family.js +20 -0
  111. package/registry/examples/sites/family.tsx +30 -0
  112. package/registry/examples/sites/french.d.ts +4 -0
  113. package/registry/examples/sites/french.js +25 -0
  114. package/registry/examples/sites/french.tsx +25 -0
  115. package/registry/field.json +1 -1
  116. package/registry/file-upload.json +1 -1
  117. package/registry/filter-bar.json +1 -1
  118. package/registry/form.json +1 -1
  119. package/registry/gallery-page.json +1 -1
  120. package/registry/glow-card.json +1 -1
  121. package/registry/goal-gauge.json +1 -1
  122. package/registry/how-it-works-page.json +1 -1
  123. package/registry/image-cropper.json +1 -1
  124. package/registry/index.json +65 -7
  125. package/registry/iso.json +1 -1
  126. package/registry/kanban.json +1 -1
  127. package/registry/kbd.json +1 -1
  128. package/registry/landing-page.json +1 -1
  129. package/registry/landscape.json +1 -1
  130. package/registry/legal-page.json +1 -1
  131. package/registry/link-card.json +18 -3
  132. package/registry/list.json +1 -1
  133. package/registry/loader.json +1 -1
  134. package/registry/longform-page.json +1 -1
  135. package/registry/map.json +2 -2
  136. package/registry/markdown.json +1 -1
  137. package/registry/marketing-layout.json +1 -1
  138. package/registry/marquee.json +1 -1
  139. package/registry/mascot-quote.json +1 -1
  140. package/registry/mascot-state.json +1 -1
  141. package/registry/mascots.json +1 -1
  142. package/registry/match-dialog.json +1 -1
  143. package/registry/media-capture.json +1 -1
  144. package/registry/meter.json +1 -1
  145. package/registry/nav-bar.json +257 -13
  146. package/registry/number-ticker.json +1 -1
  147. package/registry/page-header.json +1 -1
  148. package/registry/page-hero.json +1 -1
  149. package/registry/page.json +1 -1
  150. package/registry/pagination.json +1 -1
  151. package/registry/photo-grid.json +1 -1
  152. package/registry/pointer-light.json +1 -1
  153. package/registry/popover.json +1 -1
  154. package/registry/pricing-page.json +1 -1
  155. package/registry/product-page.json +1 -1
  156. package/registry/profile-card.json +1 -1
  157. package/registry/progress.json +1 -1
  158. package/registry/promise-card.json +1 -1
  159. package/registry/radio-group.json +1 -1
  160. package/registry/recipes.json +1 -1
  161. package/registry/scroll-stage.json +1 -1
  162. package/registry/search-field.json +1 -1
  163. package/registry/section-backdrop.json +1 -1
  164. package/registry/separator.json +1 -1
  165. package/registry/settings-layout.json +1 -1
  166. package/registry/showcase.json +421 -0
  167. package/registry/site-footer.json +389 -0
  168. package/registry/sites.json +93 -0
  169. package/registry/skeleton.json +1 -1
  170. package/registry/skip-link.json +1 -1
  171. package/registry/slider.json +1 -1
  172. package/registry/slot.json +1 -1
  173. package/registry/spinner.json +1 -1
  174. package/registry/split-view.json +1 -1
  175. package/registry/stage-banner.json +1 -1
  176. package/registry/stat.json +1 -1
  177. package/registry/status-page.json +1 -1
  178. package/registry/step-progress.json +1 -1
  179. package/registry/stepper.json +1 -1
  180. package/registry/swipe-deck.json +1 -1
  181. package/registry/switch.json +1 -1
  182. package/registry/tabs.json +1 -1
  183. package/registry/tag-input.json +1 -1
  184. package/registry/tag-selector.json +1 -1
  185. package/registry/terminal-replay.json +1 -1
  186. package/registry/theme.json +1 -1
  187. package/registry/toast.json +1 -1
  188. package/registry/toc.json +1 -1
  189. package/registry/tooltip.json +1 -1
  190. package/registry/tree-view.json +1 -1
  191. package/registry/verified-badge.json +1 -1
  192. package/registry/vertical-video.json +1 -1
  193. package/registry/video-player.json +1 -1
  194. package/registry/video-trimmer.json +1 -1
  195. package/registry/virtual.json +1 -1
@@ -2,15 +2,16 @@
2
2
  "name": "nav-bar",
3
3
  "type": "primitive",
4
4
  "title": "Nav bar",
5
- "summary": "The Krizaka navigation bar: a floating pill that tightens as the page scrolls, one light that glides to the item under the pointer or the focus and rests on the current page, accessible menus with rich links, and a mobile panel.",
6
- "description": "The Krizaka navigation bar: a floating pill that tightens as the page scrolls, one light that glides to the item under the pointer or the focus and rests on the current page, accessible menus with rich links, and a mobile panel.",
5
+ "summary": "The Krizaka navigation bar, the same on every site of the family: a floating pill that tightens as the page scrolls, the family switcher on the brand, one light that glides to the item under the pointer or the focus and rests on the current page, accessible menus with rich links, and a mobile panel.",
6
+ "description": "The Krizaka navigation bar, the same on every site of the family: a floating pill that tightens as the page scrolls, the family switcher on the brand, one light that glides to the item under the pointer or the focus and rests on the current page, accessible menus with rich links, and a mobile panel.",
7
7
  "status": "beta",
8
8
  "deprecated": null,
9
9
  "category": "navigation",
10
10
  "platforms": "web",
11
11
  "whenToUse": [
12
12
  "The top navigation of a site or a product: a brand, a few links, one or two menus, actions on the right.",
13
- "A section bar inside an app (`floating={false}`): videos, earnings, auctions."
13
+ "A section bar inside an app (`floating={false}`): videos, earnings, auctions.",
14
+ "Every site of the Krizaka family (www, ui., java., dev.<product>, admin.<product>): the brand and `NavBar.Family` on the left, at most four of the site's sections in the middle, the tools on the right."
14
15
  ],
15
16
  "whenNotToUse": [
16
17
  {
@@ -24,6 +25,10 @@
24
25
  {
25
26
  "when": "For search and commands across the app.",
26
27
  "use": "command"
28
+ },
29
+ {
30
+ "when": "To link to another site of the family from the bar's middle: the family switcher holds them.",
31
+ "use": "sites"
27
32
  }
28
33
  ],
29
34
  "bestPractices": [
@@ -31,7 +36,10 @@
31
36
  "Mark the current page with `active`: the light rests on it, `aria-current=\"page\"` says it.",
32
37
  "Use your router's link with `asChild` on `NavBar.Link`, `NavBar.MenuLink` and `NavBar.MobileLink`.",
33
38
  "One primary action at most in `NavBar.Actions` (a `Button` `size=\"sm\"` `shape=\"pill\"`).",
34
- "Wrap it in a `sticky top-3` (floating) or `sticky top-0` container to keep it on screen."
39
+ "Wrap it in a `sticky top-3` (floating) or `sticky top-0` container to keep it on screen.",
40
+ "Three zones on every Krizaka site: who I am (`NavBar.Brand` + `NavBar.Family current=\"<site>\"`, then `NavBar.Badge` with the version or `DEV · v1.2.3`), this site (at most four sections, never a link to another site), tools (search when there is something to search, `NavBar.Repo`, theme, language, one primary action).",
41
+ "Place a second `NavBar.Family` as the last child of `NavBar.Mobile`: below `md` the family is the panel's last group.",
42
+ "Pass the family's words translated: `labels={familyLabels[locale]}` from `@krizaka/ui/sites`."
35
43
  ],
36
44
  "accessibility": {
37
45
  "keyboard": [
@@ -53,16 +61,24 @@
53
61
  },
54
62
  {
55
63
  "keys": "Esc",
56
- "action": "Closes the menu or the mobile panel and returns the focus to its trigger."
64
+ "action": "Closes the menu, the family or the mobile panel and returns the focus to its trigger."
65
+ },
66
+ {
67
+ "keys": "Enter / Space / ↓ (family)",
68
+ "action": "Opens the family from its chevron; ↑ / ↓ walk the sites. It never opens on hover."
57
69
  }
58
70
  ],
59
71
  "notes": [
60
72
  "A `nav` named by `label`, a list of links and menu triggers (Radix NavigationMenu): `aria-expanded` and `aria-controls` on each trigger.",
61
73
  "The mobile panel is a disclosure (no focus trap): its toggle is named by `openLabel` / `closeLabel` and says `aria-expanded`.",
62
- "The light, the chevron and the tightening do not move under `prefers-reduced-motion`; the current page is tinted before scripts load."
74
+ "The light, the chevron and the tightening do not move under `prefers-reduced-motion`; the current page is tinted before scripts load.",
75
+ "The family is its own `nav` named by `labels.family`; its chevron is a button with the same name, `aria-expanded` and `aria-controls`. Each group is a list named by its heading, the current site says `aria-current=\"true\"` and “You are here”, a site not released says its status in words.",
76
+ "`NavBar.Repo` is an icon link named by `label`; `NavBar.Badge` is plain text (`DEV · v1.2.3`), hidden below `sm`."
63
77
  ]
64
78
  },
65
79
  "related": [
80
+ "sites",
81
+ "site-footer",
66
82
  "tabs",
67
83
  "dropdown-menu",
68
84
  "command",
@@ -112,6 +128,60 @@
112
128
  }
113
129
  ]
114
130
  },
131
+ {
132
+ "component": "NavBarFamily",
133
+ "description": "The family switcher, attached to the brand (place it right after `NavBar.Brand`): a chevron that opens every site of\nKrizaka grouped — Krizaka · Products · Open source · GitHub — the current one marked. It opens on click, Enter, Space\nor ↓ (never on hover); ↑ ↓ walk the sites, Escape closes and returns the focus. Hidden below `md`: place a second\n`NavBar.Family` as the last child of `NavBar.Mobile`, where it renders as the panel's last group.",
134
+ "props": [
135
+ {
136
+ "name": "current",
137
+ "type": "SiteId",
138
+ "default": null,
139
+ "description": "The site this bar belongs to (`@krizaka/ui/sites`): marked in the family, `aria-current=\"true\"`.",
140
+ "required": true
141
+ },
142
+ {
143
+ "name": "className",
144
+ "type": "string",
145
+ "default": null,
146
+ "description": "Classes of the switcher (or, inside `NavBar.Mobile`, of the family's group).",
147
+ "required": false
148
+ },
149
+ {
150
+ "name": "defaultOpen",
151
+ "type": "boolean",
152
+ "default": null,
153
+ "description": "Opens the panel at first render (a story, a screenshot).",
154
+ "required": false
155
+ },
156
+ {
157
+ "name": "labels",
158
+ "type": "FamilyLabels",
159
+ "default": "familyLabels.en",
160
+ "description": "The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.",
161
+ "required": false
162
+ }
163
+ ]
164
+ },
165
+ {
166
+ "component": "NavBarBadge",
167
+ "description": "A small mono badge beside the brand: the environment and the version (`DEV · v1.2.3`). Hidden below `sm`.",
168
+ "props": [
169
+ {
170
+ "name": "env",
171
+ "type": "string",
172
+ "default": null,
173
+ "description": "The environment, when it is not production (`DEV`).",
174
+ "required": false
175
+ },
176
+ {
177
+ "name": "version",
178
+ "type": "string",
179
+ "default": null,
180
+ "description": "The version the site runs (`1.2.3`, shown `v1.2.3`).",
181
+ "required": false
182
+ }
183
+ ]
184
+ },
115
185
  {
116
186
  "component": "NavBarItems",
117
187
  "description": "The desktop navigation: links and menus, with the light that glides between them. Hidden below `md` (see `NavBar.Mobile`).",
@@ -203,6 +273,19 @@
203
273
  "description": "The right side: search, theme, language, the call to action.",
204
274
  "props": []
205
275
  },
276
+ {
277
+ "component": "NavBarRepo",
278
+ "description": "The site's repository on GitHub, an icon link of `NavBar.Actions`.",
279
+ "props": [
280
+ {
281
+ "name": "label",
282
+ "type": "string",
283
+ "default": null,
284
+ "description": "The link's accessible name (“Source on GitHub”) — passed translated.",
285
+ "required": true
286
+ }
287
+ ]
288
+ },
206
289
  {
207
290
  "component": "NavBarMobile",
208
291
  "description": "Below `md`: a toggle, and a panel under the bar with its links (`NavBar.MobileLink`). A disclosure: Escape and a click\non a link close it, the focus returns to the toggle.",
@@ -245,6 +328,46 @@
245
328
  }
246
329
  ],
247
330
  "examples": [
331
+ {
332
+ "name": "family-www",
333
+ "title": "www.krizaka.com",
334
+ "description": "The company's bar: the mark and the family switcher, Products · Open source · Our story, the repository and Contact. Open the chevron: every site of Krizaka, grouped, the current one marked.",
335
+ "path": "examples/nav-bar/family-www.tsx",
336
+ "module": "@krizaka/ui/registry/examples/nav-bar/family-www",
337
+ "code": "import { KrizakaLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\nconst links = [\n { href: \"#products\", label: \"Products\" },\n { href: \"#open-source\", label: \"Open source\" },\n { href: \"#story\", label: \"Our story\" },\n];\n\n// www.krizaka.com: who I am (the mark and the family switcher) · this site (three sections) · tools (the repository,\n// one action). The chevron opens the family; the mobile panel ends with it.\nexport default function NavBarFamilyWww({ defaultOpen = false }: { defaultOpen?: boolean }) {\n return (\n <NavBar.Root label=\"Main\" className={defaultOpen ? \"mb-[30rem]\" : undefined}>\n <NavBar.Brand href=\"#home\"><KrizakaLogo size={22} />Krizaka</NavBar.Brand>\n <NavBar.Family current=\"krizaka\" labels={familyLabels.en} defaultOpen={defaultOpen} />\n <NavBar.Items>\n {links.map((l) => (\n <NavBar.Link key={l.href} href={l.href} active={l.href === \"#products\"}>{l.label}</NavBar.Link>\n ))}\n </NavBar.Items>\n <NavBar.Actions>\n <NavBar.Repo href=\"https://github.com/krizaka/krizaka-com\" label=\"Source on GitHub\" />\n <Button variant=\"primary\" size=\"sm\" shape=\"pill\" className=\"hidden sm:inline-flex\">Contact</Button>\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n {links.map((l) => (\n <NavBar.MobileLink key={l.href} href={l.href}>{l.label}</NavBar.MobileLink>\n ))}\n <NavBar.Family current=\"krizaka\" labels={familyLabels.en} />\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n );\n}\n"
338
+ },
339
+ {
340
+ "name": "family-ui",
341
+ "title": "ui.krizaka.com",
342
+ "description": "Krizaka UI: the family, the version of the packages, Get started · Foundations · Components · Templates, the repository.",
343
+ "path": "examples/nav-bar/family-ui.tsx",
344
+ "module": "@krizaka/ui/registry/examples/nav-bar/family-ui",
345
+ "code": "import { KrizakaLogo } from \"@krizaka/ui\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\nconst links = [\n { href: \"#start\", label: \"Get started\" },\n { href: \"#foundations\", label: \"Foundations\" },\n { href: \"#components\", label: \"Components\" },\n { href: \"#templates\", label: \"Templates\" },\n];\n\n// ui.krizaka.com: Krizaka UI and its family switcher, the version of the packages, four sections, the repository.\nexport default function NavBarFamilyUi() {\n return (\n <NavBar.Root label=\"Main\">\n <NavBar.Brand href=\"#home\"><KrizakaLogo size={22} />Krizaka UI</NavBar.Brand>\n <NavBar.Family current=\"ui\" labels={familyLabels.en} />\n <NavBar.Badge version=\"3.9.0\" />\n <NavBar.Items>\n {links.map((l) => (\n <NavBar.Link key={l.href} href={l.href} active={l.href === \"#components\"}>{l.label}</NavBar.Link>\n ))}\n </NavBar.Items>\n <NavBar.Actions>\n <NavBar.Repo href=\"https://github.com/krizaka/krizaka-ui\" label=\"Source on GitHub\" />\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n {links.map((l) => (\n <NavBar.MobileLink key={l.href} href={l.href}>{l.label}</NavBar.MobileLink>\n ))}\n <NavBar.Family current=\"ui\" labels={familyLabels.en} />\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n );\n}\n"
346
+ },
347
+ {
348
+ "name": "family-java",
349
+ "title": "java.krizaka.com",
350
+ "description": "Krizaka Java: the family, the version of the building blocks, Get started · Building blocks · Guides.",
351
+ "path": "examples/nav-bar/family-java.tsx",
352
+ "module": "@krizaka/ui/registry/examples/nav-bar/family-java",
353
+ "code": "import { KrizakaLogo } from \"@krizaka/ui\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\nconst links = [\n { href: \"#start\", label: \"Get started\" },\n { href: \"#blocks\", label: \"Building blocks\" },\n { href: \"#guides\", label: \"Guides\" },\n];\n\n// java.krizaka.com: Krizaka Java, the version of the building blocks, three sections, the repository.\nexport default function NavBarFamilyJava() {\n return (\n <NavBar.Root label=\"Main\">\n <NavBar.Brand href=\"#home\"><KrizakaLogo size={22} />Krizaka Java</NavBar.Brand>\n <NavBar.Family current=\"java\" labels={familyLabels.en} />\n <NavBar.Badge version=\"0.1.0\" />\n <NavBar.Items>\n {links.map((l) => (\n <NavBar.Link key={l.href} href={l.href} active={l.href === \"#blocks\"}>{l.label}</NavBar.Link>\n ))}\n </NavBar.Items>\n <NavBar.Actions>\n <NavBar.Repo href=\"https://github.com/krizaka/krizaka-build\" label=\"Source on GitHub\" />\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n {links.map((l) => (\n <NavBar.MobileLink key={l.href} href={l.href}>{l.label}</NavBar.MobileLink>\n ))}\n <NavBar.Family current=\"java\" labels={familyLabels.en} />\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n );\n}\n"
354
+ },
355
+ {
356
+ "name": "family-product",
357
+ "title": "dev.<product>",
358
+ "description": "A product's site: its mark and the family, `DEV · v1.2.3`, How it works · Demo · Docs, and “Talk to us” on krizaka.com while sign-up is closed.",
359
+ "path": "examples/nav-bar/family-product.tsx",
360
+ "module": "@krizaka/ui/registry/examples/nav-bar/family-product",
361
+ "code": "import { OrazakaLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\nconst links = [\n { href: \"#how\", label: \"How it works\" },\n { href: \"#demo\", label: \"Demo\" },\n { href: \"#docs\", label: \"Docs\" },\n];\n\n// dev.orazaka.com: the product's mark and the family, `DEV · v1.2.3`, three sections, and “Talk to us” on\n// krizaka.com while sign-up is closed.\nexport default function NavBarFamilyProduct() {\n return (\n <NavBar.Root label=\"Main\">\n <NavBar.Brand href=\"#home\"><OrazakaLogo size={22} />Orazaka</NavBar.Brand>\n <NavBar.Family current=\"orazaka\" labels={familyLabels.en} />\n <NavBar.Badge env=\"DEV\" version=\"1.2.3\" />\n <NavBar.Items>\n {links.map((l) => (\n <NavBar.Link key={l.href} href={l.href} active={l.href === \"#how\"}>{l.label}</NavBar.Link>\n ))}\n </NavBar.Items>\n <NavBar.Actions>\n <NavBar.Repo href=\"https://github.com/krizaka/orazaka\" label=\"Source on GitHub\" />\n <Button asChild variant=\"primary\" size=\"sm\" shape=\"pill\" className=\"hidden sm:inline-flex\">\n <a href=\"https://www.krizaka.com/contact?topic=orazaka\">Talk to us</a>\n </Button>\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n {links.map((l) => (\n <NavBar.MobileLink key={l.href} href={l.href}>{l.label}</NavBar.MobileLink>\n ))}\n <NavBar.Family current=\"orazaka\" labels={familyLabels.en} />\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n );\n}\n"
362
+ },
363
+ {
364
+ "name": "family-admin",
365
+ "title": "admin.<product>",
366
+ "description": "A console: the mark, the family and `DEV`, no sections, a full-width bar.",
367
+ "path": "examples/nav-bar/family-admin.tsx",
368
+ "module": "@krizaka/ui/registry/examples/nav-bar/family-admin",
369
+ "code": "import { OrochiaLogo } from \"@krizaka/ui\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\n// admin.<product>: a console — the mark, the family and `DEV`, no sections, a full-width bar. The family still says\n// where the console belongs; the mobile panel holds only the family.\nexport default function NavBarFamilyAdmin() {\n return (\n <NavBar.Root label=\"Console\" floating={false} compactAfter={false}>\n <NavBar.Brand href=\"#home\"><OrochiaLogo size={22} />Orochia admin</NavBar.Brand>\n <NavBar.Family current=\"orochia\" labels={familyLabels.en} />\n <NavBar.Badge env=\"DEV\" />\n <NavBar.Actions>\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n <NavBar.Family current=\"orochia\" labels={familyLabels.en} />\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n );\n}\n"
370
+ },
248
371
  {
249
372
  "name": "site",
250
373
  "title": "A site's bar",
@@ -273,21 +396,73 @@
273
396
  },
274
397
  "native": null,
275
398
  "files": [
399
+ {
400
+ "path": "family-mark.tsx",
401
+ "content": "// The mark of a site of the family (`@krizaka/ui/sites`): its brand's `ProductLogo`, still (a list of marks never\n// moves), or GitHub's, in the text colour. Shared by `NavBar.Family` and `SiteFooter` — not an entry of its own.\nimport type * as React from \"react\";\n\nimport { ProductLogo } from \"./marks/ProductLogo\";\nimport type { SiteMark } from \"./sites/sites\";\n\n/** GitHub's mark, in the text colour (both themes). */\nexport function GitHubGlyph({ size = 16, className }: { size?: number; className?: string }) {\n return (\n <svg viewBox=\"0 0 16 16\" width={size} height={size} fill=\"currentColor\" aria-hidden className={className}>\n <path d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z\" />\n </svg>\n );\n}\n\n/** A site's mark, decorative (its name is always written beside it). */\nexport function FamilyMark({ mark, size }: { mark: SiteMark; size: number }): React.ReactElement {\n if (mark === \"github\") return <GitHubGlyph size={Math.round(size * 0.8)} />;\n return <ProductLogo id={mark} size={size} animated={false} />;\n}\n"
402
+ },
403
+ {
404
+ "path": "marks/KrizakaLogo.tsx",
405
+ "content": "import { KRIZAKA as G } from \"./krizaka-geometry\";\nimport { a11y, type MarkProps, neutral, useIds } from \"./shared\";\n\nconst CSS = `\n.kzm-spin { transform-origin: 200px 200px; animation: kzm-spin 30s linear infinite; }\n.kzm-pulse { transform-box: fill-box; transform-origin: center; animation: kzm-pulse 3.2s ease-in-out infinite; }\n@keyframes kzm-spin { to { transform: rotate(360deg); } }\n@keyframes kzm-pulse {\n 0%, 100% { filter: drop-shadow(0 0 6px hsla(217, 92%, 53%, .25)); opacity: .9; }\n 50% { filter: drop-shadow(0 0 14px hsla(217, 92%, 53%, .55)); opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) { .kzm-spin, .kzm-pulse { animation: none; } }\n`;\n\n/**\n * The Krizaka mark: an ink hexagonal shield, open at one vertex where a blue node breaks out, around a blue core —\n * the parent of the family (Orochia's coiled serpent, Orazaka's shards). The orbit turns, the node and the core\n * breathe. The shield reads the host's text roles, so it is ink in both themes. Below 48 px it crops to the emblem.\n */\nexport function KrizakaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"kzm\", [\"ink\", \"blue\", \"glow\"] as const);\n const m = (cls: string) => (animated ? 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.ink} gradientUnits=\"userSpaceOnUse\" x1={G.ink.x1} y1={G.ink.y1} x2={G.ink.x2} y2={G.ink.y2}>\n <stop offset=\"0%\" stopColor={neutral(\"--kz-text-secondary\", 60)} />\n <stop offset=\"100%\" stopColor={neutral(\"--kz-text-primary\", 92)} />\n </linearGradient>\n <linearGradient id={id.blue} gradientUnits=\"userSpaceOnUse\" x1={G.blue.x1} y1={G.blue.y1} x2={G.blue.x2} y2={G.blue.y2}>\n {G.blue.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=\"200\" cy=\"200\" r=\"190\" fill={`url(#${id.glow})`} /> : null /* cropped: the halo would show its square */}\n <circle className={m(\"kzm-spin\")} cx=\"200\" cy=\"200\" r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx=\"200\" cy=\"200\" r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <g transform={`translate(${G.offset} ${G.offset})`}>\n <path d={G.shield} stroke={`url(#${id.ink})`} strokeWidth={G.stroke} strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <g className={m(\"kzm-pulse\")}>\n <circle cx={G.node.cx} cy={G.node.cy} r={G.node.r} fill={`url(#${id.blue})`} />\n <circle cx={G.node.cx} cy={G.node.cy} r={G.node.inner} fill={G.highlight} />\n </g>\n <g className={m(\"kzm-pulse\")}>\n <circle cx={G.core.cx} cy={G.core.cy} r={G.core.r} fill={`url(#${id.blue})`} />\n <circle cx={G.core.cx} cy={G.core.cy} r={G.core.inner} fill={G.highlight} />\n </g>\n </g>\n </svg>\n );\n}\n"
406
+ },
407
+ {
408
+ "path": "marks/OrazakaLogo.tsx",
409
+ "content": "import { ORAZAKA as G } from \"./orazaka-geometry\";\nimport { a11y, type MarkProps, neutral, useIds, withAlpha } from \"./shared\";\n\nconst CSS = `\n.ozm-orbit { transform-origin: 200px 200px; animation: ozm-orbit 35s linear infinite; }\n.ozm-core { transform-box: fill-box; transform-origin: center; animation: ozm-core 3.2s ease-in-out infinite; }\n@keyframes ozm-orbit { to { transform: rotate(360deg); } }\n@keyframes ozm-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) { .ozm-orbit, .ozm-core { animation: none; } }\n`;\n\n/**\n * The Orazaka mark: three solid hexagonal shards in the Orazaka orange (the brand's mark\n * gradient of @krizaka/tokens, deep orange → orange, hue 26 — the same in dark and light) orbiting slowly around a core that breathes —\n * the family's grammar (dashed orbit, ring, bold emblem, bright core). Below 48 px it crops to the emblem.\n */\nexport function OrazakaLogo({ size = 32, animated = true, title, className }: MarkProps) {\n const id = useIds(\"ozm\", [\"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.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\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 ? \"ozm-orbit\" : undefined}>\n {G.shards.map((d) => (\n <path key={d} d={d} fill={fill} stroke={fill} strokeWidth={G.shardStroke} strokeLinejoin=\"round\" />\n ))}\n </g>\n <g className={animated ? \"ozm-core\" : undefined}>\n <circle cx={G.center} cy={G.center} r={G.core.r} fill={fill} />\n <circle cx={G.center} cy={G.center} r={G.core.inner} fill={G.highlight} />\n </g>\n </svg>\n );\n}\n"
410
+ },
411
+ {
412
+ "path": "marks/OrochiaLogo.tsx",
413
+ "content": "import { OROCHIA as G } from \"./orochia-geometry\";\nimport { a11y, type MarkProps, neutral, useIds } from \"./shared\";\n\nconst CSS = `\n.orom-spin { transform-origin: 180px 180px; animation: orom-spin 30s linear infinite; }\n.orom-slither { animation: orom-slither 2.4s linear infinite; }\n.orom-pulse { transform-box: view-box; animation: orom-pulse 3.2s ease-in-out infinite; }\n@keyframes orom-spin { to { transform: rotate(360deg); } }\n@keyframes orom-slither { to { stroke-dashoffset: -64; } }\n@keyframes orom-pulse {\n 0%, 100% { filter: drop-shadow(0 0 6px hsla(292, 84%, 61%, .25)); opacity: .9; }\n 50% { filter: drop-shadow(0 0 14px hsla(292, 84%, 61%, .55)); opacity: 1; }\n}\n@media (prefers-reduced-motion: reduce) { .orom-spin, .orom-slither, .orom-pulse { animation: none; } }\n`;\n\n/**\n * The Orochia mark: the serpent (Orochi) coiled into an \"O\" around the flame it guards. The orbit turns, scales slide\n * along the body, the eye and the flame breathe. Below 48 px it crops to the serpent: the orbits would be hairlines.\n */\nexport function OrochiaLogo({ size = 36, animated = true, title, className }: MarkProps) {\n const id = useIds(\"orom\", [\"body\", \"flame\", \"glow\"] as const);\n const m = (cls: string) => (animated ? 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.body} gradientUnits=\"userSpaceOnUse\" x1={G.body.x1} y1={G.body.y1} x2={G.body.x2} y2={G.body.y2}>\n {G.body.stops.map(([offset, color]) => (\n <stop key={offset} offset={offset} stopColor={color} />\n ))}\n </linearGradient>\n <linearGradient id={id.flame} x1=\"50%\" y1=\"100%\" x2=\"50%\" y2=\"0%\">\n {G.flameGradient.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 <g transform={`translate(${G.offset} ${G.offset})`}>\n <circle cx=\"180\" cy=\"180\" r=\"170\" fill={`url(#${id.glow})`} />\n <circle className={m(\"orom-spin\")} cx=\"180\" cy=\"180\" r={G.orbit.r} stroke={neutral(\"--kz-border-strong\", 40)} strokeWidth={G.orbit.width} strokeDasharray={G.orbit.dash} opacity=\".6\" />\n <circle cx=\"180\" cy=\"180\" r={G.ring.r} stroke={neutral(\"--kz-border-default\", 30)} strokeWidth={G.ring.width} opacity=\".35\" />\n <path d={G.arc} stroke={`url(#${id.body})`} strokeWidth=\"22\" strokeLinecap=\"round\" />\n <path className={m(\"orom-slither\")} d={G.arc} stroke={G.scales.color} strokeOpacity={G.scales.opacity} strokeWidth={G.scales.width} strokeLinecap=\"round\" strokeDasharray={G.scales.dash} />\n <path d={G.neck} stroke={`url(#${id.body})`} strokeWidth=\"14\" strokeLinecap=\"round\" />\n <path d={G.tail} stroke={`url(#${id.body})`} strokeWidth=\"6\" strokeLinecap=\"round\" />\n <ellipse cx={G.head.cx} cy={G.head.cy} rx={G.head.rx} ry={G.head.ry} transform={`rotate(${G.head.rotate} ${G.head.cx} ${G.head.cy})`} fill={`url(#${id.body})`} />\n <path d={G.tongue.d} stroke={G.tongue.color} strokeWidth=\"3\" strokeLinecap=\"round\" />\n <circle className={m(\"orom-pulse\")} cx={G.eye.cx} cy={G.eye.cy} r={G.eye.r} fill={G.eye.color} />\n <path className={m(\"orom-pulse\")} transform={`translate(${G.flame.translate[0]} ${G.flame.translate[1]}) scale(${G.flame.scale})`} d={G.flame.d} fill={`url(#${id.flame})`} />\n </g>\n </svg>\n );\n}\n"
414
+ },
415
+ {
416
+ "path": "marks/ProductLogo.tsx",
417
+ "content": "import { KrizakaLogo } from \"./KrizakaLogo\";\nimport { OrazakaLogo } from \"./OrazakaLogo\";\nimport { OrochiaLogo } from \"./OrochiaLogo\";\nimport type { MarkProps } from \"./shared\";\nimport { YasakiaLogo } from \"./YasakiaLogo\";\nimport { YororisLogo } from \"./YororisLogo\";\n\nexport type BrandId = \"krizaka\" | \"orazaka\" | \"orochia\" | \"yasakia\" | \"yororis\";\n\n/** The mark of a Krizaka brand by id — for navigation, cards and lists driven by data. */\nexport function ProductLogo({ id, ...props }: MarkProps & { /** The brand: `krizaka`, `orazaka`, `orochia`, `yasakia` or `yororis` (`yazakia`, deprecated: the old name of `yasakia`). */ id: BrandId | \"yazakia\" }) {\n if (id === \"krizaka\") return <KrizakaLogo {...props} />;\n if (id === \"orazaka\") return <OrazakaLogo {...props} />;\n if (id === \"yasakia\" || id === \"yazakia\") return <YasakiaLogo {...props} />;\n if (id === \"yororis\") return <YororisLogo {...props} />;\n return <OrochiaLogo {...props} />;\n}\n"
418
+ },
419
+ {
420
+ "path": "marks/YasakiaLogo.tsx",
421
+ "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"
422
+ },
423
+ {
424
+ "path": "marks/YororisLogo.tsx",
425
+ "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"
426
+ },
427
+ {
428
+ "path": "marks/krizaka-geometry.ts",
429
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Krizaka mark's geometry — the one description drawn by the web mark (KrizakaLogo) and the native one\n * (KrizakaMark). Built on the Orochia mark's grammar (docs: packages/tokens/BRAND.md, \"Marks\"): a 360×360 artwork at\n * (20, 20) in a 400×400 view box, one bold stroke (22) drawn as an open figure, an end that breaks out as a node, a\n * core, a dashed orbit and a ring; below 48 px the view box crops to the emblem.\n *\n * Krizaka is the parent: the hexagonal shield is **ink** (the host's text roles), the node and the core carry the\n * Krizaka blue (`marks.krizaka` of @krizaka/tokens: the brand's accent hsl(217 92% 53%) → its text tint hsl(217 92% 68%)).\n */\nexport const KRIZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"86 82 228 228\",\n offset: 20,\n /** The shield: a pointy-top hexagon (r 92 around 180,180), open at its upper-right edge. */\n shield: \"M259.7,134 L259.7,226 L180,272 L100.3,226 L100.3,134 L180,88 L212,106.5\",\n stroke: 22,\n ink: { x1: 70, y1: 290, x2: 290, y2: 70 },\n blue: { x1: 180, y1: 180, x2: 300, y2: 60, stops: marks.krizaka },\n /** The vertex where the shield opens: a node (the serpent's head in Orochia's grammar). */\n node: { cx: 259.7, cy: 134, r: 19, inner: 6 },\n core: { cx: 180, cy: 180, r: 24, inner: 8 },\n highlight: \"#dbeafe\",\n glow: { color: \"#196df5\", opacity: 0.16 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n} as const;\n"
430
+ },
431
+ {
432
+ "path": "marks/orazaka-geometry.ts",
433
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Orazaka mark's geometry — drawn by the web mark (OrazakaLogo) and the native one (OrazakaMark). The same\n * grammar as Orochia and Krizaka (packages/tokens/BRAND.md, \"Marks\"): 400×400 view box, dashed orbit and ring, a bold\n * emblem in the brand gradient, a bright core; below 48 px the view box crops to the emblem.\n *\n * The emblem keeps the original idea — three hexagonal shards orbiting a core — drawn as solid shards (the weight of\n * Orochia's body) in the Orazaka orange: the brand's mark gradient (`marks.orazaka` of @krizaka/tokens, the same source\n * as the accent), deep orange → orange on the accent's hue 26 — never the amber of --kz-warning (hue 38). Every stop\n * reads ≥ 3:1 on the page surfaces of both themes (tested in @krizaka/tokens), so one mark serves dark and light.\n */\n/** The bright end of the gradient: the halo and the core's glow. */\nconst orange = marks.orazaka[marks.orazaka.length - 1]![1];\n\nexport const ORAZAKA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"76 76 248 248\",\n center: 200,\n /**\n * The three shards — a hexagon's cap (from the original 132-unit drawing), scaled ×1.9 around the centre and turned\n * 0°, 120°, 240° — in view-box coordinates, so that one gradient runs across all three.\n */\n shards: [\n \"M154.4,121.2 L200,94.7 L245.6,121.2 L245.6,154.4 L226.6,165.4 L226.6,132.2 L200,116.8 L173.4,132.2 L173.4,165.4 L154.4,154.4 Z\",\n \"M291.1,199.9 L291.2,252.6 L245.5,278.9 L216.7,262.3 L216.6,240.3 L245.4,257 L272.1,241.6 L272,210.9 L243.2,194.3 L262.3,183.3 Z\",\n \"M154.5,278.9 L108.8,252.6 L108.9,199.9 L137.7,183.3 L156.8,194.3 L128,210.9 L127.9,241.6 L154.6,257 L183.4,240.3 L183.3,262.3 Z\",\n ],\n shardStroke: 5.7,\n gradient: { x1: 90, y1: 310, x2: 310, y2: 90, stops: marks.orazaka },\n core: { r: 21, inner: 7.6 },\n highlight: \"#fff7ed\",\n glow: { color: orange, opacity: 0.16 },\n /** The core's breathing shadow (web), in the mark's orange. */\n pulse: { color: orange, 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"
434
+ },
435
+ {
436
+ "path": "marks/orochia-geometry.ts",
437
+ "content": "import { marks } from \"@krizaka/tokens/native\";\n\n/**\n * The Orochia mark's geometry — the one description of the serpent and its flame, drawn by the web mark (SVG in the\n * DOM, OrochiaLogo) and the native one (react-native-svg, @krizaka/ui/native). Coordinates are in the 360×360 artwork,\n * placed at (20, 20) in a 400×400 view box; below 48 px the view box crops to the serpent. The body's gradient is the\n * brand's mark gradient (`marks.orochia` of @krizaka/tokens).\n */\nexport const OROCHIA = {\n viewBox: \"0 0 400 400\",\n croppedViewBox: \"86 82 228 228\",\n offset: 20,\n body: { x1: 70, y1: 290, x2: 290, y2: 70, stops: marks.orochia },\n flameGradient: { stops: [[\"0%\", \"#a855f7\"], [\"100%\", \"#f9a8d4\"]] },\n glow: { color: \"#d946ef\", opacity: 0.18 },\n orbit: { r: 160, dash: \"8 8\", width: 1.5 },\n ring: { r: 134, width: 1 },\n arc: \"M259.8,142.8 A88,88 0 1 1 142.8,100.2\",\n neck: \"M142.8,100.2 A88,88 0 0 1 187.7,92.3\",\n tail: \"M187.7,92.3 A88,88 0 0 1 221.3,102.3\",\n scales: { color: \"#fdf4ff\", opacity: 0.45, width: 3, dash: \"2 14\" },\n head: { cx: 253.9, cy: 130.1, rx: 13, ry: 20, rotate: -25 },\n tongue: { d: \"M245.4,112.0 L242.0,104.8 L235.0,101.5 M242.0,104.8 L244.0,97.3\", color: \"#f472b6\" },\n eye: { cx: 256.3, cy: 123.5, r: 4.5, color: \"#fdf4ff\" },\n flame: {\n translate: [127, 120] as const,\n scale: 4.4,\n d: \"M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z\",\n },\n} as const;\n"
438
+ },
439
+ {
440
+ "path": "marks/shared.ts",
441
+ "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"
442
+ },
443
+ {
444
+ "path": "marks/yasakia-geometry.ts",
445
+ "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"
446
+ },
447
+ {
448
+ "path": "marks/yororis-geometry.ts",
449
+ "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"
450
+ },
276
451
  {
277
452
  "path": "meta.ts",
278
- "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 | \"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-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 | \"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 * `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 /** 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"
453
+ "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 | \"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-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 * `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 /** 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"
279
454
  },
280
455
  {
281
456
  "path": "nav-bar/index.ts",
282
- "content": "/** @krizaka/ui/nav-bar — `NavBar` (Root, Brand, Items, Link, Menu, MenuLink, Actions, Mobile, MobileLink), `navBar`. Client. */\nexport {\n NavBar,\n navBar,\n NavBarActions,\n NavBarBrand,\n type NavBarBrandProps,\n NavBarItems,\n NavBarLink,\n type NavBarLinkProps,\n NavBarMenu,\n NavBarMenuLink,\n type NavBarMenuLinkProps,\n type NavBarMenuProps,\n NavBarMobile,\n NavBarMobileLink,\n type NavBarMobileLinkProps,\n type NavBarMobileProps,\n NavBarRoot,\n type NavBarRootProps,\n} from \"./nav-bar\";\n"
457
+ "content": "/** @krizaka/ui/nav-bar — `NavBar` (Root, Brand, Family, Badge, Items, Link, Menu, MenuLink, Actions, Repo, Mobile, MobileLink), `navBar`. Client. */\nexport {\n NavBar,\n navBar,\n NavBarActions,\n NavBarBadge,\n type NavBarBadgeProps,\n NavBarBrand,\n type NavBarBrandProps,\n NavBarFamily,\n type NavBarFamilyProps,\n NavBarItems,\n NavBarLink,\n type NavBarLinkProps,\n NavBarMenu,\n NavBarMenuLink,\n type NavBarMenuLinkProps,\n type NavBarMenuProps,\n NavBarMobile,\n NavBarMobileLink,\n type NavBarMobileLinkProps,\n type NavBarMobileProps,\n NavBarRepo,\n type NavBarRepoProps,\n NavBarRoot,\n type NavBarRootProps,\n} from \"./nav-bar\";\n"
283
458
  },
284
459
  {
285
460
  "path": "nav-bar/meta.ts",
286
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Nav bar\",\n summary:\n \"The Krizaka navigation bar: a floating pill that tightens as the page scrolls, one light that glides to the item under the pointer or the focus and rests on the current page, accessible menus with rich links, and a mobile panel.\",\n status: \"beta\",\n category: \"navigation\",\n platforms: \"web\",\n whenToUse: [\n \"The top navigation of a site or a product: a brand, a few links, one or two menus, actions on the right.\",\n \"A section bar inside an app (`floating={false}`): videos, earnings, auctions.\",\n ],\n whenNotToUse: [\n { when: \"To switch views inside a page without changing the address.\", use: \"tabs\" },\n { when: \"For a list of actions on one object.\", use: \"dropdown-menu\" },\n { when: \"For search and commands across the app.\", use: \"command\" },\n ],\n bestPractices: [\n \"Five items at most on the bar; group the rest in a menu, each link with a name and one line (`NavBar.MenuLink`).\",\n \"Mark the current page with `active`: the light rests on it, `aria-current=\\\"page\\\"` says it.\",\n \"Use your router's link with `asChild` on `NavBar.Link`, `NavBar.MenuLink` and `NavBar.MobileLink`.\",\n \"One primary action at most in `NavBar.Actions` (a `Button` `size=\\\"sm\\\"` `shape=\\\"pill\\\"`).\",\n \"Wrap it in a `sticky top-3` (floating) or `sticky top-0` container to keep it on screen.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Tab\", action: \"Moves between the brand, the items and the actions; leaving an open menu closes it.\" },\n { keys: \"Enter / Space / ↓\", action: \"Opens a menu and moves into it.\" },\n { keys: \"↑ / ↓ · Home / End\", action: \"Walks the links of an open menu.\" },\n { keys: \"← / →\", action: \"Moves between the items of the bar.\" },\n { keys: \"Esc\", action: \"Closes the menu or the mobile panel and returns the focus to its trigger.\" },\n ],\n notes: [\n \"A `nav` named by `label`, a list of links and menu triggers (Radix NavigationMenu): `aria-expanded` and `aria-controls` on each trigger.\",\n \"The mobile panel is a disclosure (no focus trap): its toggle is named by `openLabel` / `closeLabel` and says `aria-expanded`.\",\n \"The light, the chevron and the tightening do not move under `prefers-reduced-motion`; the current page is tinted before scripts load.\",\n ],\n },\n related: [\"tabs\", \"dropdown-menu\", \"command\", \"button\"],\n web: {\n imports: [\"NavBar\"],\n examples: [\n { name: \"site\", title: \"A site's bar\", description: \"Brand, a Products menu, links, a call to action, a mobile panel — hover the items: one light glides between them.\" },\n { name: \"menu-open\", title: \"A rich menu\", description: \"Two columns of links with icons whose node lights up, the current page tinted.\" },\n { name: \"app-bar\", title: \"Inside an app\", description: \"`floating={false}`: a full-width bar with a hairline, for an app's sections.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
461
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Nav bar\",\n summary:\n \"The Krizaka navigation bar, the same on every site of the family: a floating pill that tightens as the page scrolls, the family switcher on the brand, one light that glides to the item under the pointer or the focus and rests on the current page, accessible menus with rich links, and a mobile panel.\",\n status: \"beta\",\n category: \"navigation\",\n platforms: \"web\",\n whenToUse: [\n \"The top navigation of a site or a product: a brand, a few links, one or two menus, actions on the right.\",\n \"A section bar inside an app (`floating={false}`): videos, earnings, auctions.\",\n \"Every site of the Krizaka family (www, ui., java., dev.<product>, admin.<product>): the brand and `NavBar.Family` on the left, at most four of the site's sections in the middle, the tools on the right.\",\n ],\n whenNotToUse: [\n { when: \"To switch views inside a page without changing the address.\", use: \"tabs\" },\n { when: \"For a list of actions on one object.\", use: \"dropdown-menu\" },\n { when: \"For search and commands across the app.\", use: \"command\" },\n { when: \"To link to another site of the family from the bar's middle: the family switcher holds them.\", use: \"sites\" },\n ],\n bestPractices: [\n \"Five items at most on the bar; group the rest in a menu, each link with a name and one line (`NavBar.MenuLink`).\",\n \"Mark the current page with `active`: the light rests on it, `aria-current=\\\"page\\\"` says it.\",\n \"Use your router's link with `asChild` on `NavBar.Link`, `NavBar.MenuLink` and `NavBar.MobileLink`.\",\n \"One primary action at most in `NavBar.Actions` (a `Button` `size=\\\"sm\\\"` `shape=\\\"pill\\\"`).\",\n \"Wrap it in a `sticky top-3` (floating) or `sticky top-0` container to keep it on screen.\",\n \"Three zones on every Krizaka site: who I am (`NavBar.Brand` + `NavBar.Family current=\\\"<site>\\\"`, then `NavBar.Badge` with the version or `DEV · v1.2.3`), this site (at most four sections, never a link to another site), tools (search when there is something to search, `NavBar.Repo`, theme, language, one primary action).\",\n \"Place a second `NavBar.Family` as the last child of `NavBar.Mobile`: below `md` the family is the panel's last group.\",\n \"Pass the family's words translated: `labels={familyLabels[locale]}` from `@krizaka/ui/sites`.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Tab\", action: \"Moves between the brand, the items and the actions; leaving an open menu closes it.\" },\n { keys: \"Enter / Space / ↓\", action: \"Opens a menu and moves into it.\" },\n { keys: \"↑ / ↓ · Home / End\", action: \"Walks the links of an open menu.\" },\n { keys: \"← / →\", action: \"Moves between the items of the bar.\" },\n { keys: \"Esc\", action: \"Closes the menu, the family or the mobile panel and returns the focus to its trigger.\" },\n { keys: \"Enter / Space / ↓ (family)\", action: \"Opens the family from its chevron; ↑ / ↓ walk the sites. It never opens on hover.\" },\n ],\n notes: [\n \"A `nav` named by `label`, a list of links and menu triggers (Radix NavigationMenu): `aria-expanded` and `aria-controls` on each trigger.\",\n \"The mobile panel is a disclosure (no focus trap): its toggle is named by `openLabel` / `closeLabel` and says `aria-expanded`.\",\n \"The light, the chevron and the tightening do not move under `prefers-reduced-motion`; the current page is tinted before scripts load.\",\n \"The family is its own `nav` named by `labels.family`; its chevron is a button with the same name, `aria-expanded` and `aria-controls`. Each group is a list named by its heading, the current site says `aria-current=\\\"true\\\"` and “You are here”, a site not released says its status in words.\",\n \"`NavBar.Repo` is an icon link named by `label`; `NavBar.Badge` is plain text (`DEV · v1.2.3`), hidden below `sm`.\",\n ],\n },\n related: [\"sites\", \"site-footer\", \"tabs\", \"dropdown-menu\", \"command\", \"button\"],\n web: {\n imports: [\"NavBar\"],\n examples: [\n { name: \"family-www\", title: \"www.krizaka.com\", description: \"The company's bar: the mark and the family switcher, Products · Open source · Our story, the repository and Contact. Open the chevron: every site of Krizaka, grouped, the current one marked.\" },\n { name: \"family-ui\", title: \"ui.krizaka.com\", description: \"Krizaka UI: the family, the version of the packages, Get started · Foundations · Components · Templates, the repository.\" },\n { name: \"family-java\", title: \"java.krizaka.com\", description: \"Krizaka Java: the family, the version of the building blocks, Get started · Building blocks · Guides.\" },\n { name: \"family-product\", title: \"dev.<product>\", description: \"A product's site: its mark and the family, `DEV · v1.2.3`, How it works · Demo · Docs, and “Talk to us” on krizaka.com while sign-up is closed.\" },\n { name: \"family-admin\", title: \"admin.<product>\", description: \"A console: the mark, the family and `DEV`, no sections, a full-width bar.\" },\n { name: \"site\", title: \"A site's bar\", description: \"Brand, a Products menu, links, a call to action, a mobile panel — hover the items: one light glides between them.\" },\n { name: \"menu-open\", title: \"A rich menu\", description: \"Two columns of links with icons whose node lights up, the current page tinted.\" },\n { name: \"app-bar\", title: \"Inside an app\", description: \"`floating={false}`: a full-width bar with a hairline, for an app's sections.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
287
462
  },
288
463
  {
289
464
  "path": "nav-bar/nav-bar.tsx",
290
- "content": "// Client (scroll, a sliding indicator, menus). Radix NavigationMenu does the menus' semantics and keyboard (Enter, Space\n// and ↓ open; ↑ ↓ Home End walk; Escape closes and returns the focus; Tab out closes); this file draws the Krizaka bar:\n// a floating pill that tightens once the page scrolls, one light that glides to the item under the pointer or the\n// focus and rests on the current page, and a mobile panel that is a plain disclosure (no focus trap, Escape closes).\nimport { NavigationMenu as Nav, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\n\nexport const navBar = tv({\n slots: {\n root:\n \"group/nav relative z-40 mx-auto flex h-14 w-full items-center gap-2 border-border-subtle bg-surface-1/75 backdrop-blur-xl backdrop-saturate-150 \" +\n \"transition-[height,background-color,border-color,box-shadow] duration-300 motion-reduce:transition-none \" +\n \"data-compact:h-12 data-compact:border-border-default data-compact:bg-surface-1/90 data-compact:shadow-lg\",\n brand:\n \"mr-2 inline-flex shrink-0 items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg no-underline \" +\n \"rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n items: \"relative hidden md:block\",\n list:\n \"group/list relative isolate flex items-center gap-0.5 \" +\n // The light: under the item that is pointed at or focused, resting on the current page.\n \"before:absolute before:left-0 before:top-0 before:-z-10 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) \" +\n \"before:rounded-full before:bg-surface-3 before:opacity-0 before:transition-[translate,width,opacity] before:duration-300 motion-reduce:before:transition-none \" +\n \"data-[indicator=on]:before:opacity-100\",\n item:\n \"inline-flex h-9 select-none items-center gap-1 rounded-full px-3.5 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:text-fg data-active:text-fg data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n // Before the light is ready (server render), the current page carries its own tint.\n \"group-not-data-[indicator=on]/list:data-active:bg-surface-3\",\n chevron: \"h-3.5 w-3.5 transition-transform duration-300 group-data-[state=open]/trigger:rotate-180 motion-reduce:transition-none\",\n viewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n panel: \"grid w-max max-w-[min(92vw,30rem)] gap-1 p-2\",\n menuLink:\n \"group/link flex items-start gap-3 rounded-xl p-3 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-active:bg-accent-soft\",\n menuIcon:\n \"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border-default bg-surface-2 text-fg-accent \" +\n \"transition-[transform,border-color] duration-300 group-hover/link:-translate-y-0.5 group-hover/link:border-accent [&>svg]:h-4.5 [&>svg]:w-4.5 motion-reduce:transform-none\",\n menuTitle: \"block text-sm font-semibold text-fg\",\n menuText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n actions: \"ml-auto flex items-center gap-2\",\n toggle:\n \"inline-flex h-9 w-9 items-center justify-center rounded-full border border-border-subtle bg-surface-2/60 text-fg md:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n mobile: \"kz-pop absolute inset-x-0 top-full mt-2 grid gap-1 rounded-2xl border border-border-default bg-surface-1 p-2 shadow-lg md:hidden\",\n mobileLink:\n \"flex items-center justify-between rounded-xl px-3 py-3 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg-accent\",\n },\n variants: {\n floating: {\n true: { root: \"max-w-5xl rounded-full border pl-5 pr-2 data-compact:max-w-4xl\" },\n false: { root: \"border-b px-4 md:px-6\" },\n },\n },\n defaultVariants: { floating: true },\n});\nconst s = navBar();\n\ninterface NavBarContext {\n label: string;\n open: boolean;\n setOpen: (open: boolean) => void;\n panelId: string;\n}\nconst Context = React.createContext<NavBarContext | null>(null);\nconst useNavBar = () => {\n const ctx = React.useContext(Context);\n if (!ctx) throw new Error(\"NavBar parts must be inside <NavBar.Root>.\");\n return ctx;\n};\n\nexport type NavBarRootProps = React.ComponentProps<\"header\"> & {\n /** The accessible name of the navigation (“Main”) — passed translated. */\n label: string;\n /**\n * Pixels of scroll after which the bar tightens (denser, lower, lifted). `false`: never.\n * @default 24\n */\n compactAfter?: number | false;\n /**\n * A floating pill (the Krizaka bar) or a full-width bar with a hairline under it.\n * @default true\n */\n floating?: boolean;\n};\n\n/** The bar. Place it in a `sticky top-3` (floating) or `sticky top-0` wrapper — or let it scroll. */\nexport function NavBarRoot({ label, compactAfter = 24, floating = true, className, children, ...props }: NavBarRootProps) {\n const [compact, setCompact] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const panelId = React.useId();\n React.useEffect(() => {\n if (compactAfter === false) return;\n const onScroll = () => setCompact(window.scrollY > compactAfter);\n onScroll();\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll);\n }, [compactAfter]);\n const ctx = React.useMemo(() => ({ label, open, setOpen, panelId }), [label, open, panelId]);\n return (\n <Context.Provider value={ctx}>\n <header data-compact={compact ? \"\" : undefined} data-floating={floating} className={navBar({ floating }).root({ className })} {...props}>\n {children}\n </header>\n </Context.Provider>\n );\n}\n\nexport type NavBarBrandProps = React.ComponentProps<\"a\"> & {\n /** Renders your link (a router `<Link>`) with the brand's look. */\n asChild?: boolean;\n};\n\n/** The brand: a mark and a name, linking home. */\nexport function NavBarBrand({ asChild, className, ...props }: NavBarBrandProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp className={s.brand({ className })} {...props} />;\n}\n\n/** The desktop navigation: links and menus, with the light that glides between them. Hidden below `md` (see `NavBar.Mobile`). */\nexport function NavBarItems({ className, children, ...props }: Omit<React.ComponentProps<typeof Nav.Root>, \"orientation\">) {\n const { label } = useNavBar();\n const { ref, handlers } = useSlidingIndicator<HTMLUListElement>({ item: \"[data-nav-item]\", active: \"[data-nav-item][data-active]\", follow: true });\n return (\n <Nav.Root aria-label={label} className={s.items({ className })} delayDuration={80} {...props}>\n <Nav.List ref={ref} className={s.list()} {...handlers}>\n {children}\n </Nav.List>\n <Nav.Viewport className={s.viewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"active\"> & {\n /** The current page: `aria-current=\"page\"`, and the light rests on it. */\n active?: boolean;\n};\n\n/** A link of the bar. `asChild` renders your router's `<Link>`. */\nexport function NavBarLink({ active, className, ...props }: NavBarLinkProps) {\n return (\n <Nav.Item>\n <Nav.Link data-nav-item=\"\" data-active={active ? \"\" : undefined} active={active} aria-current={active ? \"page\" : undefined} className={s.item({ className })} {...props} />\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuProps = Omit<React.ComponentProps<typeof Nav.Item>, \"children\"> & {\n /** The trigger's words (“Products”) — passed translated. */\n label: string;\n /** The current page is inside this menu: the light rests on its trigger. */\n active?: boolean;\n /** The menu's links (`NavBar.MenuLink`), laid out in a panel; `columns` sets the grid. */\n children: React.ReactNode;\n /**\n * Columns of the panel.\n * @default 1\n */\n columns?: 1 | 2;\n};\n\n/** A menu of the bar: its trigger, and its links in a panel that opens under the bar. */\nexport function NavBarMenu({ label, active, columns = 1, children, className, ...props }: NavBarMenuProps) {\n return (\n <Nav.Item className={className} {...props}>\n <Nav.Trigger data-nav-item=\"\" data-active={active ? \"\" : undefined} className={cn(s.item(), \"group/trigger\")}>\n {label}\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content className={cn(s.panel(), columns === 2 && \"md:grid-cols-2\")}>{children}</Nav.Content>\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"title\" | \"active\"> & {\n /** The link's name — passed translated. */\n title: string;\n /** One line under it — passed translated. */\n description?: string;\n /** An icon (a signature icon of `@krizaka/icons`: its node lights up on hover). */\n icon?: React.ReactNode;\n /** The current page. */\n active?: boolean;\n};\n\n/** A link inside a menu: an icon, a name and a line of text. */\nexport function NavBarMenuLink({ title, description, icon, active, href, className, asChild, children, ...props }: NavBarMenuLinkProps) {\n const body = (\n <>\n {icon && <span className={s.menuIcon()}>{icon}</span>}\n <span className=\"min-w-0\">\n <span className={s.menuTitle()}>{title}</span>\n {description && <span className={s.menuText()}>{description}</span>}\n </span>\n </>\n );\n return (\n <Nav.Link asChild={asChild} href={asChild ? undefined : href} active={active} aria-current={active ? \"page\" : undefined} data-active={active ? \"\" : undefined} className={s.menuLink({ className })} {...props}>\n {asChild && React.isValidElement<{ children?: React.ReactNode }>(children) ? React.cloneElement(children, undefined, body) : body}\n </Nav.Link>\n );\n}\n\n/** The right side: search, theme, language, the call to action. */\nexport function NavBarActions({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.actions({ className })} {...props} />;\n}\n\nexport type NavBarMobileProps = React.ComponentProps<\"nav\"> & {\n /** The toggle's name when closed (“Open the menu”) — passed translated. */\n openLabel: string;\n /** The toggle's name when open (“Close the menu”) — passed translated. */\n closeLabel: string;\n};\n\n/**\n * Below `md`: a toggle, and a panel under the bar with its links (`NavBar.MobileLink`). A disclosure: Escape and a click\n * on a link close it, the focus returns to the toggle.\n */\nexport function NavBarMobile({ openLabel, closeLabel, className, children, ...props }: NavBarMobileProps) {\n const { label, open, setOpen, panelId } = useNavBar();\n const toggle = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n setOpen(false);\n toggle.current?.focus();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, setOpen]);\n return (\n <>\n <button ref={toggle} type=\"button\" aria-expanded={open} aria-controls={panelId} aria-label={open ? closeLabel : openLabel} onClick={() => setOpen(!open)} className={s.toggle()}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4.5 w-4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n {open ? <path d=\"M18 6 6 18M6 6l12 12\" /> : <path d=\"M4 7h16M4 12h16M4 17h10\" />}\n </svg>\n </button>\n {open && (\n <nav\n id={panelId}\n aria-label={label}\n className={s.mobile({ className })}\n onClick={(e) => {\n if ((e.target as Element).closest(\"a\")) setOpen(false);\n }}\n {...props}\n >\n {children}\n </nav>\n )}\n </>\n );\n}\n\nexport type NavBarMobileLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the mobile panel. */\nexport function NavBarMobileLink({ active, asChild, className, ...props }: NavBarMobileLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.mobileLink({ className })} {...props} />;\n}\n\n/**\n * `NavBar.Root/Brand/Items/Link/Menu/MenuLink/Actions/Mobile/MobileLink` — the Krizaka navigation bar: floating, tightening\n * on scroll, one light gliding between its items, accessible menus, a mobile panel.\n */\nexport const NavBar = {\n Root: NavBarRoot,\n Brand: NavBarBrand,\n Items: NavBarItems,\n Link: NavBarLink,\n Menu: NavBarMenu,\n MenuLink: NavBarMenuLink,\n Actions: NavBarActions,\n Mobile: NavBarMobile,\n MobileLink: NavBarMobileLink,\n};\n"
465
+ "content": "// Client (scroll, a sliding indicator, menus). Radix NavigationMenu does the menus' semantics and keyboard (Enter, Space\n// and ↓ open; ↑ ↓ Home End walk; Escape closes and returns the focus; Tab out closes); this file draws the Krizaka bar:\n// a floating pill that tightens once the page scrolls, one light that glides to the item under the pointer or the\n// focus and rests on the current page, and a mobile panel that is a plain disclosure (no focus trap, Escape closes).\nimport { NavigationMenu as Nav, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { FamilyMark, GitHubGlyph } from \"../family-mark\";\nimport { type FamilyLabels, familyLabels, type Site, type SiteId, sitesByGroup } from \"../sites/sites\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\n\nexport const navBar = tv({\n slots: {\n root:\n \"group/nav relative z-40 mx-auto flex h-14 w-full items-center gap-2 border-border-subtle bg-surface-1/75 backdrop-blur-xl backdrop-saturate-150 \" +\n \"transition-[height,background-color,border-color,box-shadow] duration-300 motion-reduce:transition-none \" +\n \"data-compact:h-12 data-compact:border-border-default data-compact:bg-surface-1/90 data-compact:shadow-lg\",\n brand:\n \"mr-2 inline-flex shrink-0 items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg no-underline \" +\n \"rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n items: \"relative hidden md:block\",\n list:\n \"group/list relative isolate flex items-center gap-0.5 \" +\n // The light: under the item that is pointed at or focused, resting on the current page.\n \"before:absolute before:left-0 before:top-0 before:-z-10 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) \" +\n \"before:rounded-full before:bg-surface-3 before:opacity-0 before:transition-[translate,width,opacity] before:duration-300 motion-reduce:before:transition-none \" +\n \"data-[indicator=on]:before:opacity-100\",\n item:\n \"inline-flex h-9 select-none items-center gap-1 rounded-full px-3.5 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:text-fg data-active:text-fg data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n // Before the light is ready (server render), the current page carries its own tint.\n \"group-not-data-[indicator=on]/list:data-active:bg-surface-3\",\n chevron: \"h-3.5 w-3.5 transition-transform duration-300 group-data-[state=open]/trigger:rotate-180 motion-reduce:transition-none\",\n viewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n panel: \"grid w-max max-w-[min(92vw,30rem)] gap-1 p-2\",\n menuLink:\n \"group/link flex items-start gap-3 rounded-xl p-3 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-active:bg-accent-soft\",\n menuIcon:\n \"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border-default bg-surface-2 text-fg-accent \" +\n \"transition-[transform,border-color] duration-300 group-hover/link:-translate-y-0.5 group-hover/link:border-accent [&>svg]:h-4.5 [&>svg]:w-4.5 motion-reduce:transform-none\",\n menuTitle: \"block text-sm font-semibold text-fg\",\n menuText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n actions: \"ml-auto flex items-center gap-2\",\n toggle:\n \"inline-flex h-9 w-9 items-center justify-center rounded-full border border-border-subtle bg-surface-2/60 text-fg md:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n mobile: \"kz-pop absolute inset-x-0 top-full mt-2 grid gap-1 rounded-2xl border border-border-default bg-surface-1 p-2 shadow-lg md:hidden\",\n mobileLink:\n \"flex items-center justify-between rounded-xl px-3 py-3 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg-accent\",\n // The family switcher: a chevron attached to the brand, its panel aligned on the bar's left edge.\n family: \"hidden md:block\",\n familyTrigger:\n \"group/trigger -ml-2 mr-1 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors \" +\n \"hover:bg-surface-3 hover:text-fg data-[state=open]:bg-surface-3 data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n familyViewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n familyPanel: \"grid w-[min(92vw,42rem)] gap-x-2 gap-y-3 p-3 sm:grid-cols-2\",\n familyGroupTitle: \"px-3 pb-1 pt-1 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n familyLink:\n \"group/link flex items-start gap-3 rounded-xl px-3 py-2.5 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n familyMark: \"mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center text-fg\",\n familyName: \"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm font-semibold text-fg\",\n familyText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n familyTag: \"rounded-full px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider\",\n mobileFamily: \"mt-1 grid gap-2 border-t border-border-subtle px-1 pb-1 pt-3\",\n mobileFamilyLink:\n \"flex min-h-11 items-center gap-3 rounded-xl px-2 py-2 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n badge:\n \"hidden h-6 shrink-0 items-center gap-1 rounded-full border border-border-subtle bg-surface-2 px-2 font-mono text-[11px] font-medium text-fg-secondary sm:inline-flex\",\n repo:\n \"inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n floating: {\n true: { root: \"max-w-5xl rounded-full border pl-5 pr-2 data-compact:max-w-4xl\" },\n false: { root: \"border-b px-4 md:px-6\" },\n },\n },\n defaultVariants: { floating: true },\n});\nconst s = navBar();\n\ninterface NavBarContext {\n label: string;\n open: boolean;\n setOpen: (open: boolean) => void;\n panelId: string;\n}\nconst Context = React.createContext<NavBarContext | null>(null);\n/** True inside `NavBar.Mobile`: the family renders as the panel's last group instead of a switcher. */\nconst MobileContext = React.createContext(false);\nconst useNavBar = () => {\n const ctx = React.useContext(Context);\n if (!ctx) throw new Error(\"NavBar parts must be inside <NavBar.Root>.\");\n return ctx;\n};\n\nexport type NavBarRootProps = React.ComponentProps<\"header\"> & {\n /** The accessible name of the navigation (“Main”) — passed translated. */\n label: string;\n /**\n * Pixels of scroll after which the bar tightens (denser, lower, lifted). `false`: never.\n * @default 24\n */\n compactAfter?: number | false;\n /**\n * A floating pill (the Krizaka bar) or a full-width bar with a hairline under it.\n * @default true\n */\n floating?: boolean;\n};\n\n/** The bar. Place it in a `sticky top-3` (floating) or `sticky top-0` wrapper — or let it scroll. */\nexport function NavBarRoot({ label, compactAfter = 24, floating = true, className, children, ...props }: NavBarRootProps) {\n const [compact, setCompact] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const panelId = React.useId();\n React.useEffect(() => {\n if (compactAfter === false) return;\n const onScroll = () => setCompact(window.scrollY > compactAfter);\n onScroll();\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll);\n }, [compactAfter]);\n const ctx = React.useMemo(() => ({ label, open, setOpen, panelId }), [label, open, panelId]);\n return (\n <Context.Provider value={ctx}>\n <header data-compact={compact ? \"\" : undefined} data-floating={floating} className={navBar({ floating }).root({ className })} {...props}>\n {children}\n </header>\n </Context.Provider>\n );\n}\n\nexport type NavBarBrandProps = React.ComponentProps<\"a\"> & {\n /** Renders your link (a router `<Link>`) with the brand's look. */\n asChild?: boolean;\n};\n\n/** The brand: a mark and a name, linking home. */\nexport function NavBarBrand({ asChild, className, ...props }: NavBarBrandProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp className={s.brand({ className })} {...props} />;\n}\n\n/** The desktop navigation: links and menus, with the light that glides between them. Hidden below `md` (see `NavBar.Mobile`). */\nexport function NavBarItems({ className, children, ...props }: Omit<React.ComponentProps<typeof Nav.Root>, \"orientation\">) {\n const { label } = useNavBar();\n const { ref, handlers } = useSlidingIndicator<HTMLUListElement>({ item: \"[data-nav-item]\", active: \"[data-nav-item][data-active]\", follow: true });\n return (\n <Nav.Root aria-label={label} className={s.items({ className })} delayDuration={80} {...props}>\n <Nav.List ref={ref} className={s.list()} {...handlers}>\n {children}\n </Nav.List>\n <Nav.Viewport className={s.viewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"active\"> & {\n /** The current page: `aria-current=\"page\"`, and the light rests on it. */\n active?: boolean;\n};\n\n/** A link of the bar. `asChild` renders your router's `<Link>`. */\nexport function NavBarLink({ active, className, ...props }: NavBarLinkProps) {\n return (\n <Nav.Item>\n <Nav.Link data-nav-item=\"\" data-active={active ? \"\" : undefined} active={active} aria-current={active ? \"page\" : undefined} className={s.item({ className })} {...props} />\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuProps = Omit<React.ComponentProps<typeof Nav.Item>, \"children\"> & {\n /** The trigger's words (“Products”) — passed translated. */\n label: string;\n /** The current page is inside this menu: the light rests on its trigger. */\n active?: boolean;\n /** The menu's links (`NavBar.MenuLink`), laid out in a panel; `columns` sets the grid. */\n children: React.ReactNode;\n /**\n * Columns of the panel.\n * @default 1\n */\n columns?: 1 | 2;\n};\n\n/** A menu of the bar: its trigger, and its links in a panel that opens under the bar. */\nexport function NavBarMenu({ label, active, columns = 1, children, className, ...props }: NavBarMenuProps) {\n return (\n <Nav.Item className={className} {...props}>\n <Nav.Trigger data-nav-item=\"\" data-active={active ? \"\" : undefined} className={cn(s.item(), \"group/trigger\")}>\n {label}\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content className={cn(s.panel(), columns === 2 && \"md:grid-cols-2\")}>{children}</Nav.Content>\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"title\" | \"active\"> & {\n /** The link's name — passed translated. */\n title: string;\n /** One line under it — passed translated. */\n description?: string;\n /** An icon (a signature icon of `@krizaka/icons`: its node lights up on hover). */\n icon?: React.ReactNode;\n /** The current page. */\n active?: boolean;\n};\n\n/** A link inside a menu: an icon, a name and a line of text. */\nexport function NavBarMenuLink({ title, description, icon, active, href, className, asChild, children, ...props }: NavBarMenuLinkProps) {\n const body = (\n <>\n {icon && <span className={s.menuIcon()}>{icon}</span>}\n <span className=\"min-w-0\">\n <span className={s.menuTitle()}>{title}</span>\n {description && <span className={s.menuText()}>{description}</span>}\n </span>\n </>\n );\n return (\n <Nav.Link asChild={asChild} href={asChild ? undefined : href} active={active} aria-current={active ? \"page\" : undefined} data-active={active ? \"\" : undefined} className={s.menuLink({ className })} {...props}>\n {asChild && React.isValidElement<{ children?: React.ReactNode }>(children) ? React.cloneElement(children, undefined, body) : body}\n </Nav.Link>\n );\n}\n\n/** The right side: search, theme, language, the call to action. */\nexport function NavBarActions({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.actions({ className })} {...props} />;\n}\n\nexport type NavBarMobileProps = React.ComponentProps<\"nav\"> & {\n /** The toggle's name when closed (“Open the menu”) — passed translated. */\n openLabel: string;\n /** The toggle's name when open (“Close the menu”) — passed translated. */\n closeLabel: string;\n};\n\n/**\n * Below `md`: a toggle, and a panel under the bar with its links (`NavBar.MobileLink`). A disclosure: Escape and a click\n * on a link close it, the focus returns to the toggle.\n */\nexport function NavBarMobile({ openLabel, closeLabel, className, children, ...props }: NavBarMobileProps) {\n const { label, open, setOpen, panelId } = useNavBar();\n const toggle = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n setOpen(false);\n toggle.current?.focus();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, setOpen]);\n return (\n <>\n <button ref={toggle} type=\"button\" aria-expanded={open} aria-controls={panelId} aria-label={open ? closeLabel : openLabel} onClick={() => setOpen(!open)} className={s.toggle()}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4.5 w-4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n {open ? <path d=\"M18 6 6 18M6 6l12 12\" /> : <path d=\"M4 7h16M4 12h16M4 17h10\" />}\n </svg>\n </button>\n {open && (\n <nav\n id={panelId}\n aria-label={label}\n className={s.mobile({ className })}\n onClick={(e) => {\n if ((e.target as Element).closest(\"a\")) setOpen(false);\n }}\n {...props}\n >\n <MobileContext.Provider value>{children}</MobileContext.Provider>\n </nav>\n )}\n </>\n );\n}\n\nexport type NavBarMobileLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the mobile panel. */\nexport function NavBarMobileLink({ active, asChild, className, ...props }: NavBarMobileLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.mobileLink({ className })} {...props} />;\n}\n\n\nconst tagTone = {\n upcoming: \"bg-accent-soft text-fg ring-1 ring-inset ring-accent/40\",\n construction: \"bg-warning/15 text-fg ring-1 ring-inset ring-warning/40\",\n current: \"bg-surface-3 text-fg-secondary\",\n};\n\n/** A site's name and its tags: status when not released, “You are here” on the current one. */\nfunction FamilyName({ site, current, labels }: { site: Site; current: boolean; labels: FamilyLabels }) {\n return (\n <span className={s.familyName()}>\n {site.name}\n {site.status !== \"released\" && <span className={cn(s.familyTag(), tagTone[site.status])}>{labels.status[site.status]}</span>}\n {current && <span className={cn(s.familyTag(), tagTone.current)}>{labels.current}</span>}\n </span>\n );\n}\n\nexport type NavBarFamilyProps = {\n /** The site this bar belongs to (`@krizaka/ui/sites`): marked in the family, `aria-current=\"true\"`. */\n current: SiteId;\n /**\n * The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.\n * @default familyLabels.en\n */\n labels?: FamilyLabels;\n /** Opens the panel at first render (a story, a screenshot). */\n defaultOpen?: boolean;\n /** Classes of the switcher (or, inside `NavBar.Mobile`, of the family's group). */\n className?: string;\n};\n\n/**\n * The family switcher, attached to the brand (place it right after `NavBar.Brand`): a chevron that opens every site of\n * Krizaka grouped — Krizaka · Products · Open source · GitHub — the current one marked. It opens on click, Enter, Space\n * or ↓ (never on hover); ↑ ↓ walk the sites, Escape closes and returns the focus. Hidden below `md`: place a second\n * `NavBar.Family` as the last child of `NavBar.Mobile`, where it renders as the panel's last group.\n */\nexport function NavBarFamily({ current, labels = familyLabels.en, defaultOpen, className }: NavBarFamilyProps) {\n const inMobile = React.useContext(MobileContext);\n const id = React.useId();\n const groups = sitesByGroup();\n if (inMobile) {\n return (\n <div role=\"group\" aria-labelledby={`${id}-family`} className={s.mobileFamily({ className })}>\n <p id={`${id}-family`} className={s.familyGroupTitle()}>\n {labels.family}\n </p>\n {groups.map(({ group, sites }) => (\n <div key={group}>\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <a href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.mobileFamilyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={24} />\n </span>\n <FamilyName site={site} current={site.id === current} labels={labels} />\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n );\n }\n // Click only: a pointer passing over the brand never opens the family (Radix opens its menus on hover by default).\n const noHover = (e: React.PointerEvent) => e.preventDefault();\n const columns = [groups.filter((g) => g.group === \"krizaka\" || g.group === \"products\"), groups.filter((g) => g.group === \"open-source\" || g.group === \"github\")];\n return (\n <Nav.Root aria-label={labels.family} defaultValue={defaultOpen ? \"family\" : undefined} className={s.family({ className })}>\n <Nav.List className=\"flex\">\n <Nav.Item value=\"family\">\n <Nav.Trigger aria-label={labels.family} onPointerMove={noHover} onPointerLeave={noHover} className={s.familyTrigger()}>\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content onPointerLeave={noHover} className={s.familyPanel()}>\n {columns.map((column, i) => (\n <div key={i} className=\"grid content-start gap-3\">\n {column.map(({ group, sites }) => (\n <div key={group}>\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <Nav.Link href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.familyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={28} />\n </span>\n <span className=\"min-w-0\">\n <FamilyName site={site} current={site.id === current} labels={labels} />\n <span className={s.familyText()}>{labels.descriptions[site.id]}</span>\n </span>\n </Nav.Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n ))}\n </Nav.Content>\n </Nav.Item>\n </Nav.List>\n <Nav.Viewport onPointerLeave={noHover} className={s.familyViewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarBadgeProps = React.ComponentProps<\"span\"> & {\n /** The environment, when it is not production (`DEV`). */\n env?: string;\n /** The version the site runs (`1.2.3`, shown `v1.2.3`). */\n version?: string;\n};\n\n/** A small mono badge beside the brand: the environment and the version (`DEV · v1.2.3`). Hidden below `sm`. */\nexport function NavBarBadge({ env, version, className, children, ...props }: NavBarBadgeProps) {\n const parts = [env, version && `v${version.replace(/^v/, \"\")}`].filter(Boolean);\n return (\n <span className={s.badge({ className })} {...props}>\n {parts.join(\" · \")}\n {children}\n </span>\n );\n}\n\nexport type NavBarRepoProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The repository the site is built from. */\n href: string;\n /** The link's accessible name (“Source on GitHub”) — passed translated. */\n label: string;\n};\n\n/** The site's repository on GitHub, an icon link of `NavBar.Actions`. */\nexport function NavBarRepo({ label, className, ...props }: NavBarRepoProps) {\n return (\n <a aria-label={label} className={s.repo({ className })} {...props}>\n <GitHubGlyph size={18} />\n </a>\n );\n}\n\n/**\n * `NavBar.Root/Brand/Family/Badge/Items/Link/Menu/MenuLink/Actions/Repo/Mobile/MobileLink` — the Krizaka navigation bar,\n * the same on every site of the family: floating, tightening on scroll, the family switcher on the brand, one light\n * gliding between its items, accessible menus, a mobile panel.\n */\nexport const NavBar = {\n Root: NavBarRoot,\n Brand: NavBarBrand,\n Family: NavBarFamily,\n Badge: NavBarBadge,\n Items: NavBarItems,\n Link: NavBarLink,\n Menu: NavBarMenu,\n MenuLink: NavBarMenuLink,\n Actions: NavBarActions,\n Repo: NavBarRepo,\n Mobile: NavBarMobile,\n MobileLink: NavBarMobileLink,\n};\n"
291
466
  },
292
467
  {
293
468
  "path": "sliding-indicator.ts",
@@ -295,15 +470,17 @@
295
470
  }
296
471
  ],
297
472
  "dependencies": [
473
+ "@krizaka/tokens@^3.11.0",
298
474
  "radix-ui@^1.7.0",
299
475
  "tailwind-variants@^3.3.1"
300
476
  ],
301
477
  "registryDependencies": [
302
- "cn"
478
+ "cn",
479
+ "sites"
303
480
  ],
304
481
  "demo": {
305
- "path": "examples/nav-bar/site.tsx",
306
- "content": "import { Button } from \"@krizaka/ui/button\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\n\nconst products = [\n { href: \"#orazaka\", title: \"Orazaka\", description: \"Sovereign AI, on your own servers.\" },\n { href: \"#orochia\", title: \"Orochia\", description: \"Creators keep 90 % of every support.\" },\n { href: \"#ui\", title: \"Krizaka UI\", description: \"The components behind both.\" },\n];\n\n// The site's bar: a floating pill that tightens on scroll, one light gliding between its items and resting on the\n// current page, an accessible menu (Enter or ↓ opens, Escape closes), and a panel below `md`.\nexport default function NavBarSite() {\n return (\n <NavBar.Root label=\"Main\">\n <NavBar.Brand href=\"#home\">Krizaka</NavBar.Brand>\n <NavBar.Items>\n <NavBar.Menu label=\"Products\">\n {products.map((p) => (\n <NavBar.MenuLink key={p.href} {...p} />\n ))}\n </NavBar.Menu>\n <NavBar.Link href=\"#docs\">Docs</NavBar.Link>\n <NavBar.Link href=\"#story\" active>Our story</NavBar.Link>\n </NavBar.Items>\n <NavBar.Actions>\n <Button variant=\"primary\" size=\"sm\" shape=\"pill\" className=\"hidden sm:inline-flex\">Contact</Button>\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n {products.map((p) => (\n <NavBar.MobileLink key={p.href} href={p.href}>{p.title}</NavBar.MobileLink>\n ))}\n <NavBar.MobileLink href=\"#story\" active>Our story</NavBar.MobileLink>\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n );\n}\n"
482
+ "path": "examples/nav-bar/family-www.tsx",
483
+ "content": "import { KrizakaLogo } from \"@krizaka/ui\";\nimport { Button } from \"@krizaka/ui/button\";\nimport { NavBar } from \"@krizaka/ui/nav-bar\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\nconst links = [\n { href: \"#products\", label: \"Products\" },\n { href: \"#open-source\", label: \"Open source\" },\n { href: \"#story\", label: \"Our story\" },\n];\n\n// www.krizaka.com: who I am (the mark and the family switcher) · this site (three sections) · tools (the repository,\n// one action). The chevron opens the family; the mobile panel ends with it.\nexport default function NavBarFamilyWww({ defaultOpen = false }: { defaultOpen?: boolean }) {\n return (\n <NavBar.Root label=\"Main\" className={defaultOpen ? \"mb-[30rem]\" : undefined}>\n <NavBar.Brand href=\"#home\"><KrizakaLogo size={22} />Krizaka</NavBar.Brand>\n <NavBar.Family current=\"krizaka\" labels={familyLabels.en} defaultOpen={defaultOpen} />\n <NavBar.Items>\n {links.map((l) => (\n <NavBar.Link key={l.href} href={l.href} active={l.href === \"#products\"}>{l.label}</NavBar.Link>\n ))}\n </NavBar.Items>\n <NavBar.Actions>\n <NavBar.Repo href=\"https://github.com/krizaka/krizaka-com\" label=\"Source on GitHub\" />\n <Button variant=\"primary\" size=\"sm\" shape=\"pill\" className=\"hidden sm:inline-flex\">Contact</Button>\n <NavBar.Mobile openLabel=\"Open the menu\" closeLabel=\"Close the menu\">\n {links.map((l) => (\n <NavBar.MobileLink key={l.href} href={l.href}>{l.label}</NavBar.MobileLink>\n ))}\n <NavBar.Family current=\"krizaka\" labels={familyLabels.en} />\n </NavBar.Mobile>\n </NavBar.Actions>\n </NavBar.Root>\n );\n}\n"
307
484
  },
308
485
  "props": [
309
486
  {
@@ -346,6 +523,60 @@
346
523
  }
347
524
  ]
348
525
  },
526
+ {
527
+ "component": "NavBarFamily",
528
+ "description": "The family switcher, attached to the brand (place it right after `NavBar.Brand`): a chevron that opens every site of\nKrizaka grouped — Krizaka · Products · Open source · GitHub — the current one marked. It opens on click, Enter, Space\nor ↓ (never on hover); ↑ ↓ walk the sites, Escape closes and returns the focus. Hidden below `md`: place a second\n`NavBar.Family` as the last child of `NavBar.Mobile`, where it renders as the panel's last group.",
529
+ "props": [
530
+ {
531
+ "name": "current",
532
+ "type": "SiteId",
533
+ "default": null,
534
+ "description": "The site this bar belongs to (`@krizaka/ui/sites`): marked in the family, `aria-current=\"true\"`.",
535
+ "required": true
536
+ },
537
+ {
538
+ "name": "className",
539
+ "type": "string",
540
+ "default": null,
541
+ "description": "Classes of the switcher (or, inside `NavBar.Mobile`, of the family's group).",
542
+ "required": false
543
+ },
544
+ {
545
+ "name": "defaultOpen",
546
+ "type": "boolean",
547
+ "default": null,
548
+ "description": "Opens the panel at first render (a story, a screenshot).",
549
+ "required": false
550
+ },
551
+ {
552
+ "name": "labels",
553
+ "type": "FamilyLabels",
554
+ "default": "familyLabels.en",
555
+ "description": "The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.",
556
+ "required": false
557
+ }
558
+ ]
559
+ },
560
+ {
561
+ "component": "NavBarBadge",
562
+ "description": "A small mono badge beside the brand: the environment and the version (`DEV · v1.2.3`). Hidden below `sm`.",
563
+ "props": [
564
+ {
565
+ "name": "env",
566
+ "type": "string",
567
+ "default": null,
568
+ "description": "The environment, when it is not production (`DEV`).",
569
+ "required": false
570
+ },
571
+ {
572
+ "name": "version",
573
+ "type": "string",
574
+ "default": null,
575
+ "description": "The version the site runs (`1.2.3`, shown `v1.2.3`).",
576
+ "required": false
577
+ }
578
+ ]
579
+ },
349
580
  {
350
581
  "component": "NavBarItems",
351
582
  "description": "The desktop navigation: links and menus, with the light that glides between them. Hidden below `md` (see `NavBar.Mobile`).",
@@ -437,6 +668,19 @@
437
668
  "description": "The right side: search, theme, language, the call to action.",
438
669
  "props": []
439
670
  },
671
+ {
672
+ "component": "NavBarRepo",
673
+ "description": "The site's repository on GitHub, an icon link of `NavBar.Actions`.",
674
+ "props": [
675
+ {
676
+ "name": "label",
677
+ "type": "string",
678
+ "default": null,
679
+ "description": "The link's accessible name (“Source on GitHub”) — passed translated.",
680
+ "required": true
681
+ }
682
+ ]
683
+ },
440
684
  {
441
685
  "component": "NavBarMobile",
442
686
  "description": "Below `md`: a toggle, and a panel under the bar with its links (`NavBar.MobileLink`). A disclosure: Escape and a click\non a link close it, the focus returns to the toggle.",