@krizaka/ui 3.28.0 → 3.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +2 -0
  2. package/dist/blocks.css +2 -0
  3. package/dist/docs-hub-page.d.ts +7 -1
  4. package/dist/docs-hub-page.js +2 -2
  5. package/dist/docs-hub-page.js.map +1 -1
  6. package/dist/engine-flow.d.ts +97 -0
  7. package/dist/engine-flow.js +214 -0
  8. package/dist/engine-flow.js.map +1 -0
  9. package/dist/motion.css +121 -1
  10. package/dist/native.d.ts +21 -5
  11. package/dist/native.js +65 -32
  12. package/dist/native.js.map +1 -1
  13. package/package.json +20 -6
  14. package/registry/account-menu.json +1 -1
  15. package/registry/achievement.json +1 -1
  16. package/registry/action-bar.json +1 -1
  17. package/registry/activity-feed.json +1 -1
  18. package/registry/alert.json +1 -1
  19. package/registry/app-header.json +1 -1
  20. package/registry/app-shell.json +1 -1
  21. package/registry/argument-card.json +1 -1
  22. package/registry/audio-player.json +1 -1
  23. package/registry/auth-layout.json +1 -1
  24. package/registry/avatar.json +1 -1
  25. package/registry/badge.json +1 -1
  26. package/registry/button.json +1 -1
  27. package/registry/captcha.json +1 -1
  28. package/registry/card-grid.json +1 -1
  29. package/registry/card.json +1 -1
  30. package/registry/catalogue-page.json +1 -1
  31. package/registry/changelog-page.json +1 -1
  32. package/registry/chat.json +55 -8
  33. package/registry/checkbox.json +1 -1
  34. package/registry/chip-link.json +1 -1
  35. package/registry/chip-row.json +1 -1
  36. package/registry/chip.json +1 -1
  37. package/registry/choice-grid.json +1 -1
  38. package/registry/code-block.json +1 -1
  39. package/registry/combobox.json +1 -1
  40. package/registry/command.json +1 -1
  41. package/registry/comparison-table.json +1 -1
  42. package/registry/component-page.json +1 -1
  43. package/registry/confirm-button.json +1 -1
  44. package/registry/contact-page.json +1 -1
  45. package/registry/continue-prompt.json +1 -1
  46. package/registry/countdown.json +1 -1
  47. package/registry/cover-hero.json +1 -1
  48. package/registry/cta-panel.json +1 -1
  49. package/registry/dashboard-layout.json +1 -1
  50. package/registry/data-table.json +1 -1
  51. package/registry/date-picker.json +1 -1
  52. package/registry/decision-list.json +1 -1
  53. package/registry/description-list.json +1 -1
  54. package/registry/detail-layout.json +1 -1
  55. package/registry/dialog.json +1 -1
  56. package/registry/docs-article-page.json +1 -1
  57. package/registry/docs-hub-page.json +16 -2
  58. package/registry/docs-layout.json +1 -1
  59. package/registry/docs-nav.json +1 -1
  60. package/registry/document-viewer.json +1 -1
  61. package/registry/dropdown-menu.json +1 -1
  62. package/registry/empty-state.json +1 -1
  63. package/registry/engine-flow.json +280 -0
  64. package/registry/error-page.json +1 -1
  65. package/registry/examples/chat/native/assistant.d.ts +4 -0
  66. package/registry/examples/chat/native/assistant.dark.png +0 -0
  67. package/registry/examples/chat/native/assistant.js +32 -0
  68. package/registry/examples/chat/native/assistant.light.png +0 -0
  69. package/registry/examples/chat/native/assistant.tsx +28 -0
  70. package/registry/examples/engine-flow/compact.d.ts +4 -0
  71. package/registry/examples/engine-flow/compact.js +33 -0
  72. package/registry/examples/engine-flow/compact.tsx +30 -0
  73. package/registry/examples/engine-flow/radial.d.ts +4 -0
  74. package/registry/examples/engine-flow/radial.js +37 -0
  75. package/registry/examples/engine-flow/radial.tsx +32 -0
  76. package/registry/field.json +1 -1
  77. package/registry/file-upload.json +1 -1
  78. package/registry/filter-bar.json +1 -1
  79. package/registry/force-update-screen.json +1 -1
  80. package/registry/form.json +1 -1
  81. package/registry/gallery-page.json +1 -1
  82. package/registry/genui.json +1 -1
  83. package/registry/glow-card.json +1 -1
  84. package/registry/goal-gauge.json +1 -1
  85. package/registry/how-it-works-page.json +1 -1
  86. package/registry/image-cropper.json +1 -1
  87. package/registry/index.json +37 -6
  88. package/registry/iso.json +3 -3
  89. package/registry/kanban.json +1 -1
  90. package/registry/kbd.json +1 -1
  91. package/registry/landing-page.json +1 -1
  92. package/registry/landscape.json +1 -1
  93. package/registry/language-switch.json +1 -1
  94. package/registry/legal-page.json +1 -1
  95. package/registry/link-card.json +1 -1
  96. package/registry/list.json +1 -1
  97. package/registry/loader.json +1 -1
  98. package/registry/longform-page.json +1 -1
  99. package/registry/maintenance-screen.json +1 -1
  100. package/registry/map.json +2 -2
  101. package/registry/markdown.json +1 -1
  102. package/registry/marketing-layout.json +1 -1
  103. package/registry/marquee.json +1 -1
  104. package/registry/mascot-quote.json +1 -1
  105. package/registry/mascot-state.json +1 -1
  106. package/registry/mascots.json +1 -1
  107. package/registry/match-dialog.json +1 -1
  108. package/registry/media-capture.json +1 -1
  109. package/registry/media-gallery.json +1 -1
  110. package/registry/media-grid.json +1 -1
  111. package/registry/meta-row.json +1 -1
  112. package/registry/meter.json +1 -1
  113. package/registry/nav-bar.json +2 -2
  114. package/registry/number-ticker.json +1 -1
  115. package/registry/opening-hours.json +1 -1
  116. package/registry/page-header.json +1 -1
  117. package/registry/page-hero.json +1 -1
  118. package/registry/page.json +1 -1
  119. package/registry/pagination.json +1 -1
  120. package/registry/photo-grid.json +1 -1
  121. package/registry/pointer-light.json +1 -1
  122. package/registry/popover.json +1 -1
  123. package/registry/pricing-page.json +1 -1
  124. package/registry/product-page.json +1 -1
  125. package/registry/profile-card.json +1 -1
  126. package/registry/progress.json +1 -1
  127. package/registry/promise-card.json +1 -1
  128. package/registry/radio-group.json +1 -1
  129. package/registry/rail-nav.json +2 -2
  130. package/registry/recipes.json +1 -1
  131. package/registry/scroll-stage.json +1 -1
  132. package/registry/search-field.json +1 -1
  133. package/registry/section-backdrop.json +1 -1
  134. package/registry/separator.json +1 -1
  135. package/registry/service-card.json +1 -1
  136. package/registry/settings-layout.json +1 -1
  137. package/registry/showcase.json +1 -1
  138. package/registry/site-footer.json +2 -2
  139. package/registry/sites.json +1 -1
  140. package/registry/skeleton.json +1 -1
  141. package/registry/skip-link.json +1 -1
  142. package/registry/slider.json +1 -1
  143. package/registry/slot-picker.json +1 -1
  144. package/registry/slot.json +1 -1
  145. package/registry/spinner.json +1 -1
  146. package/registry/split-view.json +1 -1
  147. package/registry/stage-banner.json +1 -1
  148. package/registry/stat.json +1 -1
  149. package/registry/status-page.json +1 -1
  150. package/registry/step-progress.json +1 -1
  151. package/registry/step-showcase.json +1 -1
  152. package/registry/stepper.json +1 -1
  153. package/registry/sticky-action.json +1 -1
  154. package/registry/swipe-deck.json +1 -1
  155. package/registry/switch.json +1 -1
  156. package/registry/tabs.json +1 -1
  157. package/registry/tag-input.json +1 -1
  158. package/registry/tag-selector.json +1 -1
  159. package/registry/terminal-replay.json +1 -1
  160. package/registry/theme.json +1 -1
  161. package/registry/toast.json +1 -1
  162. package/registry/toc.json +1 -1
  163. package/registry/tooltip.json +1 -1
  164. package/registry/tree-view.json +1 -1
  165. package/registry/update-prompt.json +1 -1
  166. package/registry/verified-badge.json +1 -1
  167. package/registry/vertical-video.json +1 -1
  168. package/registry/video-player.json +1 -1
  169. package/registry/video-trimmer.json +1 -1
  170. package/registry/virtual.json +1 -1
  171. package/registry/waveform.json +1 -1
package/README.md CHANGED
@@ -176,6 +176,7 @@ light or a product's — is a set of tokens.
176
176
  | `@krizaka/ui/achievement` | `Achievement` (`title`, `eyebrow`, `badge`, `replaced`, `locked`), `AchievementList`, `Emblem` (`sword` · `mirror` · `jewel`) | server |
177
177
  | `@krizaka/ui/landscape` | `Landscape` (`tone` own · brand, `flock`, `phoenix`, `animated`, `position`, `fade`): the Krizaka valley at dusk, decorative | client |
178
178
  | `@krizaka/ui/glow-card` | `GlowCard` (`glow` border·spotlight·both, `alive`, `asChild`) | client |
179
+ | `@krizaka/ui/engine-flow` | `EngineFlow` (`label`, `hub`, `nodes` with `flow` in·out·both, `planned`, `status`, `description`, `href`; `layout` radial·top-to-bottom·left-to-right, `compactLayout` under 560 px, `particles`, `conduit` bezier·straight, `hint`, `speed`): an engine and the apps it serves, photons along SVG conduits (motion.css), no animation frame | client |
179
180
  | `@krizaka/ui/marquee` | `Marquee` (`label`, `pauseLabel`, `playLabel`, `speed` px/s, `direction`, `fade`, `defaultPaused`) | client |
