@krizaka/ui 3.53.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.
package/dist/blocks.css CHANGED
@@ -53,6 +53,9 @@
53
53
  pointer-events: none;
54
54
  }
55
55
  .kz-frame[data-margin] { --kz-margin-x: max(8px, calc(var(--kz-gutter) - 12px)); }
56
+ /* One margin rule per page (#620): a block that hangs its numbers in a margin (the chapter blocks, a
57
+ Section) inside a page that already draws one keeps its column but draws no second red line. */
58
+ [data-margin] [data-margin]::before { content: none; }
56
59
  .kz-heading-number,
57
60
  .kz-chapter-no,
58
61
  .kz-margin-note {
@@ -1335,6 +1338,43 @@ a.kz-fact:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); o
1335
1338
  .kz-chapter-detail { display: block; padding-left: 0; }
1336
1339
  }
1337
1340
 
1341
+ /* The album (#621): each chapter is its cover as a taped print beside its words, the rows alternating left and right and
1342
+ the prints facing each other (the tilt is set per row). One grid per row — margin · print · words — whose middle rows
1343
+ hold the title, the line and the period, so the number shares the title's baseline and the words sit centred on the
1344
+ print. The print keeps its own aspect-ratio (the cover's width and height), so nothing shifts while it loads. Light:
1345
+ no card, no border, the lines of the paper run under it all; nothing moves. */
1346
+ .kz-chapters[data-variant="album"] { --kz-chapters-max: 60rem; --kz-album-print: minmax(0, 5fr); --kz-album-words: minmax(0, 6fr); }
1347
+ .kz-chapters[data-variant="album"] .kz-chapter-row {
1348
+ grid-template-columns: var(--kz-margin-x) var(--kz-album-print) var(--kz-album-words);
1349
+ grid-template-rows: 1fr auto auto auto 1fr;
1350
+ padding-block: var(--kz-chapter-line);
1351
+ }
1352
+ .kz-chapters[data-variant="album"] .kz-chapter-row:nth-child(even) { grid-template-columns: var(--kz-margin-x) var(--kz-album-words) var(--kz-album-print); }
1353
+ .kz-chapters[data-variant="album"] .kz-chapter-print { grid-column: 2; grid-row: 1 / -1; align-self: center; margin: 16px 32px 16px var(--kz-chapter-gap); }
1354
+ .kz-chapters[data-variant="album"] .kz-chapter-row:nth-child(even) .kz-chapter-print { grid-column: 3; margin: 16px 28px 16px 32px; }
1355
+ .kz-chapters[data-variant="album"] .kz-chapter-row > :not(.kz-chapter-print, .kz-chapter-no) { grid-column: 3; }
1356
+ .kz-chapters[data-variant="album"] .kz-chapter-row:nth-child(even) > :not(.kz-chapter-print, .kz-chapter-no) { grid-column: 2; }
1357
+ .kz-chapters[data-variant="album"] .kz-chapter-no { grid-column: 1; grid-row: 2; }
1358
+ .kz-chapters[data-variant="album"] .kz-chapter-link { grid-row: 2; font-size: clamp(1.3125rem, 2.4vw, 1.625rem); line-height: 1.25; text-wrap: balance; }
1359
+ .kz-chapters[data-variant="album"] .kz-chapter-detail { grid-row: 3; max-width: 36ch; margin-top: 8px; font-size: 1rem; line-height: 1.5; text-wrap: pretty; }
1360
+ .kz-chapters[data-variant="album"] .kz-chapter-period { grid-row: 4; margin-top: 12px; padding-left: var(--kz-chapter-gap); }
1361
+ /* A phone: one column — the print above its words (at most 60 % wide, still tilted), the number beside the title. */
1362
+ @container (max-width: 34rem) {
1363
+ /* `:nth-child(n)` lifts these over the wide rows' alternation: on a phone every row is the same column. */
1364
+ .kz-chapters[data-variant="album"] .kz-chapter-row:nth-child(n) {
1365
+ display: grid;
1366
+ grid-template-columns: var(--kz-margin-x) minmax(0, 1fr);
1367
+ grid-template-rows: auto;
1368
+ padding: 0 0 var(--kz-chapter-line);
1369
+ }
1370
+ .kz-chapters[data-variant="album"] .kz-chapter-row:nth-child(n) .kz-chapter-print {
1371
+ grid-column: 2; grid-row: 1; justify-self: start; width: 60%; margin: 20px 0 24px calc(var(--kz-chapter-gap) + 4px);
1372
+ }
1373
+ .kz-chapters[data-variant="album"] .kz-chapter-row:nth-child(even) .kz-chapter-print { justify-self: end; margin: 20px 12px 24px 0; }
1374
+ .kz-chapters[data-variant="album"] .kz-chapter-row:nth-child(n) > :not(.kz-chapter-print, .kz-chapter-no) { grid-column: 2; display: block; padding-left: var(--kz-chapter-gap); }
1375
+ .kz-chapters[data-variant="album"] .kz-chapter-row:nth-child(n) .kz-chapter-no { display: block; width: auto; margin: 0; }
1376
+ }
1377
+
1338
1378
  /* The foot: the previous and the next as two rows of the index, then one quiet line back to the story. */
1339
1379
  .kz-chapter-row[data-dir] .kz-chapter-period { color: var(--kz-text-secondary); }
