@krizaka/ui 3.52.0 → 3.54.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 (210) hide show
  1. package/dist/action-bar.js +1 -1
  2. package/dist/argument-card.js +2 -2
  3. package/dist/blocks.css +148 -0
  4. package/dist/chapter.d.ts +106 -0
  5. package/dist/chapter.js +174 -0
  6. package/dist/chapter.js.map +1 -0
  7. package/dist/choice-grid.js +1 -1
  8. package/dist/choice-grid.js.map +1 -1
  9. package/dist/{chunk-IBDTAMPF.js → chunk-7JG3VR4C.js} +2 -2
  10. package/dist/{chunk-C7LTF5CC.js → chunk-ENOS6ETI.js} +2 -2
  11. package/dist/{chunk-C7LTF5CC.js.map → chunk-ENOS6ETI.js.map} +1 -1
  12. package/dist/{chunk-2AGSO3WH.js → chunk-FVGFPB73.js} +2 -2
  13. package/dist/{chunk-2AGSO3WH.js.map → chunk-FVGFPB73.js.map} +1 -1
  14. package/dist/cover-hero.js +1 -1
  15. package/dist/link-card.js +2 -2
  16. package/dist/media-grid.js +1 -1
  17. package/dist/media-grid.js.map +1 -1
  18. package/dist/page-hero.js +1 -1
  19. package/dist/page-index.js +1 -1
  20. package/dist/service-card.js +1 -1
  21. package/dist/slot-picker.js +1 -1
  22. package/package.json +17 -3
  23. package/registry/account-menu.json +1 -1
  24. package/registry/account-settings.json +1 -1
  25. package/registry/achievement.json +1 -1
  26. package/registry/action-bar.json +2 -2
  27. package/registry/activity-feed.json +1 -1
  28. package/registry/agenda.json +1 -1
  29. package/registry/alert.json +1 -1
  30. package/registry/anatomy-plate.json +1 -1
  31. package/registry/app-header.json +1 -1
  32. package/registry/app-shell.json +1 -1
  33. package/registry/argument-card.json +2 -2
  34. package/registry/audio-player.json +1 -1
  35. package/registry/auth-layout.json +1 -1
  36. package/registry/avatar.json +1 -1
  37. package/registry/badge.json +1 -1
  38. package/registry/bird-plate.json +1 -1
  39. package/registry/button.json +1 -1
  40. package/registry/captcha.json +1 -1
  41. package/registry/card-grid.json +1 -1
  42. package/registry/card.json +1 -1
  43. package/registry/catalogue-page.json +1 -1
  44. package/registry/changelog-page.json +1 -1
  45. package/registry/chapter.json +514 -0
  46. package/registry/chat.json +1 -1
  47. package/registry/checkbox.json +1 -1
  48. package/registry/chip-link.json +1 -1
  49. package/registry/chip-row.json +1 -1
  50. package/registry/chip.json +1 -1
  51. package/registry/choice-grid.json +2 -2
  52. package/registry/code-block.json +1 -1
  53. package/registry/combobox.json +1 -1
  54. package/registry/command.json +1 -1
  55. package/registry/comparison-table.json +1 -1
  56. package/registry/component-page.json +1 -1
  57. package/registry/confirm-button.json +1 -1
  58. package/registry/contact-page.json +1 -1
  59. package/registry/continue-prompt.json +1 -1
  60. package/registry/countdown.json +1 -1
  61. package/registry/cover-hero.json +2 -2
  62. package/registry/cta-panel.json +1 -1
  63. package/registry/dashboard-layout.json +1 -1
  64. package/registry/data-table.json +1 -1
  65. package/registry/date-picker.json +1 -1
  66. package/registry/decision-list.json +1 -1
  67. package/registry/description-list.json +1 -1
  68. package/registry/detail-layout.json +1 -1
  69. package/registry/dialog.json +1 -1
  70. package/registry/docs-article-page.json +1 -1
  71. package/registry/docs-hub-page.json +1 -1
  72. package/registry/docs-layout.json +1 -1
  73. package/registry/docs-nav.json +1 -1
  74. package/registry/document-viewer.json +1 -1
  75. package/registry/drop-overlay.json +1 -1
  76. package/registry/dropdown-menu.json +1 -1
  77. package/registry/elephant.json +2 -2
  78. package/registry/empty-state.json +1 -1
  79. package/registry/engine-flow.json +1 -1
  80. package/registry/error-page.json +1 -1
  81. package/registry/examples/chapter/album.d.ts +4 -0
  82. package/registry/examples/chapter/album.js +34 -0
  83. package/registry/examples/chapter/album.tsx +30 -0
  84. package/registry/examples/chapter/detail.d.ts +4 -0
  85. package/registry/examples/chapter/detail.js +21 -0
  86. package/registry/examples/chapter/detail.tsx +16 -0
  87. package/registry/examples/chapter/foot.d.ts +4 -0
  88. package/registry/examples/chapter/foot.js +20 -0
  89. package/registry/examples/chapter/foot.tsx +15 -0
  90. package/registry/examples/chapter/head.d.ts +4 -0
  91. package/registry/examples/chapter/head.js +22 -0
  92. package/registry/examples/chapter/head.tsx +17 -0
  93. package/registry/examples/chapter/in-ruled-page.d.ts +4 -0
  94. package/registry/examples/chapter/in-ruled-page.js +23 -0
  95. package/registry/examples/chapter/in-ruled-page.tsx +19 -0
  96. package/registry/examples/chapter/index.d.ts +4 -0
  97. package/registry/examples/chapter/index.js +34 -0
  98. package/registry/examples/chapter/index.tsx +30 -0
  99. package/registry/examples/chapter/reading-page.d.ts +4 -0
  100. package/registry/examples/chapter/reading-page.js +34 -0
  101. package/registry/examples/chapter/reading-page.tsx +25 -0
  102. package/registry/field.json +1 -1
  103. package/registry/file-upload.json +1 -1
  104. package/registry/filter-bar.json +1 -1
  105. package/registry/flock.json +1 -1
  106. package/registry/footnote.json +1 -1
  107. package/registry/force-update-screen.json +1 -1
  108. package/registry/form.json +1 -1
  109. package/registry/gallery-page.json +1 -1
  110. package/registry/genui.json +1 -1
  111. package/registry/glossary-term.json +1 -1
  112. package/registry/glossary.json +1 -1
  113. package/registry/glow-card.json +1 -1
  114. package/registry/goal-gauge.json +1 -1
  115. package/registry/how-it-works-page.json +1 -1
  116. package/registry/image-cropper.json +1 -1
  117. package/registry/index.json +38 -6
  118. package/registry/iso.json +1 -1
  119. package/registry/kanban.json +1 -1
  120. package/registry/kbd.json +1 -1
  121. package/registry/kpi-explained.json +1 -1
  122. package/registry/landing-page.json +1 -1
  123. package/registry/landscape.json +1 -1
  124. package/registry/language-switch.json +1 -1
  125. package/registry/legal-page.json +1 -1
  126. package/registry/link-card.json +2 -2
  127. package/registry/list.json +1 -1
  128. package/registry/loader.json +1 -1
  129. package/registry/longform-page.json +1 -1
  130. package/registry/maintenance-screen.json +1 -1
  131. package/registry/map.json +2 -2
  132. package/registry/markdown.json +1 -1
  133. package/registry/marketing-layout.json +1 -1
  134. package/registry/marquee.json +1 -1
  135. package/registry/mascot-quote.json +1 -1
  136. package/registry/mascot-state.json +1 -1
  137. package/registry/mascots.json +1 -1
  138. package/registry/match-dialog.json +1 -1
  139. package/registry/media-capture.json +1 -1
  140. package/registry/media-gallery.json +1 -1
  141. package/registry/media-grid.json +2 -2
  142. package/registry/meta-row.json +1 -1
  143. package/registry/meter.json +1 -1
  144. package/registry/nav-bar.json +2 -2
  145. package/registry/next-actions.json +1 -1
  146. package/registry/number-ticker.json +1 -1
  147. package/registry/opening-hours.json +1 -1
  148. package/registry/page-header.json +1 -1
  149. package/registry/page-hero.json +2 -2
  150. package/registry/page-index.json +2 -2
  151. package/registry/page.json +1 -1
  152. package/registry/pagination.json +1 -1
  153. package/registry/photo-grid.json +1 -1
  154. package/registry/pointer-light.json +1 -1
  155. package/registry/popover.json +1 -1
  156. package/registry/pricing-page.json +1 -1
  157. package/registry/product-page.json +1 -1
  158. package/registry/profile-card.json +1 -1
  159. package/registry/progress.json +1 -1
  160. package/registry/promise-card.json +1 -1
  161. package/registry/radio-group.json +1 -1
  162. package/registry/rail-nav.json +2 -2
  163. package/registry/recipes.json +1 -1
  164. package/registry/router.json +1 -1
  165. package/registry/scroll-stage.json +1 -1
  166. package/registry/search-field.json +1 -1
  167. package/registry/section-backdrop.json +1 -1
  168. package/registry/separator.json +1 -1
  169. package/registry/service-card.json +2 -2
  170. package/registry/settings-layout.json +1 -1
  171. package/registry/showcase.json +1 -1
  172. package/registry/site-footer.json +2 -2
  173. package/registry/sites.json +1 -1
  174. package/registry/skeleton.json +1 -1
  175. package/registry/skip-link.json +1 -1
  176. package/registry/slider.json +1 -1
  177. package/registry/slot-picker.json +2 -2
  178. package/registry/slot.json +1 -1
  179. package/registry/specimen-tag.json +1 -1
  180. package/registry/spinner.json +1 -1
  181. package/registry/split-view.json +1 -1
  182. package/registry/stage-banner.json +1 -1
  183. package/registry/stat.json +1 -1
  184. package/registry/status-page.json +1 -1
  185. package/registry/step-progress.json +1 -1
  186. package/registry/step-showcase.json +1 -1
  187. package/registry/stepper.json +1 -1
  188. package/registry/sticky-action.json +1 -1
  189. package/registry/swipe-deck.json +1 -1
  190. package/registry/switch.json +1 -1
  191. package/registry/tabs.json +1 -1
  192. package/registry/tag-input.json +1 -1
  193. package/registry/tag-selector.json +1 -1
  194. package/registry/taped-photo.json +1 -1
  195. package/registry/terminal-replay.json +1 -1
  196. package/registry/text-style.json +1 -1
  197. package/registry/theme.json +1 -1
  198. package/registry/toast.json +1 -1
  199. package/registry/toc.json +1 -1
  200. package/registry/tooltip.json +1 -1
  201. package/registry/tree-view.json +1 -1
  202. package/registry/update-prompt.json +1 -1
  203. package/registry/upload-queue.json +1 -1
  204. package/registry/verified-badge.json +1 -1
  205. package/registry/vertical-video.json +1 -1
  206. package/registry/video-player.json +1 -1
  207. package/registry/video-trimmer.json +1 -1
  208. package/registry/virtual.json +1 -1
  209. package/registry/waveform.json +1 -1
  210. /package/dist/{chunk-IBDTAMPF.js.map → chunk-7JG3VR4C.js.map} +0 -0