180
181
  | `@krizaka/ui/section-backdrop` | `SectionBackdrop` (`as`, `direction` down·up, `dome`, `grid`, `media` blurred for depth of field, `animated`): a page section on the brand's section gradient — see `@krizaka/tokens` BRAND.md, "Visual language" | server |
181
182
  | `@krizaka/ui/mascots` | `Mascot` (`bird` falcon·owl·duck·flamingo·pigeon·lovebird·crane, `mood` welcome·thinking·success·empty·error, `tone` brand·own, `size`, `animated`, `title`), `MASCOT_BIRDS`, `MASCOT_MOODS`: the Krizaka flock, in the host brand's accent — one bird at a moment that matters (onboarding, a long wait, a success, an empty list, an error); native `Mascot` in `/native` | client |
@@ -495,6 +496,7 @@ import { MotionObserver, RotatingWord } from "@krizaka/ui";
495
496
  | `kz-spotlight` | A soft bloom following the pointer across a card |
496
497
  | `kz-lift` | A card rising under the pointer |
497
498
  | `kz-gradient-text`, `kz-marquee` | Drifting gradient text, endless bands |
499
+ | `kz-flow` | EngineFlow's conduits and photons: paused off screen, held while settling, still under reduced motion |
498
500
  | `kz-overlay`, `kz-dialog`, `kz-pop`, `kz-fade` | Backdrops, dialogs (from the bottom on phones), menus, views swapped in place |
499
501
  | `kz-progress` | The indeterminate progress bar's travelling segment |
500
502
  | `kz-backdrop`, `kz-backdrop-drift`, `-dome`, `-grid`, `-media` | The layers of `SectionBackdrop`: the brand's section gradient, the slow light drift (40 s), the dome, the perspective grid, the blurred foreground |
package/dist/blocks.css CHANGED
@@ -708,6 +708,8 @@ a.kz-fact:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); o
708
708
  border-top: 1px solid var(--kz-border-subtle); }
709
709
  .kz-tpl-collection ul a { display: inline-flex; min-height: 28px; align-items: center; padding: 0 10px; border-radius: 999px; font-size: 12.5px; font-weight: 600;
710
710
  text-decoration: none; color: var(--kz-text-primary); background: var(--kz-surface-2); border: 1px solid var(--kz-border-subtle); }
711
+ .kz-tpl-collection[data-links="stack"] ul { display: grid; justify-items: start; }
712
+ .kz-tpl-collection[data-links="stack"] ul a { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
711
713
  .kz-tpl-collection ul a:hover { border-color: var(--kz-accent); }
712
714
  .kz-tpl-collection ul a:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); outline-offset: 2px; }
713
715
  @media (hover: hover) and (prefers-reduced-motion: no-preference) {
@@ -65,8 +65,14 @@ type DocsHubPageProps = Omit<React.HTMLAttributes<HTMLElement>, "title"> & {
65
65
  };
66
66
  /** The app's link component (Next's `Link`) for internal addresses. */
67
67
  linkAs?: LinkComponent;
68
+ /**
69
+ * How a collection's quick links sit: `wrap` (pills in a row that wraps) or `stack` (one pill per line, so the first
70
+ * link of every card starts at the same height).
71
+ * @default "wrap"
72
+ */
73
+ linksLayout?: "wrap" | "stack";
68
74
  };
69
75
  /** A documentation hub: search first, the collections in their brand, quick starts, popular and new pages. */
70
- declare function DocsHubPage({ eyebrow, title, lead, search, searchHints, collectionsTitle, collections, quickstarts, popular, whatsNew, promise, linkAs, ...props }: DocsHubPageProps): React.JSX.Element;
76
+ declare function DocsHubPage({ eyebrow, title, lead, search, searchHints, collectionsTitle, collections, quickstarts, popular, whatsNew, promise, linkAs, linksLayout, ...props }: DocsHubPageProps): React.JSX.Element;
71
77
 
72
78
  export { type DocsCollection, type DocsHubLink, DocsHubPage, type DocsHubPageProps };
@@ -10,14 +10,14 @@ function List({ items, linkAs: Link = "a" }) {
10
10
  item.meta && /* @__PURE__ */ jsx("small", { children: item.meta })
11
11
  ] }) }, `${i}:${item.href}`)) });
12
12
  }