1340
1380
  .kz-chapter-back { margin: 0; padding-left: calc(var(--kz-margin-x) + var(--kz-chapter-gap)); font-size: 14.5px; line-height: var(--kz-chapter-line); }
package/dist/chapter.d.ts CHANGED
@@ -18,6 +18,17 @@ interface ChapterEntry {
18
18
  isNew?: boolean;
19
19
  /** The page the reader is on. */
20
20
  current?: boolean;
21
+ /** Its cover, drawn by the `album` variant as a taped print. Decorative there (`alt=""`): the title carries the link. */
22
+ cover?: ChapterCover;
23
+ }
24
+ /** A chapter's cover: its intrinsic size gives the print its aspect-ratio, so nothing shifts while it loads. */
25
+ interface ChapterCover {
26
+ src: string;
27
+ width: number;
28
+ height: number;
29
+ srcSet?: string;
30
+ /** @default "(max-width: 34rem) 60vw, 26rem" */
31
+ sizes?: string;
21
32
  }
22
33
  type ChapterIndexProps = Omit<React.OlHTMLAttributes<HTMLOListElement>, "children"> & {
23
34
  /** The chapters, in reading order. */
@@ -30,9 +41,15 @@ type ChapterIndexProps = Omit<React.OlHTMLAttributes<HTMLOListElement>, "childre
30
41
  paper?: ChapterPaper;
31
42
  /** The app's router link (Next's `Link`), so internal chapters prefetch. */
32
43
  linkAs?: LinkComponent;
44
+ /**
45
+ * `lines` — one ruled line per chapter (the compact index); `album` — each chapter's `cover` as a taped print beside
46
+ * its title, its `detail` (one short line) and its period, the rows alternating left and right.
47
+ * @default "lines"
48
+ */
49
+ variant?: "lines" | "album";
33
50
  };