@@ -0,0 +1,514 @@
1
+ {
2
+ "name": "chapter",
3
+ "type": "primitive",
4
+ "title": "Chapter",
5
+ "summary": "The chapters of a story on the Cahier's ruled lines (ADR-004): `ChapterIndex` (one ruled line per chapter, the number in the margin, the serif title the link, the latest marked « New »), `ChapterHead` and `ChapterFoot` for the top and bottom of a chapter's page. No boxes. `variant=\"album\"` draws each chapter's cover as a taped print beside its title, the rows alternating left and right.",
6
+ "description": "The chapters of a story on the Cahier's ruled lines (ADR-004): `ChapterIndex` (one ruled line per chapter, the number in the margin, the serif title the link, the latest marked « New »), `ChapterHead` and `ChapterFoot` for the top and bottom of a chapter's page. No boxes. `variant=\"album\"` draws each chapter's cover as a taped print beside its title, the rows alternating left and right.",
7
+ "status": "beta",
8
+ "deprecated": null,
9
+ "category": "blocks",
10
+ "platforms": "web",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "n/a",
17
+ "reason": "A long read's index and page frame on a public site; an app lists its items with List.",
18
+ "use": "list"
19
+ },
20
+ "desktop": {
21
+ "state": "yes"
22
+ }
23
+ },
24
+ "whenToUse": [
25
+ "The chapters of a story or a series, read in order: a timeline you follow, never a feed of cards.",
26
+ "The home of a blog or a story, where each chapter has a cover: `variant=\"album\"`, light, a taped print and one line each.",
27
+ "The head of a chapter's page (its number, title, period and reading time, the standfirst once) and its foot (previous, next, back to the story)."
28
+ ],
29
+ "whenNotToUse": [
30
+ {
31
+ "when": "Entries the reader browses or picks among, with a picture or an argument each.",
32
+ "use": "link-card"
33
+ },
34
+ {
35
+ "when": "The sections of the page being read.",
36
+ "use": "toc"
37
+ },
38
+ {
39
+ "when": "A documentation's index with page numbers.",
40
+ "use": "page-index"
41
+ }
42
+ ],
43
+ "bestPractices": [
44
+ "Short numbers in the margin (`01`, `§ 1`): the margin is 3 rem wide; the word « Chapter » belongs to the page's heading.",
45
+ "One line per chapter: the title and its period. Say what was faced and what was built once, on the chapter's page (`ChapterHead`'s standfirst); `detail` is for an index that truly needs a second line.",
46
+ "In the album, give every chapter a `cover` with its real `width` and `height` (the print keeps its aspect-ratio, nothing shifts) and one short `detail`: what was built, in a sentence. The covers after the first two load lazily.",
47
+ "Inside a `Page` that already draws the margin, the chapters keep their number column and draw no second red line.",
48
+ "Mark only the latest chapter `isNew`, and only while it is new.",
49
+ "`ChapterIndex` rules its own lines so every row lands on one: put it in a section without `paper`, or pass `paper={false}` when the list starts on the page's module.",
50
+ "Give `ChapterHead` the two `versusLabels`: the arrow of the standfirst is drawn, a screen reader hears the labels.",
51
+ "Pass the router link as `linkAs` so internal chapters prefetch."
52
+ ],
53
+ "accessibility": {
54
+ "keyboard": [
55
+ {
56
+ "keys": "Tab",
57
+ "action": "Moves from one chapter's title to the next; the focus ring outlines the whole row."
58
+ }
59
+ ],
60
+ "notes": [
61
+ "`ChapterIndex` is an ordered list (`role=\"list\"`, kept by VoiceOver without bullets) of links named by their title; the « New » mark is read inside the link, the number and the period around it.",
62
+ "The whole row is the link's hit area (a stretched pseudo-element); only the title and the mark are its name.",
63
+ "`ChapterHead` renders a real `h1` (or `h2` with `level={2}`); the meta line's separator and the standfirst's arrow are hidden from assistive technology.",
64
+ "`ChapterFoot` is a `nav` named by `label` (« Chapters »); its links carry `rel=\"prev\"` and `rel=\"next\"`, their arrows are decorative.",
65
+ "In the album the cover is decorative (`alt=\"\"`, the print `aria-hidden`): the title is the link's name, the whole row its hit area.",
66
+ "Nothing moves: hover and focus underline the title in the hue, also under reduced motion."
67
+ ]
68
+ },
69
+ "related": [
70
+ "page",
71
+ "page-index",
72
+ "toc",
73
+ "footnote",
74
+ "taped-photo"
75
+ ],
76
+ "web": {
77
+ "entry": "@krizaka/ui/chapter",
78
+ "import": "import { ChapterIndex, ChapterHead, ChapterFoot } from \"@krizaka/ui/chapter\";",
79
+ "props": [
80
+ {
81
+ "component": "LinkComponent",
82
+ "description": "The app's link component (Next's `Link`, React Router's `Link`…): receives `href`, `className` and the children.",
83
+ "props": []
84
+ },
85
+ {
86
+ "component": "ChapterFoot",
87
+ "description": "The foot of a chapter: the previous and the next on the ruled lines, then one line back to the story.",
88
+ "props": [
89
+ {
90
+ "name": "back",
91
+ "type": "{ href: string; label: ReactNode; }",
92
+ "default": null,
93
+ "description": "The line back to the story: `{ href: \"/\", label: \"Back to the story\" }`.",
94
+ "required": false
95
+ },
96
+ {
97
+ "name": "label",
98
+ "type": "string",
99
+ "default": "Chapters",
100
+ "description": "The accessible name of the navigation.",
101
+ "required": false
102
+ },
103
+ {
104
+ "name": "labels",
105
+ "type": "{ previous: ReactNode; next: ReactNode; }",
106
+ "default": "{ previous: \"Previous\", next: \"Next\" }",
107
+ "description": "The quiet words beside each link.",
108
+ "required": false
109
+ },
110
+ {
111
+ "name": "linkAs",
112
+ "type": "LinkComponent",
113
+ "default": null,
114
+ "description": "The app's router link (Next's `Link`).",
115
+ "required": false
116
+ },
117
+ {
118
+ "name": "next",
119
+ "type": "ChapterLink",
120
+ "default": null,
121
+ "description": "The chapter after this one (none on the latest).",
122
+ "required": false
123
+ },
124
+ {
125
+ "name": "paper",
126
+ "type": "ChapterPaper",
127
+ "default": "lines",
128
+ "description": "The ruling under the links.",
129
+ "required": false
130
+ },
131
+ {
132
+ "name": "previous",
133
+ "type": "ChapterLink",
134
+ "default": null,
135
+ "description": "The chapter before this one (none on the first).",
136
+ "required": false
137
+ }
138
+ ]
139
+ },
140
+ {
141
+ "component": "ChapterHead",
142
+ "description": "The head of a chapter: the number in the margin, the title, the period and the reading time, the standfirst once.",
143
+ "props": [
144
+ {
145
+ "name": "number",
146
+ "type": "ReactNode",
147
+ "default": null,
148
+ "description": "The chapter's number, hung in the margin: `01`, `Chapitre 1`.",
149
+ "required": true
150
+ },
151
+ {
152
+ "name": "title",
153
+ "type": "ReactNode",
154
+ "default": null,
155
+ "description": "The title: the page's `h1`.",
156
+ "required": true
157
+ },
158
+ {
159
+ "name": "built",
160
+ "type": "ReactNode",
161
+ "default": null,
162
+ "description": "What was built: its second half, after the arrow.",
163
+ "required": false
164
+ },
165
+ {
166
+ "name": "faced",
167
+ "type": "ReactNode",
168
+ "default": null,
169
+ "description": "What was faced: the first half of the standfirst.",
170
+ "required": false
171
+ },
172
+ {
173
+ "name": "level",
174
+ "type": "1 | 2",
175
+ "default": "1",
176
+ "description": "The level of the title.",
177
+ "required": false
178
+ },
179
+ {
180
+ "name": "period",
181
+ "type": "ReactNode",
182
+ "default": null,
183
+ "description": "When it happened: `janv. – mars 2026`.",
184
+ "required": false
185
+ },
186
+ {
187
+ "name": "readingTime",
188
+ "type": "ReactNode",
189
+ "default": null,
190
+ "description": "How long it reads: `8 min`.",
191
+ "required": false
192
+ },
193
+ {
194
+ "name": "versusLabels",
195
+ "type": "readonly [string, string]",
196
+ "default": null,
197
+ "description": "The words a screen reader says before each half (the arrow is drawn, not read): `[\"What I faced\", \"What we built\"]`.",
198
+ "required": false
199
+ }
200
+ ]
201
+ },
202
+ {
203
+ "component": "ChapterIndex",
204
+ "description": "The chapters of a story as a sober timeline: one ruled line each (or a taped print each), the number in the margin, the title the link.",
205
+ "props": [
206
+ {
207
+ "name": "chapters",
208
+ "type": "readonly ChapterEntry[]",
209
+ "default": null,
210
+ "description": "The chapters, in reading order.",
211
+ "required": true
212
+ },
213
+ {
214
+ "name": "label",
215
+ "type": "string",
216
+ "default": null,
217
+ "description": "The accessible name of the list, when no heading names it (`aria-labelledby`).",
218
+ "required": false
219
+ },
220
+ {
221
+ "name": "linkAs",
222
+ "type": "LinkComponent",
223
+ "default": null,
224
+ "description": "The app's router link (Next's `Link`), so internal chapters prefetch.",
225
+ "required": false
226
+ },
227
+ {
228
+ "name": "newLabel",
229
+ "type": "ReactNode",
230
+ "default": "New",
231
+ "description": "The word of the latest chapter's mark.",
232
+ "required": false
233
+ },
234
+ {
235
+ "name": "paper",
236
+ "type": "ChapterPaper",
237
+ "default": "lines",
238
+ "description": "The ruling under the rows. `false` when the page is ruled already and the list starts on its module.",
239
+ "required": false
240
+ },
241
+ {
242
+ "name": "variant",
243
+ "type": "\"lines\" | \"album\"",
244
+ "default": "lines",
245
+ "description": "`lines` — one ruled line per chapter (the compact index); `album` — each chapter's `cover` as a taped print beside\nits title, its `detail` (one short line) and its period, the rows alternating left and right.",
246
+ "required": false
247
+ }
248
+ ]
249
+ }
250
+ ],
251
+ "examples": [
252
+ {
253
+ "name": "index",
254
+ "title": "The chapters of a story",
255
+ "description": "Eight chapters in reading order, one ruled line each, the latest marked « Nouveau ».",
256
+ "path": "examples/chapter/index.tsx",
257
+ "module": "@krizaka/ui/registry/examples/chapter/index",
258
+ "code": "import { ChapterIndex } from \"@krizaka/ui/chapter\";\n\nconst chapters = [\n [\"Pourquoi je construis Krizaka à Montréal\", \"16 oct.\"],\n [\"Un moteur d'IA sur un portable\", \"16 – 17 oct.\"],\n [\"Ce que coûtent une image et une vidéo : mes factures\", \"15 – 21 oct.\"],\n [\"D'un clip à un film : ce que nous avons raté d'abord\", \"17 – 21 oct.\"],\n [\"Une seule interface, du web au Mac\", \"17 – 21 oct.\"],\n [\"Les premiers produits : Yomizaka et Akinaza\", \"17 – 23 oct.\"],\n [\"Un résultat vide n'est pas un succès\", \"16 – 23 oct.\"],\n [\"Construire presque seul, avec des agents\", \"12 – 23 oct.\"],\n] as const;\n\n// The chapters of a story in reading order, one ruled line each: the number in the margin, the serif title is the link,\n// the period quiet on the right. The latest wears a small « Nouveau » in the hue.\nexport default function ChapterIndexStory() {\n return (\n <ChapterIndex\n label=\"Les chapitres\"\n newLabel=\"Nouveau\"\n chapters={chapters.map(([title, period], i) => ({\n number: String(i + 1).padStart(2, \"0\"),\n title,\n period,\n href: `#chapitre-${i + 1}`,\n isNew: i === chapters.length - 1,\n }))}\n />\n );\n}\n"
259
+ },
260
+ {
261
+ "name": "detail",
262
+ "title": "With a second line",
263
+ "description": "The optional `detail`: what was faced → what was built, on the next ruled line.",
264
+ "path": "examples/chapter/detail.tsx",
265
+ "module": "@krizaka/ui/registry/examples/chapter/detail",
266
+ "code": "import { ChapterIndex } from \"@krizaka/ui/chapter\";\n\n// The optional second line (`detail`): what was faced → what was built, on the next ruled line. Off by default — the\n// chapter's page says it once, as its standfirst.\nexport default function ChapterIndexDetail() {\n return (\n <ChapterIndex\n label=\"Chapters\"\n chapters={[\n { number: \"06\", title: \"The first products: Yomizaka and Akinaza\", period: \"Oct 17 – 23\", href: \"#ch-6\", detail: \"A pay stub nobody can read → a rule that cites its article\" },\n { number: \"07\", title: \"An empty result is not a success\", period: \"Oct 16 – 23\", href: \"#ch-7\", detail: \"A green screen with an empty ranking → a run never delivers nothing as a success\" },\n { number: \"08\", title: \"Building almost alone, with agents\", period: \"Oct 12 – 23\", href: \"#ch-8\", detail: \"Thirty agents, one night → rules written after every mistake\", isNew: true },\n ]}\n />\n );\n}\n"
267
+ },
268
+ {
269
+ "name": "album",
270
+ "title": "The chapters as an album",
271
+ "description": "`variant=\"album\"`: each cover a taped print, alternating left and right, the title, one line and the period beside it.",
272
+ "path": "examples/chapter/album.tsx",
273
+ "module": "@krizaka/ui/registry/examples/chapter/album",
274
+ "code": "import { ChapterIndex } from \"@krizaka/ui/chapter\";\n\nconst CDN = \"https://krizaka-assets.b-cdn.net/site/blog\";\nconst chapters = [\n [\"Pourquoi je construis Krizaka à Montréal\", \"Une ville, un portable, et l'envie de garder ses données chez soi.\", \"16 oct.\", \"shared/clip-old-port-480.69e43c77ff.webp\", 832, 480],\n [\"Un moteur d'IA sur un portable\", \"Des modèles locaux derrière une seule adresse lue en base.\", \"16 – 17 oct.\", \"shared/gallery-mini-picnic-480.851511db06.webp\", 1360, 768],\n [\"Ce que coûtent une image et une vidéo\", \"Les factures, ligne par ligne, et ce que nous avons coupé.\", \"15 – 21 oct.\", \"shared/compare-montreal-winter-sd35-480.b4ee6d3ee1.webp\", 1024, 1024],\n [\"D'un clip à un film\", \"Ce que nous avons raté d'abord, puis le relais.\", \"17 – 21 oct.\", \"cinematic-ai-videos/relay-medina-cover-480.4165085446.webp\", 1351, 760],\n] as const;\n\n// The blog's chapters as an album: each cover a taped print, the prints facing each other from left to right, beside\n// them the number in the margin, the serif title (the link), one short line and the period. The latest is « Nouveau ».\nexport default function ChapterAlbumStory() {\n return (\n <ChapterIndex\n variant=\"album\"\n label=\"Les chapitres\"\n newLabel=\"Nouveau\"\n chapters={chapters.map(([title, detail, period, cover, width, height], i) => ({\n number: `§ ${i + 1}`,\n title,\n detail,\n period,\n cover: { src: `${CDN}/${cover}`, width, height },\n href: `#chapitre-${i + 1}`,\n isNew: i === chapters.length - 1,\n }))}\n />\n );\n}\n"
275
+ },
276
+ {
277
+ "name": "in-ruled-page",
278
+ "title": "Inside a ruled page",
279
+ "description": "In a `Page` with `margin`, one red rule only: the page's.",
280
+ "path": "examples/chapter/in-ruled-page.tsx",
281
+ "module": "@krizaka/ui/registry/examples/chapter/in-ruled-page",
282
+ "code": "import { ChapterIndex } from \"@krizaka/ui/chapter\";\nimport { Page } from \"@krizaka/ui/page\";\n\n// Inside a page that already draws the margin, the chapters keep their number column and draw no second red line.\nexport default function ChapterInRuledPage() {\n return (\n <Page paper=\"lines\" margin size=\"narrow\">\n <ChapterIndex\n paper={false}\n label=\"Les chapitres\"\n chapters={[\n { number: \"01\", title: \"Pourquoi je construis Krizaka à Montréal\", period: \"16 oct.\", href: \"#chapitre-1\" },\n { number: \"02\", title: \"Un moteur d'IA sur un portable\", period: \"16 – 17 oct.\", href: \"#chapitre-2\" },\n { number: \"03\", title: \"Un résultat vide n'est pas un succès\", period: \"16 – 23 oct.\", href: \"#chapitre-3\" },\n ]}\n />\n </Page>\n );\n}\n"
283
+ },
284
+ {
285
+ "name": "head",
286
+ "title": "A chapter's head",
287
+ "description": "The number in the margin, the title, the period and the reading time, the standfirst once.",
288
+ "path": "examples/chapter/head.tsx",
289
+ "module": "@krizaka/ui/registry/examples/chapter/head",
290
+ "code": "import { ChapterHead } from \"@krizaka/ui/chapter\";\n\n// The head of a chapter's page: the number in the margin, the title, one quiet line (period · reading time), and the\n// faced → built line once, as the standfirst. The arrow is drawn; a screen reader hears the two labels instead.\nexport default function ChapterHeadStory() {\n return (\n <ChapterHead\n number=\"07\"\n title=\"Un résultat vide n'est pas un succès\"\n period=\"16 – 23 octobre 2026\"\n readingTime=\"9 min de lecture\"\n faced=\"Un écran vert avec un classement vide\"\n built=\"un run ne livre jamais un résultat vide comme un succès\"\n versusLabels={[\"Ce que j'ai affronté\", \"Ce qu'on a construit\"]}\n />\n );\n}\n"
291
+ },
292
+ {
293
+ "name": "foot",
294
+ "title": "A chapter's foot",
295
+ "description": "The previous and the next chapters on the lines, then one line back to the story.",
296
+ "path": "examples/chapter/foot.tsx",
297
+ "module": "@krizaka/ui/registry/examples/chapter/foot",
298
+ "code": "import { ChapterFoot } from \"@krizaka/ui/chapter\";\n\n// The foot of a chapter's page: the previous and the next chapters as two quiet links on the lines, then one line back\n// to the story. On the first chapter, leave `previous` out; on the latest, `next`.\nexport default function ChapterFootStory() {\n return (\n <ChapterFoot\n label=\"Chapitres\"\n labels={{ previous: \"précédent\", next: \"suivant\" }}\n previous={{ number: \"06\", title: \"Les premiers produits : Yomizaka et Akinaza\", href: \"#chapitre-6\" }}\n next={{ number: \"08\", title: \"Construire presque seul, avec des agents\", href: \"#chapitre-8\" }}\n back={{ href: \"#histoire\", label: \"Retour à l'histoire\" }}\n />\n );\n}\n"
299
+ },
300
+ {
301
+ "name": "reading-page",
302
+ "title": "The latest chapter, top and bottom",
303
+ "description": "Head and foot around the reading column; no next chapter yet.",
304
+ "path": "examples/chapter/reading-page.tsx",
305
+ "module": "@krizaka/ui/registry/examples/chapter/reading-page",
306
+ "code": "import { ChapterFoot, ChapterHead } from \"@krizaka/ui/chapter\";\n\n// The latest chapter, top and bottom: its head, the reading column, and a foot with no next chapter yet.\nexport default function ChapterReadingPage() {\n return (\n <article className=\"grid gap-8\">\n <ChapterHead\n number=\"08\"\n title=\"Construire presque seul, avec des agents\"\n period=\"12 – 23 octobre 2026\"\n readingTime=\"11 min de lecture\"\n faced=\"Une trentaine d'agents, une seule nuit\"\n built=\"des règles écrites après chaque erreur\"\n versusLabels={[\"Ce que j'ai affronté\", \"Ce qu'on a construit\"]}\n />\n <p className=\"max-w-prose text-fg-secondary\">Le texte du chapitre, dans sa colonne de lecture.</p>\n <ChapterFoot\n label=\"Chapitres\"\n labels={{ previous: \"précédent\", next: \"suivant\" }}\n previous={{ number: \"07\", title: \"Un résultat vide n'est pas un succès\", href: \"#chapitre-7\" }}\n back={{ href: \"#histoire\", label: \"Retour à l'histoire\" }}\n />\n </article>\n );\n}\n"
307
+ }
308
+ ]
309
+ },
310
+ "native": null,
311
+ "files": [
312
+ {
313
+ "path": "block-link.tsx",
314
+ "content": "// The links of the page blocks (PageHero's back link, LinkCard, ArgumentCard, ActionBar): an internal path renders\n// through the app's router link (`linkAs`, e.g. Next's `Link`), anything else is an `<a>` that opens in a new tab.\n// Shared by the block entries; never exported on its own.\nimport type * as React from \"react\";\n\n/** The app's link component (Next's `Link`, React Router's `Link`…): receives `href`, `className` and the children. */\nexport type LinkComponent = React.ElementType<{ href: string; className?: string; children?: React.ReactNode; \"aria-label\"?: string; \"aria-describedby\"?: string }>;\n\n/** An address that leaves the app: an absolute URL or a protocol (`mailto:`). */\nexport const isExternal = (href: string) => /^[a-z][a-z0-9+.-]*:/i.test(href);\n\nexport function BlockLink({\n href,\n linkAs: Link = \"a\",\n external = isExternal(href),\n rel,\n ...props\n}: Omit<React.ComponentProps<\"a\">, \"href\"> & { href: string; linkAs?: LinkComponent; external?: boolean }) {\n if (external) return <a href={href} target=\"_blank\" rel={rel ?? \"noopener noreferrer\"} {...props} />;\n const Comp = Link as React.ElementType;\n return <Comp href={href} rel={rel} {...props} />;\n}\n"
315
+ },
316
+ {
317
+ "path": "chapter/chapter.tsx",
318
+ "content": "// Server-safe. The chapters of a story on the Cahier (ADR-004, blocks.css, krizaka-ui#617): a sober timeline, never a\n// stack of boxes. The rows sit on the notebook's ruled lines (`data-paper=\"lines\"`, the ruling of `Page`), the number\n// hangs in the margin beside its red rule (`data-margin`), the serif title is the link, the period is quiet.\n// - `ChapterIndex` — the chapters in reading order, one ruled line each (two at most), the latest marked « New »;\n// - `ChapterHead` — the head of a chapter's page: number in the margin, title, one quiet meta line, the standfirst;\n// - `ChapterFoot` — its foot: the previous and next chapters on the lines, then one line back to the story.\n// No border per row, no card, no button: the paper carries the rhythm. Every word arrives as a prop.\n// `ChapterIndex variant=\"album\"` (#621): each row is the chapter's cover as a `TapedPhoto`, the prints facing each other\n// from left to right, and beside it the same number, title, line and period — light, on the same lines.\nimport type * as React from \"react\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\nimport { TapedPhoto } from \"../taped-photo\";\n\n/** The notebook under the rows: `lines` (the ruling of `Page`, aligned on the rows) or `false` (the page's own paper). */\ntype ChapterPaper = \"lines\" | false;\n\nexport interface ChapterEntry {\n /** Its number, hung in the margin: `01`, `§ 1`. */\n number: React.ReactNode;\n /** The title, in the serif: the link. */\n title: React.ReactNode;\n /** Where it leads. */\n href: string;\n /** When it happened, quiet on the right (under the title on a phone): `janv. – mars 2026`, or a `<time>`. */\n period?: React.ReactNode;\n /** One more line under the title (what was faced → what was built). Off unless given: the chapter's page says it. */\n detail?: React.ReactNode;\n /** The latest chapter: a small mark after the title, in the hue (`newLabel`). */\n isNew?: boolean;\n /** The page the reader is on. */\n current?: boolean;\n /** Its cover, drawn by the `album` variant as a taped print. Decorative there (`alt=\"\"`): the title carries the link. */\n cover?: ChapterCover;\n}\n\n/** A chapter's cover: its intrinsic size gives the print its aspect-ratio, so nothing shifts while it loads. */\nexport interface ChapterCover {\n src: string;\n width: number;\n height: number;\n srcSet?: string;\n /** @default \"(max-width: 34rem) 60vw, 26rem\" */\n sizes?: string;\n}\n\n/** The covers above the fold load at once; the rest wait for the reader. */\nconst EAGER_COVERS = 2;\n/** The prints face each other: the left one leans left, the right one leans right (TapedPhoto's practice). */\nconst ALBUM_TILT = 1.4;\n\nfunction ChapterPrint({ cover, index }: { cover: ChapterCover; index: number }) {\n return (\n <TapedPhoto className=\"kz-chapter-print\" tilt={index % 2 === 0 ? -ALBUM_TILT : ALBUM_TILT} aria-hidden>\n <img\n src={cover.src}\n srcSet={cover.srcSet}\n sizes={cover.srcSet ? (cover.sizes ?? \"(max-width: 34rem) 60vw, 26rem\") : undefined}\n width={cover.width}\n height={cover.height}\n alt=\"\"\n loading={index < EAGER_COVERS ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n />\n </TapedPhoto>\n );\n}\n\n/** One ruled row: number in the margin, the title as the link (its hit area the whole row), an aside on the right. */\nfunction ChapterRow({\n number,\n title,\n href,\n aside,\n mark,\n detail,\n current,\n rel,\n linkAs,\n}: {\n number?: React.ReactNode;\n title: React.ReactNode;\n href: string;\n aside?: React.ReactNode;\n mark?: React.ReactNode;\n detail?: React.ReactNode;\n current?: boolean;\n rel?: string;\n linkAs?: LinkComponent;\n}) {\n return (\n <>\n <span className=\"kz-chapter-no\">{number}</span>\n <BlockLink href={href} linkAs={linkAs} rel={rel} className=\"kz-chapter-link\" aria-current={current ? \"page\" : undefined}>\n <span className=\"kz-chapter-title\">{title}</span>\n {mark && (\n <>\n {\" \"}\n <span className=\"kz-chapter-new\">{mark}</span>\n </>\n )}\n </BlockLink>\n {aside && (\n <>\n {/* A space, not a margin: in a narrow column the period runs on after the title, and starts flush when it wraps. */}{\" \"}\n <span className=\"kz-chapter-period\">{aside}</span>\n </>\n )}\n {detail && <span className=\"kz-chapter-detail\">{detail}</span>}\n </>\n );\n}\n\nexport type ChapterIndexProps = Omit<React.OlHTMLAttributes<HTMLOListElement>, \"children\"> & {\n /** The chapters, in reading order. */\n chapters: readonly ChapterEntry[];\n /** The word of the latest chapter's mark. @default \"New\" */\n newLabel?: React.ReactNode;\n /** The accessible name of the list, when no heading names it (`aria-labelledby`). */\n label?: string;\n /** The ruling under the rows. `false` when the page is ruled already and the list starts on its module. @default \"lines\" */\n paper?: ChapterPaper;\n /** The app's router link (Next's `Link`), so internal chapters prefetch. */\n linkAs?: LinkComponent;\n /**\n * `lines` — one ruled line per chapter (the compact index); `album` — each chapter's `cover` as a taped print beside\n * its title, its `detail` (one short line) and its period, the rows alternating left and right.\n * @default \"lines\"\n */\n variant?: \"lines\" | \"album\";\n};\n\n/** The chapters of a story as a sober timeline: one ruled line each (or a taped print each), the number in the margin, the title the link. */\nexport function ChapterIndex({ chapters, newLabel = \"New\", label, paper = \"lines\", linkAs, variant = \"lines\", className, ...props }: ChapterIndexProps) {\n const album = variant === \"album\";\n return (\n // `role=\"list\"`: a list without bullets is still a list to VoiceOver.\n <ol\n role=\"list\"\n aria-label={label}\n data-paper={paper || undefined}\n data-margin=\"\"\n data-variant={album ? \"album\" : undefined}\n className={cn(\"kz-chapters\", className)}\n {...props}\n >\n {chapters.map((c, i) => (\n <li key={i} className=\"kz-chapter-row\" data-new={c.isNew ? \"\" : undefined} data-detail={c.detail ? \"\" : undefined}>\n {album && c.cover && <ChapterPrint cover={c.cover} index={i} />}\n <ChapterRow number={c.number} title={c.title} href={c.href} aside={c.period} mark={c.isNew ? newLabel : undefined} detail={c.detail} current={c.current} linkAs={linkAs} />\n </li>\n ))}\n </ol>\n );\n}\n\nexport type ChapterHeadProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** The chapter's number, hung in the margin: `01`, `Chapitre 1`. */\n number: React.ReactNode;\n /** The title: the page's `h1`. */\n title: React.ReactNode;\n /** When it happened: `janv. – mars 2026`. */\n period?: React.ReactNode;\n /** How long it reads: `8 min`. */\n readingTime?: React.ReactNode;\n /** What was faced: the first half of the standfirst. */\n faced?: React.ReactNode;\n /** What was built: its second half, after the arrow. */\n built?: React.ReactNode;\n /** The words a screen reader says before each half (the arrow is drawn, not read): `[\"What I faced\", \"What we built\"]`. */\n versusLabels?: readonly [string, string];\n /** The level of the title. @default 1 */\n level?: 1 | 2;\n};\n\n/** The head of a chapter: the number in the margin, the title, the period and the reading time, the standfirst once. */\nexport function ChapterHead({ number, title, period, readingTime, faced, built, versusLabels, level = 1, className, children, ...props }: ChapterHeadProps) {\n const H = level === 1 ? \"h1\" : \"h2\";\n const meta = [period, readingTime].filter((m) => m !== undefined && m !== null && m !== \"\");\n return (\n <header data-margin=\"\" className={cn(\"kz-chapter-head\", className)} {...props}>\n <p className=\"kz-chapter-no\">{number}</p>\n <H className=\"kz-chapter-head-title\">{title}</H>\n {meta.length > 0 && (\n <p className=\"kz-chapter-meta\">\n {meta.map((m, i) => (\n <span key={i}>\n {i > 0 && <span aria-hidden> · </span>}\n {m}\n </span>\n ))}\n </p>\n )}\n {faced && built && (\n <p className=\"kz-chapter-standfirst\">\n {versusLabels && <span className=\"kz-sr-only\">{versusLabels[0]}, </span>}\n {faced}\n <span className=\"kz-chapter-arrow\" aria-hidden>\n {\" → \"}\n </span>\n {versusLabels && <span className=\"kz-sr-only\">; {versusLabels[1]}, </span>}\n {built}\n </p>\n )}\n {children}\n </header>\n );\n}\n\nexport interface ChapterLink {\n /** Its number, in the margin: `02`. */\n number?: React.ReactNode;\n /** Its title, the link. */\n title: React.ReactNode;\n /** Where it leads. */\n href: string;\n}\n\nexport type ChapterFootProps = Omit<React.HTMLAttributes<HTMLElement>, \"children\"> & {\n /** The chapter before this one (none on the first). */\n previous?: ChapterLink;\n /** The chapter after this one (none on the latest). */\n next?: ChapterLink;\n /** The line back to the story: `{ href: \"/\", label: \"Back to the story\" }`. */\n back?: { href: string; label: React.ReactNode };\n /** The quiet words beside each link. @default { previous: \"Previous\", next: \"Next\" } */\n labels?: { previous: React.ReactNode; next: React.ReactNode };\n /** The accessible name of the navigation. @default \"Chapters\" */\n label?: string;\n /** The ruling under the links. @default \"lines\" */\n paper?: ChapterPaper;\n /** The app's router link (Next's `Link`). */\n linkAs?: LinkComponent;\n};\n\n/** The foot of a chapter: the previous and the next on the ruled lines, then one line back to the story. */\nexport function ChapterFoot({\n previous,\n next,\n back,\n labels = { previous: \"Previous\", next: \"Next\" },\n label = \"Chapters\",\n paper = \"lines\",\n linkAs,\n className,\n ...props\n}: ChapterFootProps) {\n return (\n <nav aria-label={label} data-paper={paper || undefined} data-margin=\"\" className={cn(\"kz-chapters kz-chapter-foot\", className)} {...props}>\n <ul role=\"list\">\n {previous && (\n <li className=\"kz-chapter-row\" data-dir=\"previous\">\n <ChapterRow\n number={previous.number}\n title={previous.title}\n href={previous.href}\n rel=\"prev\"\n linkAs={linkAs}\n aside={\n <>\n <span aria-hidden>← </span>\n {labels.previous}\n </>\n }\n />\n </li>\n )}\n {next && (\n <li className=\"kz-chapter-row\" data-dir=\"next\">\n <ChapterRow\n number={next.number}\n title={next.title}\n href={next.href}\n rel=\"next\"\n linkAs={linkAs}\n aside={\n <>\n {labels.next}\n <span aria-hidden> →</span>\n </>\n }\n />\n </li>\n )}\n </ul>\n {back && (\n <p className=\"kz-chapter-back\">\n <BlockLink href={back.href} linkAs={linkAs} className=\"kz-chapter-back-link\">\n <span aria-hidden>↩ </span>\n {back.label}\n </BlockLink>\n </p>\n )}\n </nav>\n );\n}\n"
319
+ },
320
+ {
321
+ "path": "chapter/index.ts",
322
+ "content": "/** @krizaka/ui/chapter — `ChapterIndex`, `ChapterHead`, `ChapterFoot`: the chapters of a story on the Cahier's ruled lines (krizaka-ui#617). Server-safe. */\nexport type { LinkComponent } from \"../block-link\";\nexport {\n type ChapterCover,\n type ChapterEntry,\n ChapterFoot,\n type ChapterFootProps,\n ChapterHead,\n type ChapterHeadProps,\n ChapterIndex,\n type ChapterIndexProps,\n type ChapterLink,\n} from \"./chapter\";\n"
323
+ },
324
+ {
325
+ "path": "chapter/meta.ts",
326
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Chapter\",\n summary:\n \"The chapters of a story on the Cahier's ruled lines (ADR-004): `ChapterIndex` (one ruled line per chapter, the number in the margin, the serif title the link, the latest marked « New »), `ChapterHead` and `ChapterFoot` for the top and bottom of a chapter's page. No boxes. `variant=\\\"album\\\"` draws each chapter's cover as a taped print beside its title, the rows alternating left and right.\",\n status: \"beta\",\n category: \"blocks\",\n platforms: \"web\",\n parity: {\n native: { status: \"n/a\", reason: \"A long read's index and page frame on a public site; an app lists its items with List.\", use: \"list\" },\n desktop: \"web\",\n },\n whenToUse: [\n \"The chapters of a story or a series, read in order: a timeline you follow, never a feed of cards.\",\n \"The home of a blog or a story, where each chapter has a cover: `variant=\\\"album\\\"`, light, a taped print and one line each.\",\n \"The head of a chapter's page (its number, title, period and reading time, the standfirst once) and its foot (previous, next, back to the story).\",\n ],\n whenNotToUse: [\n { when: \"Entries the reader browses or picks among, with a picture or an argument each.\", use: \"link-card\" },\n { when: \"The sections of the page being read.\", use: \"toc\" },\n { when: \"A documentation's index with page numbers.\", use: \"page-index\" },\n ],\n bestPractices: [\n \"Short numbers in the margin (`01`, `§ 1`): the margin is 3 rem wide; the word « Chapter » belongs to the page's heading.\",\n \"One line per chapter: the title and its period. Say what was faced and what was built once, on the chapter's page (`ChapterHead`'s standfirst); `detail` is for an index that truly needs a second line.\",\n \"In the album, give every chapter a `cover` with its real `width` and `height` (the print keeps its aspect-ratio, nothing shifts) and one short `detail`: what was built, in a sentence. The covers after the first two load lazily.\",\n \"Inside a `Page` that already draws the margin, the chapters keep their number column and draw no second red line.\",\n \"Mark only the latest chapter `isNew`, and only while it is new.\",\n \"`ChapterIndex` rules its own lines so every row lands on one: put it in a section without `paper`, or pass `paper={false}` when the list starts on the page's module.\",\n \"Give `ChapterHead` the two `versusLabels`: the arrow of the standfirst is drawn, a screen reader hears the labels.\",\n \"Pass the router link as `linkAs` so internal chapters prefetch.\",\n ],\n accessibility: {\n keyboard: [{ keys: \"Tab\", action: \"Moves from one chapter's title to the next; the focus ring outlines the whole row.\" }],\n notes: [\n \"`ChapterIndex` is an ordered list (`role=\\\"list\\\"`, kept by VoiceOver without bullets) of links named by their title; the « New » mark is read inside the link, the number and the period around it.\",\n \"The whole row is the link's hit area (a stretched pseudo-element); only the title and the mark are its name.\",\n \"`ChapterHead` renders a real `h1` (or `h2` with `level={2}`); the meta line's separator and the standfirst's arrow are hidden from assistive technology.\",\n \"`ChapterFoot` is a `nav` named by `label` (« Chapters »); its links carry `rel=\\\"prev\\\"` and `rel=\\\"next\\\"`, their arrows are decorative.\",\n \"In the album the cover is decorative (`alt=\\\"\\\"`, the print `aria-hidden`): the title is the link's name, the whole row its hit area.\",\n \"Nothing moves: hover and focus underline the title in the hue, also under reduced motion.\",\n ],\n },\n related: [\"page\", \"page-index\", \"toc\", \"footnote\", \"taped-photo\"],\n web: {\n imports: [\"ChapterIndex\", \"ChapterHead\", \"ChapterFoot\"],\n examples: [\n { name: \"index\", title: \"The chapters of a story\", description: \"Eight chapters in reading order, one ruled line each, the latest marked « Nouveau ».\" },\n { name: \"detail\", title: \"With a second line\", description: \"The optional `detail`: what was faced → what was built, on the next ruled line.\" },\n { name: \"album\", title: \"The chapters as an album\", description: \"`variant=\\\"album\\\"`: each cover a taped print, alternating left and right, the title, one line and the period beside it.\" },\n { name: \"in-ruled-page\", title: \"Inside a ruled page\", description: \"In a `Page` with `margin`, one red rule only: the page's.\" },\n { name: \"head\", title: \"A chapter's head\", description: \"The number in the margin, the title, the period and the reading time, the standfirst once.\" },\n { name: \"foot\", title: \"A chapter's foot\", description: \"The previous and the next chapters on the lines, then one line back to the story.\" },\n { name: \"reading-page\", title: \"The latest chapter, top and bottom\", description: \"Head and foot around the reading column; no next chapter yet.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
327
+ },
328
+ {
329
+ "path": "meta.ts",
330
+ "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 | \"agenda\"\n | \"account-settings\"\n | \"drop-overlay\"\n | \"upload-queue\"\n | \"achievement\"\n | \"anatomy-plate\"\n | \"bird-plate\"\n | \"taped-photo\"\n | \"text-style\"\n | \"flock\"\n | \"elephant\"\n | \"footnote\"\n | \"page-index\"\n | \"chapter\"\n | \"specimen-tag\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"chip-row\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cover-hero\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"engine-flow\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glossary\"\n | \"glossary-term\"\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 | \"kpi-explained\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"language-switch\"\n | \"media-gallery\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"opening-hours\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"router\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"service-card\"\n | \"settings-layout\"\n | \"account-menu\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot-picker\"\n | \"slot\"\n | \"spinner\"\n | \"sticky-action\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
331
+ }
332
+ ],
333
+ "dependencies": [],
334
+ "registryDependencies": [
335
+ "cn",
336
+ "taped-photo"
337
+ ],
338
+ "demo": {
339
+ "path": "examples/chapter/index.tsx",
340
+ "content": "import { ChapterIndex } from \"@krizaka/ui/chapter\";\n\nconst chapters = [\n [\"Pourquoi je construis Krizaka à Montréal\", \"16 oct.\"],\n [\"Un moteur d'IA sur un portable\", \"16 – 17 oct.\"],\n [\"Ce que coûtent une image et une vidéo : mes factures\", \"15 – 21 oct.\"],\n [\"D'un clip à un film : ce que nous avons raté d'abord\", \"17 – 21 oct.\"],\n [\"Une seule interface, du web au Mac\", \"17 – 21 oct.\"],\n [\"Les premiers produits : Yomizaka et Akinaza\", \"17 – 23 oct.\"],\n [\"Un résultat vide n'est pas un succès\", \"16 – 23 oct.\"],\n [\"Construire presque seul, avec des agents\", \"12 – 23 oct.\"],\n] as const;\n\n// The chapters of a story in reading order, one ruled line each: the number in the margin, the serif title is the link,\n// the period quiet on the right. The latest wears a small « Nouveau » in the hue.\nexport default function ChapterIndexStory() {\n return (\n <ChapterIndex\n label=\"Les chapitres\"\n newLabel=\"Nouveau\"\n chapters={chapters.map(([title, period], i) => ({\n number: String(i + 1).padStart(2, \"0\"),\n title,\n period,\n href: `#chapitre-${i + 1}`,\n isNew: i === chapters.length - 1,\n }))}\n />\n );\n}\n"
341
+ },
342
+ "props": [
343
+ {
344
+ "component": "LinkComponent",
345
+ "description": "The app's link component (Next's `Link`, React Router's `Link`…): receives `href`, `className` and the children.",
346
+ "props": []
347
+ },
348
+ {
349
+ "component": "ChapterFoot",
350
+ "description": "The foot of a chapter: the previous and the next on the ruled lines, then one line back to the story.",
351
+ "props": [
352
+ {
353
+ "name": "back",
354
+ "type": "{ href: string; label: ReactNode; }",
355
+ "default": null,
356
+ "description": "The line back to the story: `{ href: \"/\", label: \"Back to the story\" }`.",
357
+ "required": false
358
+ },
359
+ {
360
+ "name": "label",
361
+ "type": "string",
362
+ "default": "Chapters",
363
+ "description": "The accessible name of the navigation.",
364
+ "required": false
365
+ },
366
+ {
367
+ "name": "labels",
368
+ "type": "{ previous: ReactNode; next: ReactNode; }",
369
+ "default": "{ previous: \"Previous\", next: \"Next\" }",
370
+ "description": "The quiet words beside each link.",
371
+ "required": false
372
+ },
373
+ {
374
+ "name": "linkAs",
375
+ "type": "LinkComponent",
376
+ "default": null,
377
+ "description": "The app's router link (Next's `Link`).",
378
+ "required": false
379
+ },
380
+ {
381
+ "name": "next",
382
+ "type": "ChapterLink",
383
+ "default": null,
384
+ "description": "The chapter after this one (none on the latest).",
385
+ "required": false
386
+ },
387
+ {
388
+ "name": "paper",
389
+ "type": "ChapterPaper",
390
+ "default": "lines",
391
+ "description": "The ruling under the links.",
392
+ "required": false
393
+ },
394
+ {
395
+ "name": "previous",
396
+ "type": "ChapterLink",
397
+ "default": null,
398
+ "description": "The chapter before this one (none on the first).",
399
+ "required": false
400
+ }
401
+ ]
402
+ },
403
+ {
404
+ "component": "ChapterHead",
405
+ "description": "The head of a chapter: the number in the margin, the title, the period and the reading time, the standfirst once.",
406
+ "props": [
407
+ {
408
+ "name": "number",
409
+ "type": "ReactNode",
410
+ "default": null,
411
+ "description": "The chapter's number, hung in the margin: `01`, `Chapitre 1`.",
412
+ "required": true
413
+ },
414
+ {
415
+ "name": "title",
416
+ "type": "ReactNode",
417
+ "default": null,
418
+ "description": "The title: the page's `h1`.",
419
+ "required": true
420
+ },
421
+ {
422
+ "name": "built",
423
+ "type": "ReactNode",
424
+ "default": null,
425
+ "description": "What was built: its second half, after the arrow.",
426
+ "required": false
427
+ },
428
+ {
429
+ "name": "faced",
430
+ "type": "ReactNode",
431
+ "default": null,
432
+ "description": "What was faced: the first half of the standfirst.",
433
+ "required": false
434
+ },
435
+ {
436
+ "name": "level",
437
+ "type": "1 | 2",
438
+ "default": "1",
439
+ "description": "The level of the title.",
440
+ "required": false
441
+ },
442
+ {
443
+ "name": "period",
444
+ "type": "ReactNode",
445
+ "default": null,
446
+ "description": "When it happened: `janv. – mars 2026`.",
447
+ "required": false
448
+ },
449
+ {
450
+ "name": "readingTime",
451
+ "type": "ReactNode",
452
+ "default": null,
453
+ "description": "How long it reads: `8 min`.",
454
+ "required": false
455
+ },
456
+ {
457
+ "name": "versusLabels",
458
+ "type": "readonly [string, string]",
459
+ "default": null,
460
+ "description": "The words a screen reader says before each half (the arrow is drawn, not read): `[\"What I faced\", \"What we built\"]`.",
461
+ "required": false
462
+ }
463
+ ]
464
+ },
465
+ {
466
+ "component": "ChapterIndex",
467
+ "description": "The chapters of a story as a sober timeline: one ruled line each (or a taped print each), the number in the margin, the title the link.",
468
+ "props": [
469
+ {
470
+ "name": "chapters",
471
+ "type": "readonly ChapterEntry[]",
472
+ "default": null,
473
+ "description": "The chapters, in reading order.",
474
+ "required": true
475
+ },
476
+ {
477
+ "name": "label",
478
+ "type": "string",
479
+ "default": null,
480
+ "description": "The accessible name of the list, when no heading names it (`aria-labelledby`).",
481
+ "required": false
482
+ },
483
+ {
484
+ "name": "linkAs",
485
+ "type": "LinkComponent",
486
+ "default": null,
487
+ "description": "The app's router link (Next's `Link`), so internal chapters prefetch.",
488
+ "required": false
489
+ },
490
+ {
491
+ "name": "newLabel",
492
+ "type": "ReactNode",
493
+ "default": "New",
494
+ "description": "The word of the latest chapter's mark.",
495
+ "required": false
496
+ },
497
+ {
498
+ "name": "paper",
499
+ "type": "ChapterPaper",
500
+ "default": "lines",
501
+ "description": "The ruling under the rows. `false` when the page is ruled already and the list starts on its module.",
502
+ "required": false
503
+ },
504
+ {
505
+ "name": "variant",
506
+ "type": "\"lines\" | \"album\"",
507
+ "default": "lines",
508
+ "description": "`lines` — one ruled line per chapter (the compact index); `album` — each chapter's `cover` as a taped print beside\nits title, its `detail` (one short line) and its period, the rows alternating left and right.",
509
+ "required": false
510
+ }
511
+ ]
512
+ }
513
+ ]
514
+ }