13
- function DocsHubPage({ eyebrow, title, lead, search, searchHints, collectionsTitle, collections, quickstarts, popular, whatsNew, promise, linkAs, ...props }) {
13
+ function DocsHubPage({ eyebrow, title, lead, search, searchHints, collectionsTitle, collections, quickstarts, popular, whatsNew, promise, linkAs, linksLayout = "wrap", ...props }) {
14
14
  const L = linkAs ?? "a";
15
15
  return /* @__PURE__ */ jsxs("div", { ...props, children: [
16
16
  /* @__PURE__ */ jsx(PageHero, { eyebrow, title, lead, align: "center", backdrop: "grid", children: (search || searchHints) && /* @__PURE__ */ jsxs("div", { className: "kz-tpl-hub-search", children: [
17
17
  search,
18
18
  searchHints && /* @__PURE__ */ jsx("div", { className: "kz-tpl-hub-hints", children: searchHints })
19
19
  ] }) }),
20
- /* @__PURE__ */ jsx(Section, { id: "collections", title: collectionsTitle, headingSize: "sm", spacing: "sm", children: /* @__PURE__ */ jsx("ul", { className: "kz-tpl-collections", children: collections.map((c) => /* @__PURE__ */ jsx("li", { className: c.brand ? "brand-" + c.brand : void 0, children: /* @__PURE__ */ jsxs("div", { className: "kz-tpl-collection", children: [
20
+ /* @__PURE__ */ jsx(Section, { id: "collections", title: collectionsTitle, headingSize: "sm", spacing: "sm", children: /* @__PURE__ */ jsx("ul", { className: "kz-tpl-collections", children: collections.map((c) => /* @__PURE__ */ jsx("li", { className: c.brand ? "brand-" + c.brand : void 0, children: /* @__PURE__ */ jsxs("div", { className: "kz-tpl-collection", "data-links": linksLayout, children: [
21
21
  /* @__PURE__ */ jsxs("div", { className: "kz-tpl-collection-head", children: [
22
22
  c.mark && /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: c.mark }),
23
23
  /* @__PURE__ */ jsx(L, { href: c.href, children: c.title })
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/docs-hub-page/docs-hub-page.tsx"],"sourcesContent":["// Server-safe. The entry of a documentation (blocks.css `.kz-tpl-hub-*`, `.kz-tpl-collections`): the search in the\n// middle of the hero, the collections each in its product's brand with its quick links, the quick starts beside the\n// most read pages, what is new from the registries, and the owl's promise. One column on a phone, the search first.\nimport { MascotQuote } from \"@krizaka/ui/mascot-quote\";\nimport { Section } from \"@krizaka/ui/page\";\nimport type { LinkComponent } from \"@krizaka/ui/page-hero\";\nimport { PageHero } from \"@krizaka/ui/page-hero\";\nimport * as React from \"react\";\n\nexport interface DocsCollection {\n /** A stable key. */\n id: string;\n /** The product's brand (`orazaka`…): the panel wears `.brand-<id>`. */\n brand?: string;\n /** The product's mark. */\n mark?: React.ReactNode;\n /** The collection's name: a link to its home. */\n title: string;\n /** Where it starts. */\n href: string;\n /** What it covers, in one sentence. */\n description?: React.ReactNode;\n /** Its most useful pages (three or four). */\n links?: readonly { href: string; label: string }[];\n}\n\nexport interface DocsHubLink {\n /** Where it leads. */\n href: string;\n /** The page's title. */\n title: React.ReactNode;\n /** On the right, in mono: a collection, a version, a date. */\n meta?: React.ReactNode;\n}\n\nexport type DocsHubPageProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** A mono line above the title. */\n eyebrow?: React.ReactNode;\n /** The page's `h1`. */\n title: React.ReactNode;\n /** One sentence under it. */\n lead?: React.ReactNode;\n /** The search, front and centre (a SearchField, a button that opens the command palette). */\n search?: React.ReactNode;\n /** Under the search: what people look for (a few links or keys). */\n searchHints?: React.ReactNode;\n /** The collections' heading. */\n collectionsTitle: React.ReactNode;\n /** The collections, each in its brand. */\n collections: readonly DocsCollection[];\n /** The quick starts: a title and their samples (CodeBlocks, CodeTabs). */\n quickstarts?: { title: React.ReactNode; children: React.ReactNode };\n /** The most read pages. */\n popular?: { title: React.ReactNode; items: readonly DocsHubLink[] };\n /** What is new (releases from the registries, published pages). */\n whatsNew?: { title: React.ReactNode; items: readonly DocsHubLink[] };\n /** The owl's promise at the foot: what this documentation guarantees. */\n promise?: { label: React.ReactNode; text: React.ReactNode };\n /** The app's link component (Next's `Link`) for internal addresses. */\n linkAs?: LinkComponent;\n};\n\nfunction List({ items, linkAs: Link = \"a\" }: { items: readonly DocsHubLink[]; linkAs?: LinkComponent }) {\n const L = Link as React.ElementType;\n return (\n <ul className=\"kz-tpl-links\">\n {items.map((item, i) => (\n <li key={`${i}:${item.href}`}>\n <L href={item.href}>\n <strong>{item.title}</strong>\n {item.meta && <small>{item.meta}</small>}\n </L>\n </li>\n ))}\n </ul>\n );\n}\n\n/** A documentation hub: search first, the collections in their brand, quick starts, popular and new pages. */\nexport function DocsHubPage({ eyebrow, title, lead, search, searchHints, collectionsTitle, collections, quickstarts, popular, whatsNew, promise, linkAs, ...props }: DocsHubPageProps) {\n const L = (linkAs ?? \"a\") as React.ElementType;\n return (\n <div {...props}>\n <PageHero eyebrow={eyebrow} title={title} lead={lead} align=\"center\" backdrop=\"grid\">\n {(search || searchHints) && (\n <div className=\"kz-tpl-hub-search\">\n {search}\n {searchHints && <div className=\"kz-tpl-hub-hints\">{searchHints}</div>}\n </div>\n )}\n </PageHero>\n <Section id=\"collections\" title={collectionsTitle} headingSize=\"sm\" spacing=\"sm\">\n <ul className=\"kz-tpl-collections\">\n {collections.map((c) => (\n <li key={c.id} className={c.brand ? \"brand-\" + c.brand : undefined}>\n <div className=\"kz-tpl-collection\">\n <div className=\"kz-tpl-collection-head\">\n {c.mark && <span aria-hidden>{c.mark}</span>}\n <L href={c.href}>{c.title}</L>\n </div>\n {c.description && <p>{c.description}</p>}\n {c.links && c.links.length > 0 && (\n <ul aria-label={c.title}>\n {c.links.map((l) => (\n <li key={l.href}>\n <L href={l.href}>{l.label}</L>\n </li>\n ))}\n </ul>\n )}\n </div>\n </li>\n ))}\n </ul>\n </Section>\n {(quickstarts || popular) && (\n <Section spacing=\"sm\" divider aria-label={typeof quickstarts?.title === \"string\" ? quickstarts.title : undefined}>\n <div className=\"kz-tpl-two\">\n {quickstarts && (\n <div className=\"kz-tpl-stack\">\n <h2 className=\"kz-heading-title kz-tpl-h2\">{quickstarts.title}</h2>\n {quickstarts.children}\n </div>\n )}\n {popular && (\n <div className=\"kz-tpl-stack\">\n <h2 className=\"kz-heading-title kz-tpl-h2\">{popular.title}</h2>\n <List items={popular.items} linkAs={linkAs} />\n </div>\n )}\n </div>\n </Section>\n )}\n {whatsNew && (\n <Section id=\"whats-new\" title={whatsNew.title} headingSize=\"sm\" spacing=\"sm\" divider>\n <List items={whatsNew.items} linkAs={linkAs} />\n </Section>\n )}\n {promise && (\n <Section spacing=\"sm\" aria-label={typeof promise.label === \"string\" ? promise.label : undefined}>\n <MascotQuote variant=\"callout\" bird=\"owl\" mood=\"welcome\" label={promise.label}>\n {promise.text}\n </MascotQuote>\n </Section>\n )}\n </div>\n );\n}\n"],"mappings":";AAGA,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AAExB,SAAS,gBAAgB;AA8Df,SACE,KADF;AANV,SAAS,KAAK,EAAE,OAAO,QAAQ,OAAO,IAAI,GAA8D;AACtG,QAAM,IAAI;AACV,SACE,oBAAC,QAAG,WAAU,gBACX,gBAAM,IAAI,CAAC,MAAM,MAChB,oBAAC,QACC,+BAAC,KAAE,MAAM,KAAK,MACZ;AAAA,wBAAC,YAAQ,eAAK,OAAM;AAAA,IACnB,KAAK,QAAQ,oBAAC,WAAO,eAAK,MAAK;AAAA,KAClC,KAJO,GAAG,CAAC,IAAI,KAAK,IAAI,EAK1B,CACD,GACH;AAEJ;AAGO,SAAS,YAAY,EAAE,SAAS,OAAO,MAAM,QAAQ,aAAa,kBAAkB,aAAa,aAAa,SAAS,UAAU,SAAS,QAAQ,GAAG,MAAM,GAAqB;AACrL,QAAM,IAAK,UAAU;AACrB,SACE,qBAAC,SAAK,GAAG,OACP;AAAA,wBAAC,YAAS,SAAkB,OAAc,MAAY,OAAM,UAAS,UAAS,QAC1E,qBAAU,gBACV,qBAAC,SAAI,WAAU,qBACZ;AAAA;AAAA,MACA,eAAe,oBAAC,SAAI,WAAU,oBAAoB,uBAAY;AAAA,OACjE,GAEJ;AAAA,IACA,oBAAC,WAAQ,IAAG,eAAc,OAAO,kBAAkB,aAAY,MAAK,SAAQ,MAC1E,8BAAC,QAAG,WAAU,sBACX,sBAAY,IAAI,CAAC,MAChB,oBAAC,QAAc,WAAW,EAAE,QAAQ,WAAW,EAAE,QAAQ,QACvD,+BAAC,SAAI,WAAU,qBACb;AAAA,2BAAC,SAAI,WAAU,0BACZ;AAAA,UAAE,QAAQ,oBAAC,UAAK,eAAW,MAAE,YAAE,MAAK;AAAA,QACrC,oBAAC,KAAE,MAAM,EAAE,MAAO,YAAE,OAAM;AAAA,SAC5B;AAAA,MACC,EAAE,eAAe,oBAAC,OAAG,YAAE,aAAY;AAAA,MACnC,EAAE,SAAS,EAAE,MAAM,SAAS,KAC3B,oBAAC,QAAG,cAAY,EAAE,OACf,YAAE,MAAM,IAAI,CAAC,MACZ,oBAAC,QACC,8BAAC,KAAE,MAAM,EAAE,MAAO,YAAE,OAAM,KADnB,EAAE,IAEX,CACD,GACH;AAAA,OAEJ,KAhBO,EAAE,EAiBX,CACD,GACH,GACF;AAAA,KACE,eAAe,YACf,oBAAC,WAAQ,SAAQ,MAAK,SAAO,MAAC,cAAY,OAAO,aAAa,UAAU,WAAW,YAAY,QAAQ,QACrG,+BAAC,SAAI,WAAU,cACZ;AAAA,qBACC,qBAAC,SAAI,WAAU,gBACb;AAAA,4BAAC,QAAG,WAAU,8BAA8B,sBAAY,OAAM;AAAA,QAC7D,YAAY;AAAA,SACf;AAAA,MAED,WACC,qBAAC,SAAI,WAAU,gBACb;AAAA,4BAAC,QAAG,WAAU,8BAA8B,kBAAQ,OAAM;AAAA,QAC1D,oBAAC,QAAK,OAAO,QAAQ,OAAO,QAAgB;AAAA,SAC9C;AAAA,OAEJ,GACF;AAAA,IAED,YACC,oBAAC,WAAQ,IAAG,aAAY,OAAO,SAAS,OAAO,aAAY,MAAK,SAAQ,MAAK,SAAO,MAClF,8BAAC,QAAK,OAAO,SAAS,OAAO,QAAgB,GAC/C;AAAA,IAED,WACC,oBAAC,WAAQ,SAAQ,MAAK,cAAY,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ,QACpF,8BAAC,eAAY,SAAQ,WAAU,MAAK,OAAM,MAAK,WAAU,OAAO,QAAQ,OACrE,kBAAQ,MACX,GACF;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/docs-hub-page/docs-hub-page.tsx"],"sourcesContent":["// Server-safe. The entry of a documentation (blocks.css `.kz-tpl-hub-*`, `.kz-tpl-collections`): the search in the\n// middle of the hero, the collections each in its product's brand with its quick links, the quick starts beside the\n// most read pages, what is new from the registries, and the owl's promise. One column on a phone, the search first.\nimport { MascotQuote } from \"@krizaka/ui/mascot-quote\";\nimport { Section } from \"@krizaka/ui/page\";\nimport type { LinkComponent } from \"@krizaka/ui/page-hero\";\nimport { PageHero } from \"@krizaka/ui/page-hero\";\nimport * as React from \"react\";\n\nexport interface DocsCollection {\n /** A stable key. */\n id: string;\n /** The product's brand (`orazaka`…): the panel wears `.brand-<id>`. */\n brand?: string;\n /** The product's mark. */\n mark?: React.ReactNode;\n /** The collection's name: a link to its home. */\n title: string;\n /** Where it starts. */\n href: string;\n /** What it covers, in one sentence. */\n description?: React.ReactNode;\n /** Its most useful pages (three or four). */\n links?: readonly { href: string; label: string }[];\n}\n\nexport interface DocsHubLink {\n /** Where it leads. */\n href: string;\n /** The page's title. */\n title: React.ReactNode;\n /** On the right, in mono: a collection, a version, a date. */\n meta?: React.ReactNode;\n}\n\nexport type DocsHubPageProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** A mono line above the title. */\n eyebrow?: React.ReactNode;\n /** The page's `h1`. */\n title: React.ReactNode;\n /** One sentence under it. */\n lead?: React.ReactNode;\n /** The search, front and centre (a SearchField, a button that opens the command palette). */\n search?: React.ReactNode;\n /** Under the search: what people look for (a few links or keys). */\n searchHints?: React.ReactNode;\n /** The collections' heading. */\n collectionsTitle: React.ReactNode;\n /** The collections, each in its brand. */\n collections: readonly DocsCollection[];\n /** The quick starts: a title and their samples (CodeBlocks, CodeTabs). */\n quickstarts?: { title: React.ReactNode; children: React.ReactNode };\n /** The most read pages. */\n popular?: { title: React.ReactNode; items: readonly DocsHubLink[] };\n /** What is new (releases from the registries, published pages). */\n whatsNew?: { title: React.ReactNode; items: readonly DocsHubLink[] };\n /** The owl's promise at the foot: what this documentation guarantees. */\n promise?: { label: React.ReactNode; text: React.ReactNode };\n /** The app's link component (Next's `Link`) for internal addresses. */\n linkAs?: LinkComponent;\n /**\n * How a collection's quick links sit: `wrap` (pills in a row that wraps) or `stack` (one pill per line, so the first\n * link of every card starts at the same height).\n * @default \"wrap\"\n */\n linksLayout?: \"wrap\" | \"stack\";\n};\n\nfunction List({ items, linkAs: Link = \"a\" }: { items: readonly DocsHubLink[]; linkAs?: LinkComponent }) {\n const L = Link as React.ElementType;\n return (\n <ul className=\"kz-tpl-links\">\n {items.map((item, i) => (\n <li key={`${i}:${item.href}`}>\n <L href={item.href}>\n <strong>{item.title}</strong>\n {item.meta && <small>{item.meta}</small>}\n </L>\n </li>\n ))}\n </ul>\n );\n}\n\n/** A documentation hub: search first, the collections in their brand, quick starts, popular and new pages. */\nexport function DocsHubPage({ eyebrow, title, lead, search, searchHints, collectionsTitle, collections, quickstarts, popular, whatsNew, promise, linkAs, linksLayout = \"wrap\", ...props }: DocsHubPageProps) {\n const L = (linkAs ?? \"a\") as React.ElementType;\n return (\n <div {...props}>\n <PageHero eyebrow={eyebrow} title={title} lead={lead} align=\"center\" backdrop=\"grid\">\n {(search || searchHints) && (\n <div className=\"kz-tpl-hub-search\">\n {search}\n {searchHints && <div className=\"kz-tpl-hub-hints\">{searchHints}</div>}\n </div>\n )}\n </PageHero>\n <Section id=\"collections\" title={collectionsTitle} headingSize=\"sm\" spacing=\"sm\">\n <ul className=\"kz-tpl-collections\">\n {collections.map((c) => (\n <li key={c.id} className={c.brand ? \"brand-\" + c.brand : undefined}>\n <div className=\"kz-tpl-collection\" data-links={linksLayout}>\n <div className=\"kz-tpl-collection-head\">\n {c.mark && <span aria-hidden>{c.mark}</span>}\n <L href={c.href}>{c.title}</L>\n </div>\n {c.description && <p>{c.description}</p>}\n {c.links && c.links.length > 0 && (\n <ul aria-label={c.title}>\n {c.links.map((l) => (\n <li key={l.href}>\n <L href={l.href}>{l.label}</L>\n </li>\n ))}\n </ul>\n )}\n </div>\n </li>\n ))}\n </ul>\n </Section>\n {(quickstarts || popular) && (\n <Section spacing=\"sm\" divider aria-label={typeof quickstarts?.title === \"string\" ? quickstarts.title : undefined}>\n <div className=\"kz-tpl-two\">\n {quickstarts && (\n <div className=\"kz-tpl-stack\">\n <h2 className=\"kz-heading-title kz-tpl-h2\">{quickstarts.title}</h2>\n {quickstarts.children}\n </div>\n )}\n {popular && (\n <div className=\"kz-tpl-stack\">\n <h2 className=\"kz-heading-title kz-tpl-h2\">{popular.title}</h2>\n <List items={popular.items} linkAs={linkAs} />\n </div>\n )}\n </div>\n </Section>\n )}\n {whatsNew && (\n <Section id=\"whats-new\" title={whatsNew.title} headingSize=\"sm\" spacing=\"sm\" divider>\n <List items={whatsNew.items} linkAs={linkAs} />\n </Section>\n )}\n {promise && (\n <Section spacing=\"sm\" aria-label={typeof promise.label === \"string\" ? promise.label : undefined}>\n <MascotQuote variant=\"callout\" bird=\"owl\" mood=\"welcome\" label={promise.label}>\n {promise.text}\n </MascotQuote>\n </Section>\n )}\n </div>\n );\n}\n"],"mappings":";AAGA,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AAExB,SAAS,gBAAgB;AAoEf,SACE,KADF;AANV,SAAS,KAAK,EAAE,OAAO,QAAQ,OAAO,IAAI,GAA8D;AACtG,QAAM,IAAI;AACV,SACE,oBAAC,QAAG,WAAU,gBACX,gBAAM,IAAI,CAAC,MAAM,MAChB,oBAAC,QACC,+BAAC,KAAE,MAAM,KAAK,MACZ;AAAA,wBAAC,YAAQ,eAAK,OAAM;AAAA,IACnB,KAAK,QAAQ,oBAAC,WAAO,eAAK,MAAK;AAAA,KAClC,KAJO,GAAG,CAAC,IAAI,KAAK,IAAI,EAK1B,CACD,GACH;AAEJ;AAGO,SAAS,YAAY,EAAE,SAAS,OAAO,MAAM,QAAQ,aAAa,kBAAkB,aAAa,aAAa,SAAS,UAAU,SAAS,QAAQ,cAAc,QAAQ,GAAG,MAAM,GAAqB;AAC3M,QAAM,IAAK,UAAU;AACrB,SACE,qBAAC,SAAK,GAAG,OACP;AAAA,wBAAC,YAAS,SAAkB,OAAc,MAAY,OAAM,UAAS,UAAS,QAC1E,qBAAU,gBACV,qBAAC,SAAI,WAAU,qBACZ;AAAA;AAAA,MACA,eAAe,oBAAC,SAAI,WAAU,oBAAoB,uBAAY;AAAA,OACjE,GAEJ;AAAA,IACA,oBAAC,WAAQ,IAAG,eAAc,OAAO,kBAAkB,aAAY,MAAK,SAAQ,MAC1E,8BAAC,QAAG,WAAU,sBACX,sBAAY,IAAI,CAAC,MAChB,oBAAC,QAAc,WAAW,EAAE,QAAQ,WAAW,EAAE,QAAQ,QACvD,+BAAC,SAAI,WAAU,qBAAoB,cAAY,aAC7C;AAAA,2BAAC,SAAI,WAAU,0BACZ;AAAA,UAAE,QAAQ,oBAAC,UAAK,eAAW,MAAE,YAAE,MAAK;AAAA,QACrC,oBAAC,KAAE,MAAM,EAAE,MAAO,YAAE,OAAM;AAAA,SAC5B;AAAA,MACC,EAAE,eAAe,oBAAC,OAAG,YAAE,aAAY;AAAA,MACnC,EAAE,SAAS,EAAE,MAAM,SAAS,KAC3B,oBAAC,QAAG,cAAY,EAAE,OACf,YAAE,MAAM,IAAI,CAAC,MACZ,oBAAC,QACC,8BAAC,KAAE,MAAM,EAAE,MAAO,YAAE,OAAM,KADnB,EAAE,IAEX,CACD,GACH;AAAA,OAEJ,KAhBO,EAAE,EAiBX,CACD,GACH,GACF;AAAA,KACE,eAAe,YACf,oBAAC,WAAQ,SAAQ,MAAK,SAAO,MAAC,cAAY,OAAO,aAAa,UAAU,WAAW,YAAY,QAAQ,QACrG,+BAAC,SAAI,WAAU,cACZ;AAAA,qBACC,qBAAC,SAAI,WAAU,gBACb;AAAA,4BAAC,QAAG,WAAU,8BAA8B,sBAAY,OAAM;AAAA,QAC7D,YAAY;AAAA,SACf;AAAA,MAED,WACC,qBAAC,SAAI,WAAU,gBACb;AAAA,4BAAC,QAAG,WAAU,8BAA8B,kBAAQ,OAAM;AAAA,QAC1D,oBAAC,QAAK,OAAO,QAAQ,OAAO,QAAgB;AAAA,SAC9C;AAAA,OAEJ,GACF;AAAA,IAED,YACC,oBAAC,WAAQ,IAAG,aAAY,OAAO,SAAS,OAAO,aAAY,MAAK,SAAQ,MAAK,SAAO,MAClF,8BAAC,QAAK,OAAO,SAAS,OAAO,QAAgB,GAC/C;AAAA,IAED,WACC,oBAAC,WAAQ,SAAQ,MAAK,cAAY,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ,QACpF,8BAAC,eAAY,SAAQ,WAAU,MAAK,OAAM,MAAK,WAAU,OAAO,QAAQ,OACrE,kBAAQ,MACX,GACF;AAAA,KAEJ;AAEJ;","names":[]}
@@ -0,0 +1,97 @@
1
+ import * as React from 'react';
2
+
3
+ /** How the nodes are placed around the hub. */
4
+ type FlowLayout = "radial" | "top-to-bottom" | "left-to-right";
5
+ /** The shape of a conduit: an S-shaped cubic Bézier, or a straight line. */
6
+ type FlowConduit = "bezier" | "straight";
7
+ interface FlowPoint {
8
+ x: number;
9
+ y: number;
10
+ }
11
+ interface FlowGeometry {
12
+ /** The box: 100 units wide, `height` units high (the stage keeps this aspect ratio, so nothing is distorted). */
13
+ width: number;
14
+ height: number;
15
+ hub: FlowPoint;
16
+ nodes: FlowPoint[];
17
+ /** One path per node, from the node to the hub. */
18
+ paths: string[];
19
+ /** The length of each path, in units. */
20
+ lengths: number[];
21
+ }
22
+ /** The box height per layout (the width is always 100). */
23
+ declare const FLOW_HEIGHT: Record<FlowLayout, number>;
24
+ /** The positions of `count` nodes around the hub. */
25
+ declare function flowPositions(layout: FlowLayout, count: number): {
26
+ hub: FlowPoint;
27
+ nodes: FlowPoint[];
28
+ };
29
+ /** The whole geometry of a flow: positions, conduits (node → hub) and their lengths. */
30
+ declare function flowGeometry(layout: FlowLayout, count: number, conduit?: FlowConduit): FlowGeometry;
31
+
32
+ interface EngineFlowNode {
33
+ /** A stable id (`orochia`). */
34
+ id: string;
35
+ /** Its name, the button's accessible name ("Orochia"). */
36
+ label: string;
37
+ /** Its mark (a `ProductLogo`), decorative. */
38
+ mark?: React.ReactNode;
39
+ /** One line: what it uses the engine for — shown when the node is pointed at or focused. */
40
+ description?: React.ReactNode;
41
+ /** Where it lives (its site). */
42
+ href?: string;
43
+ /** The link's words ("Visit dev.orochia.com"); defaults to the label. */
44
+ linkLabel?: React.ReactNode;
45
+ /** Opens the link in a new tab. */
46
+ external?: boolean;
47
+ /** What travels: `in` towards the hub, `out` back from it, `both`.
48
+ * @default "in" */
49
+ flow?: "in" | "out" | "both";
50
+ /** Not wired yet: drawn dim, its conduit dashed and still, no photon. */
51
+ planned?: boolean;
52
+ /** A word beside the name in the detail line ("planned"), passed translated. */
53
+ status?: React.ReactNode;
54
+ /** Its brand scope (`brand-orochia`): its conduit, photons and ring take its `--kz-accent`. */
55
+ className?: string;
56
+ }
57
+ interface EngineFlowHub {
58
+ /** The engine's name ("Orazaka"). */
59
+ label: string;
60
+ /** Its mark, decorative. */
61
+ mark?: React.ReactNode;
62
+ /** A line under the name ("the engine"). */
63
+ caption?: React.ReactNode;
64
+ /** Its brand scope; the photons coming back take its accent. */
65
+ className?: string;
66
+ }
67
+ type EngineFlowProps = Omit<React.ComponentProps<"figure">, "children"> & {
68
+ /** What the figure shows ("Orazaka and the products it powers"), passed translated. */
69
+ label: string;
70
+ /** The engine, at the hub. */
71
+ hub: EngineFlowHub;
72
+ /** The apps around it. */
73
+ nodes: readonly EngineFlowNode[];
74
+ /** How the nodes sit around the hub.
75
+ * @default "radial" */
76
+ layout?: FlowLayout;
77
+ /** The layout under a 560 px wide container (a phone); `false` keeps `layout` everywhere.
78
+ * @default "top-to-bottom" */
79
+ compactLayout?: FlowLayout | false;
80
+ /** `photons` travel the conduits; `none` draws them still.
81
+ * @default "photons" */
82
+ particles?: "photons" | "none";
83
+ /** The conduits' shape.
84
+ * @default "bezier" */
85
+ conduit?: FlowConduit;
86
+ /** The detail line before a node is chosen ("Point at a product"). */
87
+ hint?: React.ReactNode;
88
+ /** The node shown in the detail line at first. */
89
+ defaultActive?: string;
90
+ /** Photon speed, in units per second (the stage is 100 units wide).
91
+ * @default 16 */
92
+ speed?: number;
93
+ };
94
+ /** An engine at the hub and the apps it serves, with what travels between them. */
95
+ declare function EngineFlow({ label, hub, nodes, layout, compactLayout, particles, conduit, hint, defaultActive, speed, className, ...props }: EngineFlowProps): React.JSX.Element;
96
+
97
+ export { EngineFlow, type EngineFlowHub, type EngineFlowNode, type EngineFlowProps, FLOW_HEIGHT, type FlowConduit, type FlowGeometry, type FlowLayout, type FlowPoint, flowGeometry, flowPositions };
@@ -0,0 +1,214 @@
1
+ "use client";
2
+ import {
3
+ cn
4
+ } from "./chunk-NSLXEDIS.js";
5
+
6
+ // src/engine-flow/engine-flow.tsx
7
+ import * as React from "react";
8
+
9
+ // src/engine-flow/geometry.ts
10
+ var FLOW_HEIGHT = { radial: 60, "top-to-bottom": 92, "left-to-right": 56 };
11
+ var r2 = (n) => Math.round(n * 100) / 100;
12
+ var pt = (p) => `${r2(p.x)} ${r2(p.y)}`;
13
+ function cubicLength(a, b, c, d) {
14
+ let length = 0;
15
+ let prev = a;
16
+ for (let i = 1; i <= 32; i++) {
17
+ const t = i / 32;
18
+ const u = 1 - t;
19
+ const p = {
20
+ x: u * u * u * a.x + 3 * u * u * t * b.x + 3 * u * t * t * c.x + t * t * t * d.x,
21
+ y: u * u * u * a.y + 3 * u * u * t * b.y + 3 * u * t * t * c.y + t * t * t * d.y
22
+ };
23
+ length += Math.hypot(p.x - prev.x, p.y - prev.y);
24
+ prev = p;
25
+ }
26
+ return length;
27
+ }
28
+ function flowPositions(layout, count) {
29
+ const height = FLOW_HEIGHT[layout];
30
+ const n = Math.max(0, count);
31
+ if (layout === "top-to-bottom") {
32
+ return { hub: { x: 50, y: 72 }, nodes: Array.from({ length: n }, (_, i) => ({ x: (i + 0.5) * 100 / n, y: 13 })) };
33
+ }
34
+ if (layout === "left-to-right") {
35
+ return { hub: { x: 80, y: height / 2 }, nodes: Array.from({ length: n }, (_, i) => ({ x: 12, y: (i + 0.5) * height / n })) };
36
+ }
37
+ const hub = { x: 50, y: height / 2 };
38
+ const start = n % 2 === 0 ? -90 + 180 / Math.max(n, 1) : -90;
39
+ return {
40
+ hub,
41
+ nodes: Array.from({ length: n }, (_, i) => {
42
+ const a = (start + 360 / n * i) * Math.PI / 180;
43
+ return { x: hub.x + 37 * Math.cos(a), y: hub.y + 20 * Math.sin(a) };
44
+ })
45
+ };
46
+ }
47
+ function flowGeometry(layout, count, conduit = "bezier") {
48
+ const { hub, nodes } = flowPositions(layout, count);
49
+ const paths = [];
50
+ const lengths = [];
51
+ for (const p of nodes) {
52
+ const end = layout === "top-to-bottom" ? { x: hub.x + (p.x - hub.x) * 0.18, y: hub.y } : layout === "left-to-right" ? { x: hub.x, y: hub.y + (p.y - hub.y) * 0.18 } : hub;
53
+ if (conduit === "straight") {
54
+ paths.push(`M${pt(p)} L${pt(end)}`);
55
+ lengths.push(Math.hypot(end.x - p.x, end.y - p.y));
56
+ continue;
57
+ }
58
+ let c1;
59
+ let c2;
60
+ if (layout === "top-to-bottom") {
61
+ const mid = (p.y + end.y) / 2;
62
+ c1 = { x: p.x, y: mid };
63
+ c2 = { x: end.x, y: mid };
64
+ } else if (layout === "left-to-right") {
65
+ const mid = (p.x + end.x) / 2;
66
+ c1 = { x: mid, y: p.y };
67
+ c2 = { x: mid, y: end.y };
68
+ } else {
69
+ const dx = end.x - p.x;
70
+ const dy = end.y - p.y;
71
+ const len = Math.hypot(dx, dy) || 1;
72
+ const k = 0.14 * len;
73
+ const nx = -dy / len;
74
+ const ny = dx / len;
75
+ c1 = { x: p.x + dx * 0.35 + nx * k, y: p.y + dy * 0.35 + ny * k };
76
+ c2 = { x: p.x + dx * 0.65 - nx * k, y: p.y + dy * 0.65 - ny * k };
77
+ }
78
+ paths.push(`M${pt(p)} C${pt(c1)} ${pt(c2)} ${pt(end)}`);
79
+ lengths.push(cubicLength(p, c1, c2, end));
80
+ }
81
+ return { width: 100, height: FLOW_HEIGHT[layout], hub, nodes, paths, lengths };
82
+ }
83
+
84
+ // src/engine-flow/engine-flow.tsx
85
+ import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
86
+ function Stage({
87
+ layout,
88
+ variant,
89
+ nodes,
90
+ hub,
91
+ conduit,
92
+ particles,
93
+ speed,
94
+ active,
95
+ onActive,
96
+ detailId
97
+ }) {
98
+ const g = flowGeometry(layout, nodes.length, conduit);
99
+ const pct = (v, of) => `${(v / of * 100).toFixed(3)}%`;
100
+ return /* @__PURE__ */ jsxs("div", { className: "kz-flow-stage", "data-variant": variant, "data-layout": layout, style: { aspectRatio: `${g.width} / ${g.height}` }, children: [
101
+ /* @__PURE__ */ jsx("svg", { viewBox: `0 0 ${g.width} ${g.height}`, "aria-hidden": "true", focusable: "false", children: nodes.map((node, i) => {
102
+ const flow = node.flow ?? "in";
103
+ const length = g.lengths[i] ?? 0;
104
+ const gap = length / Math.min(4, Math.max(2, Math.round(length / 16)));
105
+ const timing = (dir) => ({
106
+ "--kz-flow-gap": gap.toFixed(3),
107
+ animationDuration: `${(gap / speed).toFixed(2)}s`,
108
+ animationDelay: `${(-((i * 0.37 + (dir === "out" ? 0.5 : 0)) % 1) * (gap / speed)).toFixed(2)}s`
109
+ });
110
+ const lanes = node.planned || particles === "none" ? [] : flow === "both" ? ["in", "out"] : [flow];
111
+ return /* @__PURE__ */ jsxs(
112
+ "g",
113
+ {
114
+ className: cn("kz-flow-link", node.className),
115
+ "data-flow": flow,
116
+ "data-planned": node.planned ? "" : void 0,
117
+ "data-active": active === node.id ? "" : void 0,
118
+ children: [
119
+ /* @__PURE__ */ jsx("path", { className: "kz-flow-wire", d: g.paths[i] }),
120
+ lanes.map((dir) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
121
+ /* @__PURE__ */ jsx("path", { className: "kz-flow-photon kz-loop", "data-dir": dir, "data-halo": "", d: g.paths[i], style: timing(dir) }),
122
+ /* @__PURE__ */ jsx("path", { className: "kz-flow-photon kz-loop", "data-dir": dir, d: g.paths[i], style: timing(dir) })
123
+ ] }, dir))
124
+ ]
125
+ },
126
+ node.id
127
+ );
128
+ }) }),
129
+ /* @__PURE__ */ jsxs("div", { className: cn("kz-flow-hub", hub.className), style: { left: pct(g.hub.x, g.width), top: pct(g.hub.y, g.height) }, children: [
130
+ hub.mark ? /* @__PURE__ */ jsx("span", { className: "kz-flow-hub-mark", children: hub.mark }) : null,
131
+ /* @__PURE__ */ jsx("span", { className: "kz-flow-hub-label", children: hub.label }),
132
+ hub.caption ? /* @__PURE__ */ jsx("span", { className: "kz-flow-hub-caption", children: hub.caption }) : null
133
+ ] }),
134
+ /* @__PURE__ */ jsx("ul", { className: "kz-flow-nodes", children: nodes.map((node, i) => {
135
+ const p = g.nodes[i];
136
+ const on = active === node.id;
137
+ return /* @__PURE__ */ jsx("li", { style: { left: pct(p.x, g.width), top: pct(p.y, g.height) }, children: /* @__PURE__ */ jsxs(
138
+ "button",
139
+ {
140
+ type: "button",
141
+ className: cn("kz-flow-node", node.className),
142
+ "data-active": on ? "" : void 0,
143
+ "data-planned": node.planned ? "" : void 0,
144
+ "aria-pressed": on,
145
+ "aria-controls": detailId,
146
+ onPointerEnter: () => onActive(node.id),
147
+ onFocus: () => onActive(node.id),
148
+ onClick: () => onActive(node.id),
149
+ children: [
150
+ node.mark ? /* @__PURE__ */ jsx("span", { className: "kz-flow-node-mark", "aria-hidden": "true", children: node.mark }) : null,
151
+ /* @__PURE__ */ jsx("span", { className: "kz-flow-node-label", children: node.label })
152
+ ]
153
+ }
154
+ ) }, node.id);
155
+ }) })
156
+ ] });
157
+ }
158
+ function EngineFlow({
159
+ label,
160
+ hub,
161
+ nodes,
162
+ layout = "radial",
163
+ compactLayout = "top-to-bottom",
164
+ particles = "photons",
165
+ conduit = "bezier",
166
+ hint,
167
+ defaultActive,
168
+ speed = 16,
169
+ className,
170
+ ...props
171
+ }) {
172
+ const [active, setActive] = React.useState(defaultActive);
173
+ const detailId = `${React.useId()}-detail`;
174
+ const chosen = nodes.find((n) => n.id === active);
175
+ const compact = compactLayout && compactLayout !== layout ? compactLayout : void 0;
176
+ const stage = { nodes, hub, conduit, particles, speed: Math.max(1, speed), active, onActive: setActive, detailId };
177
+ return /* @__PURE__ */ jsxs(
178
+ "figure",
179
+ {
180
+ "aria-label": label,
181
+ className: cn("kz-flow", className),
182
+ "data-motion-scope": "",
183
+ "data-active": chosen ? "" : void 0,
184
+ "data-compact": compact ? "" : void 0,
185
+ ...props,
186
+ children: [
187
+ /* @__PURE__ */ jsx(Stage, { layout, variant: "wide", ...stage }),
188
+ compact ? /* @__PURE__ */ jsx(Stage, { layout: compact, variant: "compact", ...stage }) : null,
189
+ /* @__PURE__ */ jsx("figcaption", { id: detailId, className: cn("kz-flow-detail", chosen?.className), "aria-live": "polite", children: chosen ? /* @__PURE__ */ jsxs(Fragment2, { children: [
190
+ /* @__PURE__ */ jsx("strong", { children: chosen.label }),
191
+ chosen.status ? /* @__PURE__ */ jsx("span", { className: "kz-flow-status", children: chosen.status }) : null,
192
+ chosen.description ? /* @__PURE__ */ jsxs("span", { children: [
193
+ " ",
194
+ chosen.description
195
+ ] }) : null,
196
+ chosen.href ? /* @__PURE__ */ jsxs(Fragment2, { children: [
197
+ " ",
198
+ /* @__PURE__ */ jsxs("a", { href: chosen.href, ...chosen.external ? { target: "_blank", rel: "noopener" } : {}, children: [
199
+ chosen.linkLabel ?? chosen.label,
200
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " \u2192" })
201
+ ] })
202
+ ] }) : null
203
+ ] }) : hint })
204
+ ]
205
+ }
206
+ );
207
+ }
208
+ export {
209
+ EngineFlow,
210
+ FLOW_HEIGHT,
211
+ flowGeometry,
212
+ flowPositions
213
+ };
214
+ //# sourceMappingURL=engine-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engine-flow/engine-flow.tsx","../src/engine-flow/geometry.ts"],"sourcesContent":["// Client (the node a visitor points at or focuses). An engine at the hub and the apps it serves around it: one conduit\n// per app, and photons that travel it — towards the hub (`in`: requests), back (`out`: results) or both — in the app's\n// brand colour going in and the hub's coming back. A planned app is drawn dim, its conduit dashed and still.\n//\n// Motion is the Krizaka signature's (motion.css): CSS animations of `stroke-dashoffset`, no animation frame, no canvas;\n// paused off screen (`data-motion-scope` → MotionObserver's `data-offscreen`), held while the page settles (`kz-loop`),\n// a still frame under reduced motion (the photons rest along their conduit). The stage keeps its aspect ratio and the\n// detail line its height: no layout shift.\n//\n// Accessible: the drawing is hidden from assistive technology; every node is a real button named by its label, and the\n// detail line (polite live region) says the chosen node's description with its link. Pair it with a text version (a\n// list) in the page: the figure is a picture of it.\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { type FlowConduit, flowGeometry, type FlowLayout } from \"./geometry\";\n\nexport interface EngineFlowNode {\n /** A stable id (`orochia`). */\n id: string;\n /** Its name, the button's accessible name (\"Orochia\"). */\n label: string;\n /** Its mark (a `ProductLogo`), decorative. */\n mark?: React.ReactNode;\n /** One line: what it uses the engine for — shown when the node is pointed at or focused. */\n description?: React.ReactNode;\n /** Where it lives (its site). */\n href?: string;\n /** The link's words (\"Visit dev.orochia.com\"); defaults to the label. */\n linkLabel?: React.ReactNode;\n /** Opens the link in a new tab. */\n external?: boolean;\n /** What travels: `in` towards the hub, `out` back from it, `both`.\n * @default \"in\" */\n flow?: \"in\" | \"out\" | \"both\";\n /** Not wired yet: drawn dim, its conduit dashed and still, no photon. */\n planned?: boolean;\n /** A word beside the name in the detail line (\"planned\"), passed translated. */\n status?: React.ReactNode;\n /** Its brand scope (`brand-orochia`): its conduit, photons and ring take its `--kz-accent`. */\n className?: string;\n}\n\nexport interface EngineFlowHub {\n /** The engine's name (\"Orazaka\"). */\n label: string;\n /** Its mark, decorative. */\n mark?: React.ReactNode;\n /** A line under the name (\"the engine\"). */\n caption?: React.ReactNode;\n /** Its brand scope; the photons coming back take its accent. */\n className?: string;\n}\n\nexport type EngineFlowProps = Omit<React.ComponentProps<\"figure\">, \"children\"> & {\n /** What the figure shows (\"Orazaka and the products it powers\"), passed translated. */\n label: string;\n /** The engine, at the hub. */\n hub: EngineFlowHub;\n /** The apps around it. */\n nodes: readonly EngineFlowNode[];\n /** How the nodes sit around the hub.\n * @default \"radial\" */\n layout?: FlowLayout;\n /** The layout under a 560 px wide container (a phone); `false` keeps `layout` everywhere.\n * @default \"top-to-bottom\" */\n compactLayout?: FlowLayout | false;\n /** `photons` travel the conduits; `none` draws them still.\n * @default \"photons\" */\n particles?: \"photons\" | \"none\";\n /** The conduits' shape.\n * @default \"bezier\" */\n conduit?: FlowConduit;\n /** The detail line before a node is chosen (\"Point at a product\"). */\n hint?: React.ReactNode;\n /** The node shown in the detail line at first. */\n defaultActive?: string;\n /** Photon speed, in units per second (the stage is 100 units wide).\n * @default 16 */\n speed?: number;\n};\n\nfunction Stage({\n layout,\n variant,\n nodes,\n hub,\n conduit,\n particles,\n speed,\n active,\n onActive,\n detailId,\n}: {\n layout: FlowLayout;\n variant: \"wide\" | \"compact\";\n nodes: readonly EngineFlowNode[];\n hub: EngineFlowHub;\n conduit: FlowConduit;\n particles: \"photons\" | \"none\";\n speed: number;\n active: string | undefined;\n onActive: (id: string) => void;\n detailId: string;\n}) {\n const g = flowGeometry(layout, nodes.length, conduit);\n const pct = (v: number, of: number) => `${((v / of) * 100).toFixed(3)}%`;\n return (\n <div className=\"kz-flow-stage\" data-variant={variant} data-layout={layout} style={{ aspectRatio: `${g.width} / ${g.height}` }}>\n <svg viewBox={`0 0 ${g.width} ${g.height}`} aria-hidden=\"true\" focusable=\"false\">\n {nodes.map((node, i) => {\n const flow = node.flow ?? \"in\";\n const length = g.lengths[i] ?? 0;\n // Two to four photons per conduit, evenly spaced; the same speed on every conduit.\n const gap = length / Math.min(4, Math.max(2, Math.round(length / 16)));\n const timing = (dir: \"in\" | \"out\") =>\n ({\n \"--kz-flow-gap\": gap.toFixed(3),\n animationDuration: `${(gap / speed).toFixed(2)}s`,\n animationDelay: `${(-((i * 0.37 + (dir === \"out\" ? 0.5 : 0)) % 1) * (gap / speed)).toFixed(2)}s`,\n }) as React.CSSProperties;\n const lanes = node.planned || particles === \"none\" ? [] : flow === \"both\" ? ([\"in\", \"out\"] as const) : [flow];\n return (\n <g\n key={node.id}\n className={cn(\"kz-flow-link\", node.className)}\n data-flow={flow}\n data-planned={node.planned ? \"\" : undefined}\n data-active={active === node.id ? \"\" : undefined}\n >\n <path className=\"kz-flow-wire\" d={g.paths[i]} />\n {lanes.map((dir) => (\n <React.Fragment key={dir}>\n <path className=\"kz-flow-photon kz-loop\" data-dir={dir} data-halo=\"\" d={g.paths[i]} style={timing(dir)} />\n <path className=\"kz-flow-photon kz-loop\" data-dir={dir} d={g.paths[i]} style={timing(dir)} />\n </React.Fragment>\n ))}\n </g>\n );\n })}\n </svg>\n <div className={cn(\"kz-flow-hub\", hub.className)} style={{ left: pct(g.hub.x, g.width), top: pct(g.hub.y, g.height) }}>\n {hub.mark ? <span className=\"kz-flow-hub-mark\">{hub.mark}</span> : null}\n <span className=\"kz-flow-hub-label\">{hub.label}</span>\n {hub.caption ? <span className=\"kz-flow-hub-caption\">{hub.caption}</span> : null}\n </div>\n <ul className=\"kz-flow-nodes\">\n {nodes.map((node, i) => {\n const p = g.nodes[i]!;\n const on = active === node.id;\n return (\n <li key={node.id} style={{ left: pct(p.x, g.width), top: pct(p.y, g.height) }}>\n <button\n type=\"button\"\n className={cn(\"kz-flow-node\", node.className)}\n data-active={on ? \"\" : undefined}\n data-planned={node.planned ? \"\" : undefined}\n aria-pressed={on}\n aria-controls={detailId}\n onPointerEnter={() => onActive(node.id)}\n onFocus={() => onActive(node.id)}\n onClick={() => onActive(node.id)}\n >\n {node.mark ? (\n <span className=\"kz-flow-node-mark\" aria-hidden=\"true\">\n {node.mark}\n </span>\n ) : null}\n <span className=\"kz-flow-node-label\">{node.label}</span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\n/** An engine at the hub and the apps it serves, with what travels between them. */\nexport function EngineFlow({\n label,\n hub,\n nodes,\n layout = \"radial\",\n compactLayout = \"top-to-bottom\",\n particles = \"photons\",\n conduit = \"bezier\",\n hint,\n defaultActive,\n speed = 16,\n className,\n ...props\n}: EngineFlowProps) {\n const [active, setActive] = React.useState<string | undefined>(defaultActive);\n const detailId = `${React.useId()}-detail`;\n const chosen = nodes.find((n) => n.id === active);\n const compact = compactLayout && compactLayout !== layout ? compactLayout : undefined;\n const stage = { nodes, hub, conduit, particles, speed: Math.max(1, speed), active, onActive: setActive, detailId };\n return (\n <figure\n aria-label={label}\n className={cn(\"kz-flow\", className)}\n data-motion-scope=\"\"\n data-active={chosen ? \"\" : undefined}\n data-compact={compact ? \"\" : undefined}\n {...props}\n >\n <Stage layout={layout} variant=\"wide\" {...stage} />\n {compact ? <Stage layout={compact} variant=\"compact\" {...stage} /> : null}\n <figcaption id={detailId} className={cn(\"kz-flow-detail\", chosen?.className)} aria-live=\"polite\">\n {chosen ? (\n <>\n <strong>{chosen.label}</strong>\n {chosen.status ? <span className=\"kz-flow-status\">{chosen.status}</span> : null}\n {chosen.description ? <span> {chosen.description}</span> : null}\n {chosen.href ? (\n <>\n {\" \"}\n <a href={chosen.href} {...(chosen.external ? { target: \"_blank\", rel: \"noopener\" } : {})}>\n {chosen.linkLabel ?? chosen.label}\n <span aria-hidden=\"true\"> →</span>\n </a>\n </>\n ) : null}\n </>\n ) : (\n hint\n )}\n </figcaption>\n </figure>\n );\n}\n","// Server-safe, pure. The geometry of EngineFlow: where the hub and the nodes sit in a box 100 units wide, and the\n// conduit from each node to the hub (an SVG path drawn node → hub, and its length, so photons travel at one speed on\n// every conduit). No DOM: the same numbers on the server and in the browser.\n\n/** How the nodes are placed around the hub. */\nexport type FlowLayout = \"radial\" | \"top-to-bottom\" | \"left-to-right\";\n/** The shape of a conduit: an S-shaped cubic Bézier, or a straight line. */\nexport type FlowConduit = \"bezier\" | \"straight\";\n\nexport interface FlowPoint {\n x: number;\n y: number;\n}\n\nexport interface FlowGeometry {\n /** The box: 100 units wide, `height` units high (the stage keeps this aspect ratio, so nothing is distorted). */\n width: number;\n height: number;\n hub: FlowPoint;\n nodes: FlowPoint[];\n /** One path per node, from the node to the hub. */\n paths: string[];\n /** The length of each path, in units. */\n lengths: number[];\n}\n\n/** The box height per layout (the width is always 100). */\nexport const FLOW_HEIGHT: Record<FlowLayout, number> = { radial: 60, \"top-to-bottom\": 92, \"left-to-right\": 56 };\n\nconst r2 = (n: number) => Math.round(n * 100) / 100;\nconst pt = (p: FlowPoint) => `${r2(p.x)} ${r2(p.y)}`;\n\nfunction cubicLength(a: FlowPoint, b: FlowPoint, c: FlowPoint, d: FlowPoint): number {\n let length = 0;\n let prev = a;\n for (let i = 1; i <= 32; i++) {\n const t = i / 32;\n const u = 1 - t;\n const p = {\n x: u * u * u * a.x + 3 * u * u * t * b.x + 3 * u * t * t * c.x + t * t * t * d.x,\n y: u * u * u * a.y + 3 * u * u * t * b.y + 3 * u * t * t * c.y + t * t * t * d.y,\n };\n length += Math.hypot(p.x - prev.x, p.y - prev.y);\n prev = p;\n }\n return length;\n}\n\n/** The positions of `count` nodes around the hub. */\nexport function flowPositions(layout: FlowLayout, count: number): { hub: FlowPoint; nodes: FlowPoint[] } {\n const height = FLOW_HEIGHT[layout];\n const n = Math.max(0, count);\n if (layout === \"top-to-bottom\") {\n return { hub: { x: 50, y: 72 }, nodes: Array.from({ length: n }, (_, i) => ({ x: ((i + 0.5) * 100) / n, y: 13 })) };\n }\n if (layout === \"left-to-right\") {\n return { hub: { x: 80, y: height / 2 }, nodes: Array.from({ length: n }, (_, i) => ({ x: 12, y: ((i + 0.5) * height) / n })) };\n }\n // Radial: on an ellipse around the centre; an even count puts two nodes level with the hub (more room for labels).\n const hub = { x: 50, y: height / 2 };\n const start = n % 2 === 0 ? -90 + 180 / Math.max(n, 1) : -90;\n return {\n hub,\n nodes: Array.from({ length: n }, (_, i) => {\n const a = ((start + (360 / n) * i) * Math.PI) / 180;\n return { x: hub.x + 37 * Math.cos(a), y: hub.y + 20 * Math.sin(a) };\n }),\n };\n}\n\n/** The whole geometry of a flow: positions, conduits (node → hub) and their lengths. */\nexport function flowGeometry(layout: FlowLayout, count: number, conduit: FlowConduit = \"bezier\"): FlowGeometry {\n const { hub, nodes } = flowPositions(layout, count);\n const paths: string[] = [];\n const lengths: number[] = [];\n for (const p of nodes) {\n // Each conduit lands on its own point of the hub (a fan), so the lines stay apart where they meet.\n const end =\n layout === \"top-to-bottom\"\n ? { x: hub.x + (p.x - hub.x) * 0.18, y: hub.y }\n : layout === \"left-to-right\"\n ? { x: hub.x, y: hub.y + (p.y - hub.y) * 0.18 }\n : hub;\n if (conduit === \"straight\") {\n paths.push(`M${pt(p)} L${pt(end)}`);\n lengths.push(Math.hypot(end.x - p.x, end.y - p.y));\n continue;\n }\n let c1: FlowPoint;\n let c2: FlowPoint;\n if (layout === \"top-to-bottom\") {\n const mid = (p.y + end.y) / 2;\n c1 = { x: p.x, y: mid };\n c2 = { x: end.x, y: mid };\n } else if (layout === \"left-to-right\") {\n const mid = (p.x + end.x) / 2;\n c1 = { x: mid, y: p.y };\n c2 = { x: mid, y: end.y };\n } else {\n // An S: leave the node bending one way, reach the hub bending the other.\n const dx = end.x - p.x;\n const dy = end.y - p.y;\n const len = Math.hypot(dx, dy) || 1;\n const k = 0.14 * len;\n const nx = -dy / len;\n const ny = dx / len;\n c1 = { x: p.x + dx * 0.35 + nx * k, y: p.y + dy * 0.35 + ny * k };\n c2 = { x: p.x + dx * 0.65 - nx * k, y: p.y + dy * 0.65 - ny * k };\n }\n paths.push(`M${pt(p)} C${pt(c1)} ${pt(c2)} ${pt(end)}`);\n lengths.push(cubicLength(p, c1, c2, end));\n }\n return { width: 100, height: FLOW_HEIGHT[layout], hub, nodes, paths, lengths };\n}\n"],"mappings":"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","names":["Fragment"]}
package/dist/motion.css CHANGED
@@ -636,11 +636,131 @@ svg[data-kz-icon="ExternalIcon"] {
636
636
  @keyframes kz-replay-blink { 50% { opacity: 0; } }
637
637
  @keyframes kz-replay-reveal { from { visibility: hidden; } }
638
638
 
639
+ /* ─── EngineFlow: an engine at the hub, the apps around it, photons along the conduits ───
640
+ The stage keeps the geometry's aspect ratio (no layout shift); the conduits are SVG paths in a box 100 units wide,
641
+ their photons zero-length dashes whose offset slides one gap per period — so every conduit moves at one speed.
642
+ `--kz-flow-back` is read on the figure: the photons coming back wear the hub's accent, those going in the node's. */
643
+ .kz-flow {
644
+ --kz-flow-back: var(--kz-accent);
645
+ --kz-flow-w: 0.22;
646
+ --kz-flow-mark: 3.25rem;
647
+ --kz-flow-hub: 5.75rem;
648
+ position: relative;
649
+ margin: 0;
650
+ container-type: inline-size;
651
+ }
652
+ .kz-flow-stage { position: relative; width: 100%; }
653
+ .kz-flow-stage > svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
654
+ .kz-flow[data-compact] > .kz-flow-stage[data-variant="compact"] { display: none; }
655
+ @container (max-width: 559.98px) {
656
+ .kz-flow[data-compact] > .kz-flow-stage[data-variant="wide"] { display: none; }
657
+ .kz-flow[data-compact] > .kz-flow-stage[data-variant="compact"] { display: block; }
658
+ }
659
+ .kz-flow-stage[data-variant="compact"] { --kz-flow-w: 0.5; --kz-flow-mark: 2.5rem; --kz-flow-hub: 4.5rem; }
660
+ .kz-flow-link { transition: opacity 0.3s var(--kz-ease); }
661
+ .kz-flow-wire {
662
+ fill: none;
663
+ stroke: color-mix(in oklab, var(--kz-accent) 55%, var(--kz-border-default, currentColor));
664
+ stroke-width: var(--kz-flow-w);
665
+ stroke-linecap: round;
666
+ opacity: 0.55;
667
+ }
668
+ .kz-flow-link[data-planned] .kz-flow-wire { stroke-dasharray: calc(var(--kz-flow-w) * 3) calc(var(--kz-flow-w) * 5); opacity: 0.4; }
669
+ .kz-flow-photon {
670
+ fill: none;
671
+ stroke: var(--kz-accent);
672
+ stroke-width: calc(var(--kz-flow-w) * 3.6);
673
+ stroke-linecap: round;
674
+ stroke-dasharray: 0.001 var(--kz-flow-gap, 12);
675
+ animation: kz-flow-in 2.4s linear infinite;
676
+ }
677
+ .kz-flow-photon[data-halo] { stroke-width: calc(var(--kz-flow-w) * 9); opacity: 0.22; }
678
+ .kz-flow-photon[data-dir="out"] { stroke: var(--kz-flow-back); animation-name: kz-flow-out; }
679
+ .kz-flow[data-active] .kz-flow-link:not([data-active]) { opacity: 0.25; }
680
+ .kz-flow-link[data-active] .kz-flow-wire { opacity: 0.9; }
681
+ @keyframes kz-flow-in { to { stroke-dashoffset: calc(var(--kz-flow-gap, 12) * -1); } }
682
+ @keyframes kz-flow-out { from { stroke-dashoffset: calc(var(--kz-flow-gap, 12) * -1); } to { stroke-dashoffset: 0; } }
683
+
684
+ .kz-flow-nodes { margin: 0; padding: 0; list-style: none; }
685
+ .kz-flow-nodes > li, .kz-flow-hub { position: absolute; translate: -50% -50%; }
686
+ .kz-flow-node {
687
+ display: flex;
688
+ flex-direction: column;
689
+ align-items: center;
690
+ gap: 0.375rem;
691
+ min-width: 2.75rem;
692
+ min-height: 2.75rem;
693
+ padding: 0.25rem;
694
+ border: 0;
695
+ border-radius: 0.875rem;
696
+ background: none;
697
+ color: var(--kz-text-primary);
698
+ font: inherit;
699
+ cursor: pointer;
700
+ transition: opacity 0.3s var(--kz-ease);
701
+ }
702
+ .kz-flow-node:focus-visible { outline: 2px solid var(--kz-ring); outline-offset: 2px; }
703
+ .kz-flow-node-mark, .kz-flow-hub-mark {
704
+ display: grid;
705
+ place-items: center;
706
+ border-radius: 999px;
707
+ background: var(--kz-surface-1);
708
+ border: 1px solid color-mix(in oklab, var(--kz-accent) 45%, var(--kz-border-default, transparent));
709
+ transition: border-color 0.3s var(--kz-ease), box-shadow 0.3s var(--kz-ease), scale 0.3s var(--kz-ease);
710
+ }
711
+ .kz-flow-node-mark { width: var(--kz-flow-mark); height: var(--kz-flow-mark); }
712
+ .kz-flow-node-mark > svg { width: 62%; height: 62%; }
713
+ .kz-flow-node-label { font-size: 0.8125rem; font-weight: 600; line-height: 1.2; white-space: nowrap; }
714
+ .kz-flow-node:is(:hover, :focus-visible, [data-active]) .kz-flow-node-mark {
715
+ border-color: var(--kz-accent);
716
+ box-shadow: 0 0 0 4px var(--kz-accent-soft), 0 0 24px -6px var(--kz-accent);
717
+ scale: 1.06;
718
+ }
719
+ .kz-flow[data-active] .kz-flow-node:not([data-active]) { opacity: 0.6; }
720
+ .kz-flow-node[data-planned] .kz-flow-node-mark { border-style: dashed; }
721
+ .kz-flow-node[data-planned] .kz-flow-node-mark > * { opacity: 0.5; }
722
+ .kz-flow-node[data-planned] .kz-flow-node-label { color: var(--kz-text-secondary); }
723
+ /* On a phone the row of marks carries no names: the detail line says the chosen one. */
724
+ .kz-flow-stage[data-layout="top-to-bottom"] .kz-flow-node-label {
725
+ position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
726
+ }
727
+ .kz-flow-hub { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; text-align: center; pointer-events: none; }
728
+ .kz-flow-hub-mark {
729
+ width: var(--kz-flow-hub);
730
+ height: var(--kz-flow-hub);
731
+ /* Opaque: the conduits end under the hub. */
732
+ background: radial-gradient(circle, var(--kz-accent-soft), transparent 72%), var(--kz-surface-1);
733
+ border-color: var(--kz-accent);
734
+ box-shadow: 0 0 40px -8px var(--kz-accent);
735
+ }
736
+ .kz-flow-hub-mark > svg { width: 64%; height: 64%; }
737
+ .kz-flow-hub-label { font-weight: 700; font-size: 0.9375rem; color: var(--kz-text-primary); }
738
+ .kz-flow-hub-caption { font-size: 0.75rem; color: var(--kz-text-secondary); }
739
+ .kz-flow-detail {
740
+ min-height: 3lh;
741
+ max-width: 40rem;
742
+ margin: 1rem auto 0;
743
+ text-align: center;
744
+ font-size: 0.9375rem;
745
+ line-height: 1.5;
746
+ color: var(--kz-text-secondary);
747
+ }
748
+ .kz-flow-detail strong { color: var(--kz-text-primary); }
749
+ .kz-flow-status {
750
+ margin-left: 0.5rem;
751
+ padding: 0.0625rem 0.5rem;
752
+ border-radius: 999px;
753
+ border: 1px solid var(--kz-border-default, currentColor);
754
+ font-size: 0.75rem;
755
+ }
756
+ .kz-flow-detail a { color: var(--kz-accent-text, var(--kz-accent)); font-weight: 600; text-underline-offset: 3px; }
757
+
639
758
  @media (prefers-reduced-motion: reduce) {
640
759
  .kz-page, .kz-word, .kz-gradient-text, .kz-marquee, .kz-backdrop-drift, .kz-overlay, .kz-dialog, .kz-pop, .kz-fade, .kz-progress,
641
760
  .kz-skeleton, .kz-skeleton-shape *, .kz-toast::after, .kz-glow::before,
642
761
  .kz-rotating > *, .kz-loop, .kz-iso-rise, .kz-count-roll, .kz-loader-trace, .kz-loader-node, .kz-loader-mark,
643
- [data-kz-status], .kz-replay-caret, .kz-month, .kz-map-user::after { animation: none !important; }
762
+ [data-kz-status], .kz-replay-caret, .kz-month, .kz-map-user::after, .kz-flow-photon { animation: none !important; }
763
+ .kz-flow-node-mark, .kz-flow-node, .kz-flow-link { transition: none; }
644
764
  /* At rest, the trace stands still where a reader expects it: a third of the hexagon from the top. */
645
765
  .kz-loader-trace:not([stroke-dasharray]) { stroke-dashoffset: -8; }
646
766
  [data-reveal] { opacity: 1; transform: none; filter: none; transition: none; }