34
- /** The chapters of a story as a sober timeline: one ruled line each, the number in the margin, the title the link. */
35
- declare function ChapterIndex({ chapters, newLabel, label, paper, linkAs, className, ...props }: ChapterIndexProps): React.JSX.Element;
51
+ /** 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. */
52
+ declare function ChapterIndex({ chapters, newLabel, label, paper, linkAs, variant, className, ...props }: ChapterIndexProps): React.JSX.Element;
36
53
  type ChapterHeadProps = Omit<React.HTMLAttributes<HTMLElement>, "title"> & {
37
54
  /** The chapter's number, hung in the margin: `01`, `Chapitre 1`. */
38
55
  number: React.ReactNode;
@@ -86,4 +103,4 @@ type ChapterFootProps = Omit<React.HTMLAttributes<HTMLElement>, "children"> & {
86
103
  /** The foot of a chapter: the previous and the next on the ruled lines, then one line back to the story. */
87
104
  declare function ChapterFoot({ previous, next, back, labels, label, paper, linkAs, className, ...props }: ChapterFootProps): React.JSX.Element;
88
105
 
89
- export { type ChapterEntry, ChapterFoot, type ChapterFootProps, ChapterHead, type ChapterHeadProps, ChapterIndex, type ChapterIndexProps, type ChapterLink, LinkComponent };
106
+ export { type ChapterCover, type ChapterEntry, ChapterFoot, type ChapterFootProps, ChapterHead, type ChapterHeadProps, ChapterIndex, type ChapterIndexProps, type ChapterLink, LinkComponent };
package/dist/chapter.js CHANGED
@@ -5,8 +5,43 @@ import {
5
5
  cn
6
6
  } from "./chunk-W675RFOA.js";
7
7
 
8
+ // src/taped-photo/taped-photo.tsx
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ function TapedPhoto({ children, caption, tilt = -1.6, tape = "corners", className, style, ...props }) {
11
+ return /* @__PURE__ */ jsxs(
12
+ "figure",
13
+ {
14
+ className: cn("kz-taped", className),
15
+ "data-tape": tape === "corners" ? void 0 : tape,
16
+ style: { ...style, ["--kz-taped-tilt"]: `${tilt}deg` },
17
+ ...props,
18
+ children: [
19
+ /* @__PURE__ */ jsx("div", { className: "kz-taped-picture", children }),
20
+ caption ? /* @__PURE__ */ jsx("figcaption", { className: "kz-taped-caption", children: caption }) : null
21
+ ]
22
+ }
23
+ );
24
+ }
25
+
8
26
  // src/chapter/chapter.tsx
9
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
27
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
28
+ var EAGER_COVERS = 2;
29
+ var ALBUM_TILT = 1.4;
30
+ function ChapterPrint({ cover, index }) {
31
+ return /* @__PURE__ */ jsx2(TapedPhoto, { className: "kz-chapter-print", tilt: index % 2 === 0 ? -ALBUM_TILT : ALBUM_TILT, "aria-hidden": true, children: /* @__PURE__ */ jsx2(
32
+ "img",
33
+ {
34
+ src: cover.src,
35
+ srcSet: cover.srcSet,
36
+ sizes: cover.srcSet ? cover.sizes ?? "(max-width: 34rem) 60vw, 26rem" : void 0,
37
+ width: cover.width,
38
+ height: cover.height,
39
+ alt: "",
40
+ loading: index < EAGER_COVERS ? "eager" : "lazy",
41
+ decoding: "async"
42
+ }
43
+ ) });
44
+ }
10
45
  function ChapterRow({
11
46
  number,
12
47
  title,
@@ -18,46 +53,62 @@ function ChapterRow({
18
53
  rel,
19
54
  linkAs
20
55
  }) {
21
- return /* @__PURE__ */ jsxs(Fragment, { children: [
22
- /* @__PURE__ */ jsx("span", { className: "kz-chapter-no", children: number }),
23
- /* @__PURE__ */ jsxs(BlockLink, { href, linkAs, rel, className: "kz-chapter-link", "aria-current": current ? "page" : void 0, children: [
24
- /* @__PURE__ */ jsx("span", { className: "kz-chapter-title", children: title }),
25
- mark && /* @__PURE__ */ jsxs(Fragment, { children: [
56
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
57
+ /* @__PURE__ */ jsx2("span", { className: "kz-chapter-no", children: number }),
58
+ /* @__PURE__ */ jsxs2(BlockLink, { href, linkAs, rel, className: "kz-chapter-link", "aria-current": current ? "page" : void 0, children: [
59
+ /* @__PURE__ */ jsx2("span", { className: "kz-chapter-title", children: title }),
60
+ mark && /* @__PURE__ */ jsxs2(Fragment, { children: [
26
61
  " ",
27
- /* @__PURE__ */ jsx("span", { className: "kz-chapter-new", children: mark })
62
+ /* @__PURE__ */ jsx2("span", { className: "kz-chapter-new", children: mark })
28
63
  ] })
29
64
  ] }),
30
- aside && /* @__PURE__ */ jsxs(Fragment, { children: [
65
+ aside && /* @__PURE__ */ jsxs2(Fragment, { children: [
31
66
  " ",
32
- /* @__PURE__ */ jsx("span", { className: "kz-chapter-period", children: aside })
67
+ /* @__PURE__ */ jsx2("span", { className: "kz-chapter-period", children: aside })
33
68
  ] }),
34
- detail && /* @__PURE__ */ jsx("span", { className: "kz-chapter-detail", children: detail })
69
+ detail && /* @__PURE__ */ jsx2("span", { className: "kz-chapter-detail", children: detail })
35
70
  ] });
36
71
  }
37
- function ChapterIndex({ chapters, newLabel = "New", label, paper = "lines", linkAs, className, ...props }) {
72
+ function ChapterIndex({ chapters, newLabel = "New", label, paper = "lines", linkAs, variant = "lines", className, ...props }) {
73
+ const album = variant === "album";
38
74
  return (
39
75
  // `role="list"`: a list without bullets is still a list to VoiceOver.
40
- /* @__PURE__ */ jsx("ol", { role: "list", "aria-label": label, "data-paper": paper || void 0, "data-margin": "", className: cn("kz-chapters", className), ...props, children: chapters.map((c, i) => /* @__PURE__ */ jsx("li", { className: "kz-chapter-row", "data-new": c.isNew ? "" : void 0, "data-detail": c.detail ? "" : void 0, children: /* @__PURE__ */ jsx(ChapterRow, { number: c.number, title: c.title, href: c.href, aside: c.period, mark: c.isNew ? newLabel : void 0, detail: c.detail, current: c.current, linkAs }) }, i)) })
76
+ /* @__PURE__ */ jsx2(
77
+ "ol",
78
+ {
79
+ role: "list",
80
+ "aria-label": label,
81
+ "data-paper": paper || void 0,
82
+ "data-margin": "",
83
+ "data-variant": album ? "album" : void 0,
84
+ className: cn("kz-chapters", className),
85
+ ...props,
86
+ children: chapters.map((c, i) => /* @__PURE__ */ jsxs2("li", { className: "kz-chapter-row", "data-new": c.isNew ? "" : void 0, "data-detail": c.detail ? "" : void 0, children: [
87
+ album && c.cover && /* @__PURE__ */ jsx2(ChapterPrint, { cover: c.cover, index: i }),
88
+ /* @__PURE__ */ jsx2(ChapterRow, { number: c.number, title: c.title, href: c.href, aside: c.period, mark: c.isNew ? newLabel : void 0, detail: c.detail, current: c.current, linkAs })
89
+ ] }, i))
90
+ }
91
+ )
41
92
  );
42
93
  }
43
94
  function ChapterHead({ number, title, period, readingTime, faced, built, versusLabels, level = 1, className, children, ...props }) {
44
95
  const H = level === 1 ? "h1" : "h2";
45
96
  const meta = [period, readingTime].filter((m) => m !== void 0 && m !== null && m !== "");
46
- return /* @__PURE__ */ jsxs("header", { "data-margin": "", className: cn("kz-chapter-head", className), ...props, children: [
47
- /* @__PURE__ */ jsx("p", { className: "kz-chapter-no", children: number }),
48
- /* @__PURE__ */ jsx(H, { className: "kz-chapter-head-title", children: title }),
49
- meta.length > 0 && /* @__PURE__ */ jsx("p", { className: "kz-chapter-meta", children: meta.map((m, i) => /* @__PURE__ */ jsxs("span", { children: [
50
- i > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: " \xB7 " }),
97
+ return /* @__PURE__ */ jsxs2("header", { "data-margin": "", className: cn("kz-chapter-head", className), ...props, children: [
98
+ /* @__PURE__ */ jsx2("p", { className: "kz-chapter-no", children: number }),
99
+ /* @__PURE__ */ jsx2(H, { className: "kz-chapter-head-title", children: title }),
100
+ meta.length > 0 && /* @__PURE__ */ jsx2("p", { className: "kz-chapter-meta", children: meta.map((m, i) => /* @__PURE__ */ jsxs2("span", { children: [
101
+ i > 0 && /* @__PURE__ */ jsx2("span", { "aria-hidden": true, children: " \xB7 " }),
51
102
  m
52
103
  ] }, i)) }),
53
- faced && built && /* @__PURE__ */ jsxs("p", { className: "kz-chapter-standfirst", children: [
54
- versusLabels && /* @__PURE__ */ jsxs("span", { className: "kz-sr-only", children: [
104
+ faced && built && /* @__PURE__ */ jsxs2("p", { className: "kz-chapter-standfirst", children: [
105
+ versusLabels && /* @__PURE__ */ jsxs2("span", { className: "kz-sr-only", children: [
55
106
  versusLabels[0],
56
107
  ", "
57
108
  ] }),
58
109
  faced,
59
- /* @__PURE__ */ jsx("span", { className: "kz-chapter-arrow", "aria-hidden": true, children: " \u2192 " }),
60
- versusLabels && /* @__PURE__ */ jsxs("span", { className: "kz-sr-only", children: [
110
+ /* @__PURE__ */ jsx2("span", { className: "kz-chapter-arrow", "aria-hidden": true, children: " \u2192 " }),
111
+ versusLabels && /* @__PURE__ */ jsxs2("span", { className: "kz-sr-only", children: [
61
112
  "; ",
62
113
  versusLabels[1],
63
114
  ", "
@@ -78,9 +129,9 @@ function ChapterFoot({
78
129
  className,
79
130
  ...props
80
131
  }) {
81
- return /* @__PURE__ */ jsxs("nav", { "aria-label": label, "data-paper": paper || void 0, "data-margin": "", className: cn("kz-chapters kz-chapter-foot", className), ...props, children: [
82
- /* @__PURE__ */ jsxs("ul", { role: "list", children: [
83
- previous && /* @__PURE__ */ jsx("li", { className: "kz-chapter-row", "data-dir": "previous", children: /* @__PURE__ */ jsx(
132
+ return /* @__PURE__ */ jsxs2("nav", { "aria-label": label, "data-paper": paper || void 0, "data-margin": "", className: cn("kz-chapters kz-chapter-foot", className), ...props, children: [
133
+ /* @__PURE__ */ jsxs2("ul", { role: "list", children: [
134
+ previous && /* @__PURE__ */ jsx2("li", { className: "kz-chapter-row", "data-dir": "previous", children: /* @__PURE__ */ jsx2(
84
135
  ChapterRow,
85
136
  {
86
137
  number: previous.number,
@@ -88,13 +139,13 @@ function ChapterFoot({
88
139
  href: previous.href,
89
140
  rel: "prev",
90
141
  linkAs,
91
- aside: /* @__PURE__ */ jsxs(Fragment, { children: [
92
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u2190 " }),
142
+ aside: /* @__PURE__ */ jsxs2(Fragment, { children: [
143
+ /* @__PURE__ */ jsx2("span", { "aria-hidden": true, children: "\u2190 " }),
93
144
  labels.previous
94
145
  ] })
95
146
  }
96
147
  ) }),
97
- next && /* @__PURE__ */ jsx("li", { className: "kz-chapter-row", "data-dir": "next", children: /* @__PURE__ */ jsx(
148
+ next && /* @__PURE__ */ jsx2("li", { className: "kz-chapter-row", "data-dir": "next", children: /* @__PURE__ */ jsx2(
98
149
  ChapterRow,
99
150
  {
100
151
  number: next.number,
@@ -102,15 +153,15 @@ function ChapterFoot({
102
153
  href: next.href,
103
154
  rel: "next",
104
155
  linkAs,
105
- aside: /* @__PURE__ */ jsxs(Fragment, { children: [
156
+ aside: /* @__PURE__ */ jsxs2(Fragment, { children: [
106
157
  labels.next,
107
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: " \u2192" })
158
+ /* @__PURE__ */ jsx2("span", { "aria-hidden": true, children: " \u2192" })
108
159
  ] })
109
160
  }
110
161
  ) })
111
162
  ] }),
112
- back && /* @__PURE__ */ jsx("p", { className: "kz-chapter-back", children: /* @__PURE__ */ jsxs(BlockLink, { href: back.href, linkAs, className: "kz-chapter-back-link", children: [
113
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u21A9 " }),
163
+ back && /* @__PURE__ */ jsx2("p", { className: "kz-chapter-back", children: /* @__PURE__ */ jsxs2(BlockLink, { href: back.href, linkAs, className: "kz-chapter-back-link", children: [
164
+ /* @__PURE__ */ jsx2("span", { "aria-hidden": true, children: "\u21A9 " }),
114
165
  back.label
115
166
  ] }) })
116
167
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/chapter/chapter.tsx"],"sourcesContent":["// 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.\nimport type * as React from \"react\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\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}\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\n/** The chapters of a story as a sober timeline: one ruled line each, the number in the margin, the title the link. */\nexport function ChapterIndex({ chapters, newLabel = \"New\", label, paper = \"lines\", linkAs, className, ...props }: ChapterIndexProps) {\n return (\n // `role=\"list\"`: a list without bullets is still a list to VoiceOver.\n <ol role=\"list\" aria-label={label} data-paper={paper || undefined} data-margin=\"\" className={cn(\"kz-chapters\", className)} {...props}>\n {chapters.map((c, i) => (\n <li key={i} className=\"kz-chapter-row\" data-new={c.isNew ? \"\" : undefined} data-detail={c.detail ? \"\" : undefined}>\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/taped-photo/taped-photo.tsx","../src/chapter/chapter.tsx"],"sourcesContent":["// The taped photo of the Cahier (ADR-004, blocks.css): a print stuck in the notebook — slightly tilted, a thin white print\n// border, a soft paper shadow (layered, never a glow), strips of tape. Whatever it holds (an image, a video, a live scene)\n// is its picture; the tilt is a transform, so nothing shifts and the picture keeps its own box. Server-safe: no state.\nimport type * as React from \"react\";\n\nimport { cn } from \"../cn\";\n\nexport type TapedPhotoProps = React.HTMLAttributes<HTMLElement> & {\n /** The picture: an `img`, a `video`, a scene. It fills the print. */\n children: React.ReactNode;\n /** A line written under the picture, on the print. */\n caption?: React.ReactNode;\n /** The tilt in degrees (negative leans left); eased on a phone. @default -1.6 */\n tilt?: number;\n /** Where the tape holds it: two opposite `corners`, one strip at the `top`, or `none`. @default \"corners\" */\n tape?: \"corners\" | \"top\" | \"none\";\n};\n\n/** A print taped in the notebook: tilted, a white border, a paper shadow, tape at two corners or at the top. */\nexport function TapedPhoto({ children, caption, tilt = -1.6, tape = \"corners\", className, style, ...props }: TapedPhotoProps) {\n return (\n <figure\n className={cn(\"kz-taped\", className)}\n data-tape={tape === \"corners\" ? undefined : tape}\n style={{ ...style, [\"--kz-taped-tilt\" as string]: `${tilt}deg` }}\n {...props}\n >\n <div className=\"kz-taped-picture\">{children}</div>\n {caption ? <figcaption className=\"kz-taped-caption\">{caption}</figcaption> : null}\n </figure>\n );\n}\n","// 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"],"mappings":"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","names":["jsx","jsxs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krizaka/ui",
3
- "version": "3.53.0",
3
+ "version": "3.54.0",
4
4
  "description": "The Krizaka front-end platform's components: the primitives (button, card, dialog, toast, menu, field, tabs, chip, switch, slider, command palette, theme, countdown…) on the semantic --kz-* tokens, the animated marks (Krizaka, Orazaka, Orochia, Yasakia, Yororis) and the motion signature — web and /native.",
5
5
  "keywords": [
6
6
  "krizaka",
@@ -801,7 +801,7 @@
801
801
  ],
802
802
  "peerDependencies": {
803
803
  "@krizaka/query": ">=0.2.1",
804
- "@krizaka/tailwind": ">=3.53.0",
804
+ "@krizaka/tailwind": ">=3.54.0",
805
805
  "@maplibre/maplibre-react-native": ">=11",
806
806
  "@shopify/flash-list": ">=2",
807
807
  "expo-camera": ">=16",
@@ -860,8 +860,8 @@
860
860
  "tsup": "^8.5.1",
861
861
  "typescript": "^5.9.0",
862
862
  "vitest": "^4.1.11",
863
- "@krizaka/query": "0.2.1",
864
- "@krizaka/config": "0.7.0"
863
+ "@krizaka/config": "0.7.0",
864
+ "@krizaka/query": "0.2.1"
865
865
  },
866
866
  "engines": {
867
867
  "node": ">=22"
@@ -2030,7 +2030,7 @@
2030
2030
  {
2031
2031
  "name": "./chapter",
2032
2032
  "path": "dist/chapter.js",
2033
- "limit": "1.5 kB",
2033
+ "limit": "1.8 kB",
2034
2034
  "gzip": true,
2035
2035
  "ignore": [
2036
2036
  "tailwind-variants",
@@ -2692,8 +2692,8 @@
2692
2692
  "tailwind-variants": "^3.3.1",
2693
2693
  "zod": "^4.6.5",
2694
2694
  "@krizaka/intl": "^0.3.0",
2695
- "@krizaka/tokens": "^3.53.0",
2696
- "@krizaka/pacer": "^0.1.0"
2695
+ "@krizaka/pacer": "^0.1.0",
2696
+ "@krizaka/tokens": "^3.54.0"
2697
2697
  },
2698
2698
  "scripts": {
2699
2699
  "build": "node -e \"const fs=require('node:fs');fs.rmSync('dist',{recursive:true,force:true});for(const f of fs.readdirSync('registry/examples',{recursive:true}))if(/\\.(js|d\\.ts)$/.test(f))fs.rmSync('registry/examples/'+f)\" && tsup && node -e \"require('node:fs').copyFileSync('src/motion.css','dist/motion.css');require('node:fs').copyFileSync('src/blocks.css','dist/blocks.css');require('node:fs').copyFileSync('src/story.css','dist/story.css')\" && node scripts/build-registry.mjs",
@@ -2,8 +2,8 @@
2
2
  "name": "chapter",
3
3
  "type": "primitive",
4
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.",
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.",
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
7
  "status": "beta",
8
8
  "deprecated": null,
9
9
  "category": "blocks",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "whenToUse": [
25
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.",
26
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)."
27
28
  ],
28
29
  "whenNotToUse": [
@@ -42,6 +43,8 @@
42
43
  "bestPractices": [
43
44
  "Short numbers in the margin (`01`, `§ 1`): the margin is 3 rem wide; the word « Chapter » belongs to the page's heading.",
44
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.",
45
48
  "Mark only the latest chapter `isNew`, and only while it is new.",
46
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.",
47
50
  "Give `ChapterHead` the two `versusLabels`: the arrow of the standfirst is drawn, a screen reader hears the labels.",
@@ -59,6 +62,7 @@
59
62
  "The whole row is the link's hit area (a stretched pseudo-element); only the title and the mark are its name.",
60
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.",
61
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.",
62
66
  "Nothing moves: hover and focus underline the title in the hue, also under reduced motion."
63
67
  ]
64
68
  },
@@ -66,7 +70,8 @@
66
70
  "page",
67
71
  "page-index",
68
72
  "toc",
69
- "footnote"
73
+ "footnote",
74
+ "taped-photo"
70
75
  ],
71
76
  "web": {
72
77
  "entry": "@krizaka/ui/chapter",
@@ -196,7 +201,7 @@
196
201
  },
197
202
  {
198
203
  "component": "ChapterIndex",
199
- "description": "The chapters of a story as a sober timeline: one ruled line each, the number in the margin, the title the link.",
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.",
200
205
  "props": [
201
206
  {
202
207
  "name": "chapters",
@@ -232,6 +237,13 @@
232
237
  "default": "lines",
233
238
  "description": "The ruling under the rows. `false` when the page is ruled already and the list starts on its module.",
234
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
235
247
  }
236
248
  ]
237
249
  }
@@ -253,6 +265,22 @@
253
265
  "module": "@krizaka/ui/registry/examples/chapter/detail",
254
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"
255
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
+ },
256
284
  {
257
285
  "name": "head",
258
286
  "title": "A chapter's head",
@@ -287,15 +315,15 @@
287
315
  },
288
316
  {
289
317
  "path": "chapter/chapter.tsx",
290
- "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.\nimport type * as React from \"react\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\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}\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\n/** The chapters of a story as a sober timeline: one ruled line each, the number in the margin, the title the link. */\nexport function ChapterIndex({ chapters, newLabel = \"New\", label, paper = \"lines\", linkAs, className, ...props }: ChapterIndexProps) {\n return (\n // `role=\"list\"`: a list without bullets is still a list to VoiceOver.\n <ol role=\"list\" aria-label={label} data-paper={paper || undefined} data-margin=\"\" className={cn(\"kz-chapters\", className)} {...props}>\n {chapters.map((c, i) => (\n <li key={i} className=\"kz-chapter-row\" data-new={c.isNew ? \"\" : undefined} data-detail={c.detail ? \"\" : undefined}>\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"
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"
291
319
  },
292
320
  {
293
321
  "path": "chapter/index.ts",
294
- "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 ChapterEntry,\n ChapterFoot,\n type ChapterFootProps,\n ChapterHead,\n type ChapterHeadProps,\n ChapterIndex,\n type ChapterIndexProps,\n type ChapterLink,\n} from \"./chapter\";\n"
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"
295
323
  },
296
324
  {
297
325
  "path": "chapter/meta.ts",
298
- "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.\",\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 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 \"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 \"Nothing moves: hover and focus underline the title in the hue, also under reduced motion.\",\n ],\n },\n related: [\"page\", \"page-index\", \"toc\", \"footnote\"],\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: \"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"
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"
299
327
  },
300
328
  {
301
329
  "path": "meta.ts",
@@ -304,7 +332,8 @@
304
332
  ],
305
333
  "dependencies": [],
306
334
  "registryDependencies": [
307
- "cn"
335
+ "cn",
336
+ "taped-photo"
308
337
  ],
309
338
  "demo": {
310
339
  "path": "examples/chapter/index.tsx",
@@ -435,7 +464,7 @@
435
464
  },
436
465
  {
437
466
  "component": "ChapterIndex",
438
- "description": "The chapters of a story as a sober timeline: one ruled line each, the number in the margin, the title the link.",
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.",
439
468
  "props": [
440
469
  {
441
470
  "name": "chapters",
@@ -471,6 +500,13 @@
471
500
  "default": "lines",
472
501
  "description": "The ruling under the rows. `false` when the page is ruled already and the list starts on its module.",
473
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
474
510
  }
475
511
  ]
476
512
  }
@@ -141,7 +141,7 @@
141
141
  }
142
142
  ],
143
143
  "dependencies": [
144
- "@krizaka/tokens@^3.53.0"
144
+ "@krizaka/tokens@^3.54.0"
145
145
  ],
146
146
  "registryDependencies": [],
147
147
  "demo": {
@@ -0,0 +1,4 @@
1
+ import type { ComponentType } from "react";
2
+ /** A named example of the @krizaka/ui registry; `defaultOpen` opens what it opens (a dialog, a menu, toasts) at load. */
3
+ declare const Example: ComponentType<{ defaultOpen?: boolean }>;
4
+ export default Example;
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ // registry/examples/chapter/album.tsx
4
+ import { ChapterIndex } from "@krizaka/ui/chapter";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var CDN = "https://krizaka-assets.b-cdn.net/site/blog";
7
+ var chapters = [
8
+ ["Pourquoi je construis Krizaka \xE0 Montr\xE9al", "Une ville, un portable, et l'envie de garder ses donn\xE9es chez soi.", "16 oct.", "shared/clip-old-port-480.69e43c77ff.webp", 832, 480],
9
+ ["Un moteur d'IA sur un portable", "Des mod\xE8les locaux derri\xE8re une seule adresse lue en base.", "16 \u2013 17 oct.", "shared/gallery-mini-picnic-480.851511db06.webp", 1360, 768],
10
+ ["Ce que co\xFBtent une image et une vid\xE9o", "Les factures, ligne par ligne, et ce que nous avons coup\xE9.", "15 \u2013 21 oct.", "shared/compare-montreal-winter-sd35-480.b4ee6d3ee1.webp", 1024, 1024],
11
+ ["D'un clip \xE0 un film", "Ce que nous avons rat\xE9 d'abord, puis le relais.", "17 \u2013 21 oct.", "cinematic-ai-videos/relay-medina-cover-480.4165085446.webp", 1351, 760]
12
+ ];
13
+ function ChapterAlbumStory() {
14
+ return /* @__PURE__ */ jsx(
15
+ ChapterIndex,
16
+ {
17
+ variant: "album",
18
+ label: "Les chapitres",
19
+ newLabel: "Nouveau",
20
+ chapters: chapters.map(([title, detail, period, cover, width, height], i) => ({
21
+ number: `\xA7 ${i + 1}`,
22
+ title,
23
+ detail,
24
+ period,
25
+ cover: { src: `${CDN}/${cover}`, width, height },
26
+ href: `#chapitre-${i + 1}`,
27
+ isNew: i === chapters.length - 1
28
+ }))
29
+ }
30
+ );
31
+ }
32
+ export {
33
+ ChapterAlbumStory as default
34
+ };
@@ -0,0 +1,30 @@
1
+ import { ChapterIndex } from "@krizaka/ui/chapter";
2
+
3
+ const CDN = "https://krizaka-assets.b-cdn.net/site/blog";
4
+ const chapters = [
5
+ ["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],
6
+ ["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],
7
+ ["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],
8
+ ["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],
9
+ ] as const;
10
+
11
+ // The blog's chapters as an album: each cover a taped print, the prints facing each other from left to right, beside
12
+ // them the number in the margin, the serif title (the link), one short line and the period. The latest is « Nouveau ».
13
+ export default function ChapterAlbumStory() {
14
+ return (
15
+ <ChapterIndex
16
+ variant="album"
17
+ label="Les chapitres"
18
+ newLabel="Nouveau"
19
+ chapters={chapters.map(([title, detail, period, cover, width, height], i) => ({
20
+ number: `§ ${i + 1}`,
21
+ title,
22
+ detail,
23
+ period,
24
+ cover: { src: `${CDN}/${cover}`, width, height },
25
+ href: `#chapitre-${i + 1}`,
26
+ isNew: i === chapters.length - 1,
27
+ }))}
28
+ />
29
+ );
30
+ }
@@ -0,0 +1,4 @@
1
+ import type { ComponentType } from "react";
2
+ /** A named example of the @krizaka/ui registry; `defaultOpen` opens what it opens (a dialog, a menu, toasts) at load. */
3
+ declare const Example: ComponentType<{ defaultOpen?: boolean }>;
4
+ export default Example;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ // registry/examples/chapter/in-ruled-page.tsx
4
+ import { ChapterIndex } from "@krizaka/ui/chapter";
5
+ import { Page } from "@krizaka/ui/page";
6
+ import { jsx } from "react/jsx-runtime";
7
+ function ChapterInRuledPage() {
8
+ return /* @__PURE__ */ jsx(Page, { paper: "lines", margin: true, size: "narrow", children: /* @__PURE__ */ jsx(
9
+ ChapterIndex,
10
+ {
11
+ paper: false,
12
+ label: "Les chapitres",
13
+ chapters: [
14
+ { number: "01", title: "Pourquoi je construis Krizaka \xE0 Montr\xE9al", period: "16 oct.", href: "#chapitre-1" },
15
+ { number: "02", title: "Un moteur d'IA sur un portable", period: "16 \u2013 17 oct.", href: "#chapitre-2" },
16
+ { number: "03", title: "Un r\xE9sultat vide n'est pas un succ\xE8s", period: "16 \u2013 23 oct.", href: "#chapitre-3" }
17
+ ]
18
+ }
19
+ ) });
20
+ }
21
+ export {
22
+ ChapterInRuledPage as default
23
+ };
@@ -0,0 +1,19 @@
1
+ import { ChapterIndex } from "@krizaka/ui/chapter";
2
+ import { Page } from "@krizaka/ui/page";
3
+
4
+ // Inside a page that already draws the margin, the chapters keep their number column and draw no second red line.
5
+ export default function ChapterInRuledPage() {
6
+ return (
7
+ <Page paper="lines" margin size="narrow">
8
+ <ChapterIndex
9
+ paper={false}
10
+ label="Les chapitres"
11
+ chapters={[
12
+ { number: "01", title: "Pourquoi je construis Krizaka à Montréal", period: "16 oct.", href: "#chapitre-1" },
13
+ { number: "02", title: "Un moteur d'IA sur un portable", period: "16 – 17 oct.", href: "#chapitre-2" },
14
+ { number: "03", title: "Un résultat vide n'est pas un succès", period: "16 – 23 oct.", href: "#chapitre-3" },
15
+ ]}
16
+ />
17
+ </Page>
18
+ );
19
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krizaka/ui",
3
- "version": "3.53.0",
3
+ "version": "3.54.0",
4
4
  "templatePurposes": {
5
5
  "convince": {
6
6
  "title": "Convince",
@@ -1203,8 +1203,8 @@
1203
1203
  "name": "chapter",
1204
1204
  "type": "primitive",
1205
1205
  "title": "Chapter",
1206
- "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.",
1207
- "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.",
1206
+ "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.",
1207
+ "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.",
1208
1208
  "status": "beta",
1209
1209
  "category": "blocks",
1210
1210
  "platforms": "web",
@@ -1222,12 +1222,13 @@
1222
1222
  }
1223
1223
  },
1224
1224
  "examples": {
1225
- "web": 5,
1225
+ "web": 7,
1226
1226
  "native": 0
1227
1227
  },
1228
1228
  "dependencies": [],
1229
1229
  "registryDependencies": [
1230
- "cn"
1230
+ "cn",
1231
+ "taped-photo"
1231
1232
  ]
1232
1233
  },
1233
1234
  {
@@ -2295,7 +2296,7 @@
2295
2296
  "native": 0
2296
2297
  },
2297
2298
  "dependencies": [
2298
- "@krizaka/tokens@^3.53.0"
2299
+ "@krizaka/tokens@^3.54.0"
2299
2300
  ],
2300
2301
  "registryDependencies": []
2301
2302
  },
@@ -3413,7 +3414,7 @@
3413
3414
  "native": 2
3414
3415
  },
3415
3416
  "dependencies": [
3416
- "@krizaka/tokens@^3.53.0",
3417
+ "@krizaka/tokens@^3.54.0",
3417
3418
  "@protomaps/basemaps@5.7.2",
3418
3419
  "tailwind-variants@^3.3.1"
3419
3420
  ],
@@ -3852,7 +3853,7 @@
3852
3853
  "native": 0
3853
3854
  },
3854
3855
  "dependencies": [
3855
- "@krizaka/tokens@^3.53.0",
3856
+ "@krizaka/tokens@^3.54.0",
3856
3857
  "radix-ui@^1.7.0",
3857
3858
  "tailwind-variants@^3.3.1"
3858
3859
  ],
@@ -4546,7 +4547,7 @@
4546
4547
  "native": 0
4547
4548
  },
4548
4549
  "dependencies": [
4549
- "@krizaka/tokens@^3.53.0",
4550
+ "@krizaka/tokens@^3.54.0",
4550
4551
  "radix-ui@^1.7.0",
4551
4552
  "tailwind-variants@^3.3.1"
4552
4553
  ],
@@ -4919,7 +4920,7 @@
4919
4920
  "native": 0
4920
4921
  },
4921
4922
  "dependencies": [
4922
- "@krizaka/tokens@^3.53.0",
4923
+ "@krizaka/tokens@^3.54.0",
4923
4924
  "radix-ui@^1.7.0",
4924
4925
  "tailwind-variants@^3.3.1"
4925
4926
  ],
package/registry/map.json CHANGED
@@ -1074,7 +1074,7 @@
1074
1074
  }
1075
1075
  ],
1076
1076
  "dependencies": [
1077
- "@krizaka/tokens@^3.53.0",
1077
+ "@krizaka/tokens@^3.54.0",
1078
1078
  "@protomaps/basemaps@5.7.2",
1079
1079
  "tailwind-variants@^3.3.1"
1080
1080
  ],
@@ -664,7 +664,7 @@
664
664
  }
665
665
  ],
666
666
  "dependencies": [
667
- "@krizaka/tokens@^3.53.0",
667
+ "@krizaka/tokens@^3.54.0",
668
668
  "radix-ui@^1.7.0",
669
669
  "tailwind-variants@^3.3.1"
670
670
  ],
@@ -342,7 +342,7 @@
342
342
  }
343
343
  ],
344
344
  "dependencies": [
345
- "@krizaka/tokens@^3.53.0",
345
+ "@krizaka/tokens@^3.54.0",
346
346
  "radix-ui@^1.7.0",
347
347
  "tailwind-variants@^3.3.1"
348
348
  ],
@@ -343,7 +343,7 @@
343
343
  }
344
344
  ],
345
345
  "dependencies": [
346
- "@krizaka/tokens@^3.53.0",
346
+ "@krizaka/tokens@^3.54.0",
347
347
  "radix-ui@^1.7.0",
348
348
  "tailwind-variants@^3.3.1"
349
349
  ],