@conduction/docusaurus-preset 0.1.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 (163) hide show
  1. package/MISSING_COMPONENTS.md +109 -0
  2. package/README.md +171 -0
  3. package/package.json +59 -0
  4. package/src/components/AgentTrace/AgentTrace.jsx +128 -0
  5. package/src/components/AgentTrace/AgentTrace.module.css +115 -0
  6. package/src/components/AppMock/AppMock.jsx +86 -0
  7. package/src/components/AppMock/AppMock.module.css +629 -0
  8. package/src/components/AppMock/variants/DeciDeskMock.jsx +71 -0
  9. package/src/components/AppMock/variants/DocuDeskMock.jsx +69 -0
  10. package/src/components/AppMock/variants/LarpingAppMock.jsx +59 -0
  11. package/src/components/AppMock/variants/MyDashBiMock.jsx +135 -0
  12. package/src/components/AppMock/variants/MyDashMock.jsx +96 -0
  13. package/src/components/AppMock/variants/MyDashTilesMock.jsx +103 -0
  14. package/src/components/AppMock/variants/MyDashWidgetsMock.jsx +123 -0
  15. package/src/components/AppMock/variants/NLDesignMock.jsx +70 -0
  16. package/src/components/AppMock/variants/OpenCatalogiMock.jsx +61 -0
  17. package/src/components/AppMock/variants/OpenConnectorMock.jsx +83 -0
  18. package/src/components/AppMock/variants/OpenRegisterMock.jsx +100 -0
  19. package/src/components/AppMock/variants/OpenWooMock.jsx +61 -0
  20. package/src/components/AppMock/variants/PipelinQMock.jsx +88 -0
  21. package/src/components/AppMock/variants/ProcestMock.jsx +87 -0
  22. package/src/components/AppMock/variants/SoftwareCatalogMock.jsx +71 -0
  23. package/src/components/AppMock/variants/ZaakAfhandelAppMock.jsx +71 -0
  24. package/src/components/AppsGrid/AppsGrid.jsx +84 -0
  25. package/src/components/AppsGrid/AppsGrid.module.css +46 -0
  26. package/src/components/AppsPreview/AppsPreview.jsx +85 -0
  27. package/src/components/AppsPreview/AppsPreview.module.css +128 -0
  28. package/src/components/Clients/Clients.jsx +205 -0
  29. package/src/components/Clients/Clients.module.css +166 -0
  30. package/src/components/ComposeBlock/ComposeBlock.jsx +70 -0
  31. package/src/components/ComposeBlock/ComposeBlock.module.css +74 -0
  32. package/src/components/ConductionBg/ConductionBg.jsx +150 -0
  33. package/src/components/ConductionBg/ConductionBg.module.css +41 -0
  34. package/src/components/ContentCard/ContentCard.jsx +126 -0
  35. package/src/components/ContentCard/ContentCard.module.css +84 -0
  36. package/src/components/ContentDetailHero/ContentDetailHero.jsx +136 -0
  37. package/src/components/ContentDetailHero/ContentDetailHero.module.css +96 -0
  38. package/src/components/ContentTypeFilter/ContentTypeFilter.jsx +103 -0
  39. package/src/components/ContentTypeFilter/ContentTypeFilter.module.css +60 -0
  40. package/src/components/ContentTypeFilter/contentTypes.js +58 -0
  41. package/src/components/CookieCli/CookieCli.jsx +223 -0
  42. package/src/components/CookieCli/CookieCli.module.css +166 -0
  43. package/src/components/CtaBanner/CtaBanner.jsx +61 -0
  44. package/src/components/CtaBanner/CtaBanner.module.css +65 -0
  45. package/src/components/DetailHero/DetailHero.jsx +143 -0
  46. package/src/components/DetailHero/DetailHero.module.css +154 -0
  47. package/src/components/Diagrams/Diagrams.jsx +148 -0
  48. package/src/components/EmployeeCard/EmployeeCard.jsx +127 -0
  49. package/src/components/EmployeeCard/EmployeeCard.module.css +144 -0
  50. package/src/components/ExternalAppShelf/ExternalAppShelf.jsx +61 -0
  51. package/src/components/ExternalAppShelf/ExternalAppShelf.module.css +90 -0
  52. package/src/components/FAQ/FAQ.jsx +42 -0
  53. package/src/components/FAQ/FAQ.module.css +74 -0
  54. package/src/components/FacetedFilters/FacetedFilters.jsx +125 -0
  55. package/src/components/FacetedFilters/FacetedFilters.module.css +133 -0
  56. package/src/components/FeatureGrid/FeatureGrid.jsx +94 -0
  57. package/src/components/FeatureGrid/FeatureGrid.module.css +114 -0
  58. package/src/components/FeatureList/FeatureList.jsx +54 -0
  59. package/src/components/FeatureList/FeatureList.module.css +52 -0
  60. package/src/components/FeaturedCard/FeaturedCard.jsx +101 -0
  61. package/src/components/FeaturedCard/FeaturedCard.module.css +98 -0
  62. package/src/components/GameModal/GameModal.jsx +197 -0
  63. package/src/components/GameModal/GameModal.module.css +184 -0
  64. package/src/components/Hero/Hero.jsx +101 -0
  65. package/src/components/Hero/Hero.module.css +95 -0
  66. package/src/components/HexBackground/HexBackground.jsx +56 -0
  67. package/src/components/HexBackground/HexBackground.module.css +73 -0
  68. package/src/components/HexNetwork/HexNetwork.jsx +141 -0
  69. package/src/components/HexNetwork/HexNetwork.module.css +187 -0
  70. package/src/components/HexRain/HexRain.jsx +81 -0
  71. package/src/components/HowSteps/HowSteps.jsx +57 -0
  72. package/src/components/HowSteps/HowSteps.module.css +52 -0
  73. package/src/components/ManagedCommonGround/ManagedCommonGround.jsx +78 -0
  74. package/src/components/ManagedCommonGround/ManagedCommonGround.module.css +16 -0
  75. package/src/components/NewsletterCta/NewsletterCta.jsx +83 -0
  76. package/src/components/NewsletterCta/NewsletterCta.module.css +103 -0
  77. package/src/components/PairCard/PairCard.jsx +58 -0
  78. package/src/components/PairCard/PairCard.module.css +54 -0
  79. package/src/components/PartnerCard/PartnerCard.jsx +130 -0
  80. package/src/components/PartnerCard/PartnerCard.module.css +198 -0
  81. package/src/components/PartnerDirectory/PartnerDirectory.jsx +122 -0
  82. package/src/components/PartnerDirectory/PartnerDirectory.module.css +25 -0
  83. package/src/components/PartnerSidecard/PartnerSidecard.jsx +116 -0
  84. package/src/components/PartnerSidecard/PartnerSidecard.module.css +185 -0
  85. package/src/components/Pipeline/Pipeline.jsx +198 -0
  86. package/src/components/Pipeline/Pipeline.module.css +206 -0
  87. package/src/components/PlatformDiagram/PlatformDiagram.jsx +110 -0
  88. package/src/components/PlatformOverview/PlatformOverview.jsx +68 -0
  89. package/src/components/PlatformOverview/PlatformOverview.module.css +71 -0
  90. package/src/components/ReferenceCard/ReferenceCard.jsx +44 -0
  91. package/src/components/ReferenceCard/ReferenceCard.module.css +57 -0
  92. package/src/components/RelatedPosts/RelatedPosts.jsx +58 -0
  93. package/src/components/RelatedPosts/RelatedPosts.module.css +51 -0
  94. package/src/components/RotatingCards/RotatingCards.jsx +98 -0
  95. package/src/components/RotatingCards/RotatingCards.module.css +153 -0
  96. package/src/components/Showcase/Showcase.jsx +129 -0
  97. package/src/components/Showcase/Showcase.module.css +168 -0
  98. package/src/components/SolutionCard/SolutionCard.jsx +83 -0
  99. package/src/components/SolutionCard/SolutionCard.module.css +99 -0
  100. package/src/components/StatsStrip/StatsStrip.jsx +38 -0
  101. package/src/components/StatsStrip/StatsStrip.module.css +53 -0
  102. package/src/components/WidgetShelf/WidgetShelf.jsx +67 -0
  103. package/src/components/WidgetShelf/WidgetShelf.module.css +73 -0
  104. package/src/components/index.js +96 -0
  105. package/src/components/primitives/AuthorByline.jsx +85 -0
  106. package/src/components/primitives/AuthorByline.module.css +57 -0
  107. package/src/components/primitives/BrandCitation.jsx +71 -0
  108. package/src/components/primitives/Button.jsx +46 -0
  109. package/src/components/primitives/Button.module.css +88 -0
  110. package/src/components/primitives/Card.jsx +42 -0
  111. package/src/components/primitives/Card.module.css +42 -0
  112. package/src/components/primitives/Eyebrow.jsx +37 -0
  113. package/src/components/primitives/Eyebrow.module.css +19 -0
  114. package/src/components/primitives/HexBullet.jsx +37 -0
  115. package/src/components/primitives/HexBullet.module.css +16 -0
  116. package/src/components/primitives/HexThumbnail.jsx +70 -0
  117. package/src/components/primitives/HexThumbnail.module.css +45 -0
  118. package/src/components/primitives/Pill.jsx +42 -0
  119. package/src/components/primitives/Pill.module.css +30 -0
  120. package/src/components/primitives/Section.jsx +51 -0
  121. package/src/components/primitives/Section.module.css +31 -0
  122. package/src/components/primitives/SectionHead.jsx +36 -0
  123. package/src/components/primitives/SectionHead.module.css +43 -0
  124. package/src/components/primitives/index.js +22 -0
  125. package/src/css/brand.css +158 -0
  126. package/src/css/tokens.css +12 -0
  127. package/src/data/app-downloads.js +42 -0
  128. package/src/diagrams/README.md +74 -0
  129. package/src/diagrams/cn-domain-tree.js +105 -0
  130. package/src/diagrams/cn-hex-prism.js +163 -0
  131. package/src/diagrams/cn-hex.js +181 -0
  132. package/src/diagrams/cn-honeycomb-bg.js +135 -0
  133. package/src/diagrams/cn-pipeline.js +150 -0
  134. package/src/diagrams/cn-platform.js +156 -0
  135. package/src/diagrams/cn-side-box.js +104 -0
  136. package/src/diagrams/index.js +28 -0
  137. package/src/index.js +183 -0
  138. package/src/theme/Footer/index.jsx +516 -0
  139. package/src/theme/MDXPage/index.jsx +134 -0
  140. package/src/theme/Navbar/index.jsx +120 -0
  141. package/src/theme/Navbar/styles.module.css +114 -0
  142. package/src/theme/brand.jsx +63 -0
  143. package/src/theme.js +45 -0
  144. package/src/utils/lazyScript.js +37 -0
  145. package/static/img/favicon.svg +14 -0
  146. package/static/img/honeycomb-scatter.svg +23 -0
  147. package/static/img/honeycomb-watermark.svg +108 -0
  148. package/static/img/logo-dark.svg +11 -0
  149. package/static/img/logo.svg +14 -0
  150. package/static/img/nextcloud-logo.svg +5 -0
  151. package/static/lib/canal-footer.css +418 -0
  152. package/static/lib/canal-footer.js +499 -0
  153. package/static/lib/clients-flow.js +317 -0
  154. package/static/lib/conduction-bg.css +50 -0
  155. package/static/lib/conduction-bg.js +122 -0
  156. package/static/lib/hex-rain.css +128 -0
  157. package/static/lib/hex-rain.js +284 -0
  158. package/static/lib/kade-cyclist.css +264 -0
  159. package/static/lib/kade-cyclist.js +420 -0
  160. package/static/lib/logo-memory.css +219 -0
  161. package/static/lib/logo-memory.js +540 -0
  162. package/static/lib/platform-diagram.css +458 -0
  163. package/static/lib/platform-diagram.js +414 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ZaakAfhandelApp abstract — citizen + case-worker portal.
3
+ *
4
+ * Inferred from the app role (citizen-facing case portal, ZGW APIs,
5
+ * archive interfaces): centre shows the active case-worker queue
6
+ * (werkvoorraad widget) with stage pips, plus a citizen-side timeline
7
+ * of recent submissions. Left nav for queue / archive / public portal /
8
+ * audit / settings.
9
+ */
10
+
11
+ import React from 'react';
12
+ import styles from '../AppMock.module.css';
13
+
14
+ export default function ZaakAfhandelAppMock() {
15
+ return (
16
+ <>
17
+ <div className={styles.topbar}>
18
+ <div className={styles.logo}></div>
19
+ {Array.from({length: 14}).map((_, i) => <div key={i} className={styles.icon}></div>)}
20
+ <div className={styles.spacer}></div>
21
+ <div className={styles.bell}></div>
22
+ <div className={styles.avatar}></div>
23
+ </div>
24
+ <div className={[styles.body, styles.procest].filter(Boolean).join(' ')}>
25
+ <div className={styles.nav}>
26
+ <div className={styles.navHead}><div className={styles.h}></div><div className={styles.l}></div></div>
27
+ {[true, false, false, false, false].map((active, i) => (
28
+ <div key={i} className={[styles.item, active && styles.active].filter(Boolean).join(' ')}>
29
+ <div className={styles.ico}></div>
30
+ <div className={styles.l}></div>
31
+ </div>
32
+ ))}
33
+ </div>
34
+ <div className={styles.col}>
35
+ <div className={styles.head}>
36
+ <div className={styles.row + ' ' + styles.head} style={{width: '35%'}}></div>
37
+ <div className={styles.actions}>
38
+ <div className={styles.btn + ' ' + styles.ghost}></div>
39
+ <div className={styles.btn}></div>
40
+ </div>
41
+ </div>
42
+ {/* Case-worker werkvoorraad */}
43
+ <div className={[styles.w, styles['w-werkvoorraad']].join(' ')}>
44
+ <div className={styles.wHead}>
45
+ <div className={styles.h}></div><div className={styles.t}></div>
46
+ </div>
47
+ <div className={styles.list}>
48
+ <div className={[styles.item, styles.now].join(' ')}><div className={styles.stage}></div><div className={styles.l1}></div><div className={styles.av}></div></div>
49
+ <div className={styles.item}><div className={styles.stage}></div><div className={styles.l1}></div><div className={[styles.av, styles.b].join(' ')}></div></div>
50
+ <div className={[styles.item, styles.late].join(' ')}><div className={styles.stage}></div><div className={styles.l1}></div><div className={[styles.av, styles.c].join(' ')}></div></div>
51
+ <div className={styles.item}><div className={styles.stage}></div><div className={styles.l1}></div><div className={styles.av}></div></div>
52
+ <div className={styles.item}><div className={styles.stage}></div><div className={styles.l1}></div><div className={[styles.av, styles.b].join(' ')}></div></div>
53
+ </div>
54
+ </div>
55
+ {/* Citizen-side recent submissions list */}
56
+ <div className={[styles.w, styles['w-rss']].join(' ')}>
57
+ <div className={styles.wHead}>
58
+ <div className={styles.h}></div><div className={styles.t}></div>
59
+ </div>
60
+ <div className={styles.list}>
61
+ <div className={styles.item}><div className={styles.src}></div><div className={styles.title}></div><div className={styles.when}></div></div>
62
+ <div className={styles.item}><div className={styles.src}></div><div className={styles.title}></div><div className={styles.when}></div></div>
63
+ <div className={styles.item}><div className={styles.src}></div><div className={styles.title}></div><div className={styles.when}></div></div>
64
+ <div className={styles.item}><div className={styles.src}></div><div className={styles.title}></div><div className={styles.when}></div></div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </>
70
+ );
71
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * <AppsGrid />
3
+ *
4
+ * Filterable apps grid: a row of category chips above a 3-up grid of
5
+ * <AppCard/> tiles. Mirrors preview/components/apps-grid.html. Each
6
+ * app declares one or more categories; clicking a chip narrows the
7
+ * grid to apps in that category. The first chip is always "All".
8
+ *
9
+ * For non-interactive 3-up rows (e.g. "Most installed" on landing.html),
10
+ * use <AppsPreview/> instead. AppsGrid is the catalogue surface.
11
+ *
12
+ * Usage in MDX:
13
+ *
14
+ * <AppsGrid
15
+ * categories={['All', 'Data', 'Processes', 'Connectors', 'Documents', 'AI']}
16
+ * apps={[
17
+ * {
18
+ * name: 'OpenRegister',
19
+ * tagline: 'Schemas, registers, structured data objects.',
20
+ * status: 'STABLE', version: 'v3.1',
21
+ * href: '/apps/openregister',
22
+ * icon: <svg>...</svg>,
23
+ * categories: ['Data'],
24
+ * },
25
+ * // ...
26
+ * ]}
27
+ * />
28
+ */
29
+
30
+ import React, {useState, useMemo} from 'react';
31
+ import {AppCard} from '../AppsPreview/AppsPreview';
32
+ import styles from './AppsGrid.module.css';
33
+
34
+ const ALL_KEY = 'All';
35
+
36
+ export default function AppsGrid({categories, apps = [], className}) {
37
+ const cats = categories && categories.length > 0
38
+ ? categories
39
+ : [ALL_KEY, ...uniqueCategories(apps)];
40
+
41
+ const [active, setActive] = useState(cats[0] || ALL_KEY);
42
+
43
+ const visible = useMemo(() => {
44
+ if (!active || active === ALL_KEY) return apps;
45
+ return apps.filter(a => Array.isArray(a.categories) && a.categories.includes(active));
46
+ }, [apps, active]);
47
+
48
+ return (
49
+ <div className={[styles.section, className].filter(Boolean).join(' ')}>
50
+ <div className={styles.chips} role="tablist" aria-label="Filter apps by category">
51
+ {cats.map((c, i) => (
52
+ <button
53
+ key={i}
54
+ type="button"
55
+ role="tab"
56
+ aria-selected={c === active}
57
+ className={[styles.chip, c === active ? styles.chipActive : null].filter(Boolean).join(' ')}
58
+ onClick={() => setActive(c)}
59
+ >
60
+ {c}
61
+ </button>
62
+ ))}
63
+ </div>
64
+
65
+ <div className={styles.grid}>
66
+ {visible.map((app, i) => <AppCard key={i} app={app} />)}
67
+ </div>
68
+
69
+ {visible.length === 0 && (
70
+ <p className={styles.empty}>No apps match this filter.</p>
71
+ )}
72
+ </div>
73
+ );
74
+ }
75
+
76
+ function uniqueCategories(apps) {
77
+ const seen = new Set();
78
+ for (const a of apps) {
79
+ if (Array.isArray(a.categories)) {
80
+ for (const c of a.categories) seen.add(c);
81
+ }
82
+ }
83
+ return Array.from(seen);
84
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * <AppsGrid /> styles. Mirrors apps-grid.html chip-row + 3-up grid.
3
+ */
4
+
5
+ .section { font-family: var(--conduction-typography-font-family-body); }
6
+
7
+ .chips {
8
+ display: flex;
9
+ gap: 8px;
10
+ margin-bottom: 32px;
11
+ flex-wrap: wrap;
12
+ }
13
+
14
+ .chip {
15
+ padding: 7px 16px;
16
+ border-radius: var(--radius-pill);
17
+ background: var(--c-cobalt-50);
18
+ color: var(--c-cobalt-700);
19
+ font-size: 14px;
20
+ font-weight: 500;
21
+ border: 1px solid transparent;
22
+ cursor: pointer;
23
+ font-family: inherit;
24
+ transition: background 140ms ease, color 140ms ease;
25
+ }
26
+ .chip:hover { background: var(--c-cobalt-100); }
27
+ .chipActive {
28
+ background: var(--c-blue-cobalt);
29
+ color: white;
30
+ }
31
+ .chipActive:hover { background: var(--c-cobalt-700); }
32
+
33
+ .grid {
34
+ display: grid;
35
+ grid-template-columns: repeat(3, 1fr);
36
+ gap: 20px;
37
+ }
38
+ @media (max-width: 900px) { .grid { grid-template-columns: repeat(2, 1fr); } }
39
+ @media (max-width: 600px) { .grid { grid-template-columns: 1fr; } }
40
+
41
+ .empty {
42
+ margin: 32px 0;
43
+ font-size: 14px;
44
+ color: var(--c-cobalt-400);
45
+ text-align: center;
46
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * <AppsPreview />
3
+ *
4
+ * Featured apps section: 3-up app cards + "browse all" link.
5
+ * Section-head pattern (eyebrow + h2 + lede) above the cards.
6
+ *
7
+ * Mirrors the .apps-preview section in preview/pages/landing.html,
8
+ * with .app-card cards from preview/components/apps-grid.css.
9
+ *
10
+ * Composes from primitives:
11
+ * <SectionHead/> for the eyebrow + title + lede pattern
12
+ * <HexBullet/> for the status dot inside each <AppCard/> meta
13
+ *
14
+ * Usage in MDX:
15
+ *
16
+ * <AppsPreview
17
+ * eyebrow="Most installed"
18
+ * title="Three apps that ship the most outcomes."
19
+ * lede={<>Start with the apps that solve a concrete problem ...</>}
20
+ * apps={[
21
+ * {
22
+ * name: 'OpenCatalogi',
23
+ * tagline: 'Public software catalog. Every app, dataset...',
24
+ * status: 'STABLE',
25
+ * version: 'v2.4 · NL · EN',
26
+ * href: '/apps/opencatalogi',
27
+ * icon: <svg>...</svg>,
28
+ * },
29
+ * ...
30
+ * ]}
31
+ * seeAll={{label: 'Browse all twelve apps', href: '/apps'}}
32
+ * />
33
+ */
34
+
35
+ import React from 'react';
36
+ import HexBullet from '../primitives/HexBullet';
37
+ import SectionHead from '../primitives/SectionHead';
38
+ import styles from './AppsPreview.module.css';
39
+
40
+ function AppCard({app}) {
41
+ const statusColor = (app.status || '').toLowerCase() === 'beta'
42
+ ? 'var(--c-orange-knvb)'
43
+ : 'var(--c-mint-500)';
44
+
45
+ return (
46
+ <a href={app.href || '#'} className={styles.card}>
47
+ <div className={styles.iconWrap}>{app.icon}</div>
48
+ <div className={styles.name}>{app.name}</div>
49
+ <div className={styles.tagline}>{app.tagline}</div>
50
+ <div className={styles.meta}>
51
+ {app.status && (
52
+ <span className={styles.badge}>
53
+ <HexBullet size="sm" color={statusColor} />
54
+ {app.status}
55
+ </span>
56
+ )}
57
+ {app.version && <span className={styles.ver}>{app.version}</span>}
58
+ </div>
59
+ </a>
60
+ );
61
+ }
62
+
63
+ export default function AppsPreview({eyebrow, title, lede, apps = [], seeAll}) {
64
+ return (
65
+ <section className={styles.section}>
66
+ {(eyebrow || title || lede) && (
67
+ <SectionHead eyebrow={eyebrow} title={title} lede={lede} />
68
+ )}
69
+
70
+ <div className={styles.row}>
71
+ {apps.map((app, i) => (
72
+ <AppCard key={i} app={app} />
73
+ ))}
74
+ </div>
75
+
76
+ {seeAll && (
77
+ <a href={seeAll.href || '#'} className={styles.seeAll}>
78
+ {seeAll.label} →
79
+ </a>
80
+ )}
81
+ </section>
82
+ );
83
+ }
84
+
85
+ export {AppCard};
@@ -0,0 +1,128 @@
1
+ /**
2
+ * <AppsPreview /> styles. Mirrors the .apps-preview section from
3
+ * preview/pages/landing.html plus .app-card from
4
+ * preview/components/apps-grid.css (CSS Module copy so it scopes).
5
+ *
6
+ * SectionHead + HexBullet primitives carry their own styling now;
7
+ * this module only owns the section frame, the card grid, and the
8
+ * .app-card rules.
9
+ */
10
+
11
+ .section {
12
+ max-width: 1280px;
13
+ margin: 0 auto;
14
+ padding: 96px 64px;
15
+ font-family: var(--conduction-typography-font-family-body);
16
+ background: white;
17
+ }
18
+ @media (max-width: 900px) {
19
+ .section { padding: 64px 24px; }
20
+ }
21
+
22
+ /* App card grid */
23
+ .row {
24
+ display: grid;
25
+ grid-template-columns: repeat(3, 1fr);
26
+ gap: 20px;
27
+ }
28
+ @media (max-width: 900px) {
29
+ .row { grid-template-columns: 1fr; }
30
+ }
31
+
32
+ /* App card, mirrors .app-card from preview/components/apps-grid.css */
33
+ .card {
34
+ background: white;
35
+ border: 1px solid var(--c-cobalt-100);
36
+ border-radius: var(--radius-lg);
37
+ padding: 28px;
38
+ text-decoration: none;
39
+ color: inherit;
40
+ transition: all 160ms ease;
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: 16px;
44
+ }
45
+ .card:hover {
46
+ border-color: var(--c-blue-cobalt);
47
+ transform: translateY(-2px);
48
+ box-shadow: var(--shadow-2);
49
+ text-decoration: none;
50
+ color: inherit;
51
+ }
52
+
53
+ .iconWrap {
54
+ width: 56px;
55
+ height: 64px;
56
+ clip-path: var(--hex-pointy-top);
57
+ background: var(--c-blue-cobalt);
58
+ color: white;
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ flex-shrink: 0;
63
+ }
64
+ .iconWrap svg {
65
+ width: 26px;
66
+ height: 26px;
67
+ stroke: currentColor;
68
+ stroke-width: 2;
69
+ fill: none;
70
+ }
71
+
72
+ .name {
73
+ font-size: 20px;
74
+ font-weight: 700;
75
+ letter-spacing: -0.01em;
76
+ color: var(--c-cobalt-900);
77
+ }
78
+
79
+ .tagline {
80
+ font-size: 15px;
81
+ color: var(--c-cobalt-700);
82
+ line-height: 1.5;
83
+ }
84
+ .tagline :global(.next-blue) { color: var(--c-nextcloud-blue); }
85
+
86
+ .meta {
87
+ display: flex;
88
+ align-items: center;
89
+ justify-content: space-between;
90
+ margin-top: auto;
91
+ padding-top: 20px;
92
+ border-top: 1px solid var(--c-cobalt-100);
93
+ }
94
+
95
+ .badge {
96
+ display: inline-flex;
97
+ align-items: center;
98
+ gap: 8px;
99
+ font-family: var(--conduction-typography-font-family-code);
100
+ font-size: 11px;
101
+ letter-spacing: 0.06em;
102
+ text-transform: uppercase;
103
+ color: var(--c-cobalt-700);
104
+ }
105
+
106
+ .ver {
107
+ font-family: var(--conduction-typography-font-family-code);
108
+ font-size: 12px;
109
+ color: var(--c-cobalt-400);
110
+ }
111
+
112
+ /* "Browse all" link */
113
+ .seeAll {
114
+ margin-top: 32px;
115
+ display: inline-flex;
116
+ align-items: center;
117
+ gap: 8px;
118
+ color: var(--c-blue-cobalt);
119
+ font-weight: 600;
120
+ font-size: 15px;
121
+ text-decoration: none;
122
+ border-bottom: 2px solid var(--c-orange-knvb);
123
+ padding-bottom: 2px;
124
+ }
125
+ .seeAll:hover {
126
+ color: var(--c-orange-knvb);
127
+ text-decoration: none;
128
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * <Clients />
3
+ *
4
+ * Public-sector and ecosystem logo wall. Trust-signal section for the
5
+ * Conduction landing, separate from <PartnerCard /> which is the
6
+ * commercial implementation-partner row on /partners.
7
+ *
8
+ * Two variants:
9
+ * - "grid" (default): static columns, used for the partners row
10
+ * and any short logo set
11
+ * - "marquee": three pointy-top hex rows, honeycomb stagger, all
12
+ * lanes scroll right-to-left at the same speed so the
13
+ * rows read as one continuous wall. Logos default to
14
+ * grayscale and restore colour on hover; the lane pauses
15
+ * on hover and respects prefers-reduced-motion. Below
16
+ * 720px it collapses to a single lane.
17
+ *
18
+ * Logos live in sites/www/static/img/clients/ and ship via the brand
19
+ * assets folder; the kit's downloads page mirrors the same set.
20
+ */
21
+
22
+ import React from 'react';
23
+ import Head from '@docusaurus/Head';
24
+ import SectionHead from '../primitives/SectionHead';
25
+ import {useLazyScript} from '../../utils/lazyScript';
26
+ import styles from './Clients.module.css';
27
+
28
+ const LOGO_MEMORY_BASE = '/lib';
29
+
30
+ export const DEFAULT_CLIENTS = [
31
+ {name: 'Albrandswaard', src: '/img/clients/albrandswaard.png'},
32
+ {name: 'Alkmaar', src: '/img/clients/alkmaar.png'},
33
+ {name: 'Amsterdam', src: '/img/clients/amsterdam.png'},
34
+ {name: 'Baarn', src: '/img/clients/baarn.png'},
35
+ {name: 'Barendrecht', src: '/img/clients/barendrecht.png'},
36
+ {name: 'Barneveld', src: '/img/clients/barneveld.svg'},
37
+ {name: 'Beek', src: '/img/clients/beek.svg'},
38
+ {name: 'Breda', src: '/img/clients/breda.png'},
39
+ {name: 'Buren', src: '/img/clients/buren.png'},
40
+ {name: 'DBP', src: '/img/clients/dbp.svg'},
41
+ {name: 'De Bilt', src: '/img/clients/de-bilt.svg'},
42
+ {name: 'Delft', src: '/img/clients/delft.png'},
43
+ {name: 'Dinkelland', src: '/img/clients/dinkelland.png'},
44
+ {name: 'Edam-Volendam', src: '/img/clients/edam-volendam.png'},
45
+ {name: 'Ede', src: '/img/clients/ede.svg'},
46
+ {name: 'Epe', src: '/img/clients/epe.png'},
47
+ {name: 'Gooise Meren', src: '/img/clients/gooise-meren.svg'},
48
+ {name: 'Gouda', src: '/img/clients/gouda.png'},
49
+ {name: 'Hoeksche Waard', src: '/img/clients/hoeksche-waard.png'},
50
+ {name: 'Hof van Twente', src: '/img/clients/hof-van-twente.svg'},
51
+ {name: 'Kansspelautoriteit', src: '/img/clients/ksa.svg'},
52
+ {name: 'Lansingerland', src: '/img/clients/lansingerland.svg'},
53
+ {name: 'Meppel', src: '/img/clients/meppel.svg'},
54
+ {name: 'Moerdijk', src: '/img/clients/moerdijk.svg'},
55
+ {name: 'Molenlanden', src: '/img/clients/molenlanden.png'},
56
+ {name: 'Noaberkracht', src: '/img/clients/noaberkracht.svg'},
57
+ {name: 'Noordwijk', src: '/img/clients/noordwijk.svg'},
58
+ {name: 'ODMH', src: '/img/clients/odmh.svg'},
59
+ {name: 'Oude IJsselstreek', src: '/img/clients/oude-ijsselstreek.png'},
60
+ {name: 'Overbetuwe', src: '/img/clients/over-betuwe.svg'},
61
+ {name: 'Provincie Zeeland', src: '/img/clients/provincie-zeeland.svg'},
62
+ {name: 'Ridderkerk', src: '/img/clients/ridderkerk.png'},
63
+ {name: 'Rijswijk', src: '/img/clients/rijswijk.png'},
64
+ {name: 'Roosendaal', src: '/img/clients/roosendaal.svg'},
65
+ {name: 'Rotterdam', src: '/img/clients/rotterdam.png'},
66
+ {name: 'Soest', src: '/img/clients/soest.svg'},
67
+ {name: 'Stichtse Vecht', src: '/img/clients/stichtse-vecht.svg'},
68
+ {name: 'SURF', src: '/img/clients/surf.svg'},
69
+ {name: 'Tilburg', src: '/img/clients/tilburg.png'},
70
+ {name: 'Tubbergen', src: '/img/clients/tubbergen.png'},
71
+ {name: 'VNG', src: '/img/clients/vng.png'},
72
+ {name: 'Zutphen', src: '/img/clients/zutphen.svg'},
73
+ {name: 'Zwolle', src: '/img/clients/zwolle.svg'},
74
+ ];
75
+
76
+ export const DEFAULT_PARTNERS = [
77
+ {name: 'Nextcloud', src: '/img/clients/nextcloud.png'},
78
+ {name: 'iO', src: '/img/clients/io.webp'},
79
+ {name: 'Ritense', src: '/img/clients/ritense.png'},
80
+ {name: 'Shift2', src: '/img/clients/Shift2.png'},
81
+ {name: 'Open Webconcept', src: '/img/clients/open_webconcept.png'},
82
+ {name: 'SIDN Fonds', src: '/img/clients/SIDN.png'},
83
+ {name: 'BCT', src: '/img/clients/bct.png'},
84
+ ];
85
+
86
+ function splitIntoRows(items, rowCount) {
87
+ const rows = Array.from({length: rowCount}, () => []);
88
+ items.forEach((item, i) => rows[i % rowCount].push(item));
89
+ /* Pad shorter rows with duplicates from their own front so every row
90
+ has the same length and the same track width. Without this, the
91
+ marquee's translateX(-50%) animation drifts the rows out of
92
+ honeycomb alignment because each track's 50% resolves to a
93
+ different pixel offset. */
94
+ const max = Math.max(...rows.map(r => r.length));
95
+ rows.forEach(r => {
96
+ let i = 0;
97
+ while (r.length < max) {
98
+ r.push(r[i % (r.length || 1)]);
99
+ i++;
100
+ }
101
+ });
102
+ return rows;
103
+ }
104
+
105
+ /* ClientsMarquee
106
+ *
107
+ * Marquee variant body. The hex wall is now driven by the JS runtime
108
+ * lib/clients-flow.js: each row is a static relative band, the runtime
109
+ * spawns absolutely-positioned hex anchors on the right with random
110
+ * logos from the supplied pool, drifts them left, and culls them once
111
+ * they leave the viewport. We render only the empty row containers
112
+ * here and pass the client pool through as a data-clients JSON
113
+ * attribute. CSS Modules hashes the .hex / .hexLogo class names, so
114
+ * the runtime needs them via data-hex-class / data-hex-logo-class to
115
+ * style its spawned children consistently.
116
+ *
117
+ * Logo Memory's click trigger still works the same way — the runtime
118
+ * spawns real <a class={styles.hex}> elements, so e.target.closest('a')
119
+ * inside the click handler picks them up.
120
+ */
121
+ function ClientsMarquee({head, clients, title, className}) {
122
+ useLazyScript(LOGO_MEMORY_BASE + '/clients-flow.js', 'clients-flow');
123
+ useLazyScript(LOGO_MEMORY_BASE + '/logo-memory.js', 'logo-memory');
124
+ React.useEffect(() => {
125
+ if (window.ConductionClientsFlow?.hydrate) window.ConductionClientsFlow.hydrate();
126
+ if (window.LogoMemory?.hydrate) window.LogoMemory.hydrate();
127
+ });
128
+ const clientsJson = React.useMemo(() => JSON.stringify(clients), [clients]);
129
+ return (
130
+ <>
131
+ <Head>
132
+ <link rel="stylesheet" href={LOGO_MEMORY_BASE + '/logo-memory.css'} />
133
+ </Head>
134
+ <section
135
+ className={[styles.section, className].filter(Boolean).join(' ')}
136
+ data-logo-memory
137
+ >
138
+ <div className={styles.inner}>{head}</div>
139
+ <div
140
+ className={styles.marquee}
141
+ data-memory-marquee
142
+ data-clients={clientsJson}
143
+ data-hex-class={styles.hex}
144
+ data-hex-logo-class={styles.hexLogo}
145
+ role="region"
146
+ aria-label={typeof title === 'string' ? title : 'Clients'}
147
+ >
148
+ <div data-flow-row className={[styles.row, styles.row1].join(' ')} />
149
+ <div data-flow-row className={[styles.row, styles.row2].join(' ')} />
150
+ <div data-flow-row className={[styles.row, styles.row3].join(' ')} />
151
+ </div>
152
+ </section>
153
+ </>
154
+ );
155
+ }
156
+
157
+ export default function Clients({
158
+ eyebrow = 'Who we work with',
159
+ title = 'Public sector, ecosystem, partners.',
160
+ lede,
161
+ clients = DEFAULT_CLIENTS,
162
+ variant = 'grid',
163
+ className,
164
+ }) {
165
+ const head = (
166
+ <SectionHead
167
+ eyebrow={eyebrow}
168
+ title={title}
169
+ align="stack"
170
+ lede={lede}
171
+ />
172
+ );
173
+
174
+ if (variant === 'marquee') {
175
+ return (
176
+ <ClientsMarquee
177
+ head={head}
178
+ clients={clients}
179
+ title={title}
180
+ className={className}
181
+ />
182
+ );
183
+ }
184
+
185
+ return (
186
+ <section className={[styles.section, className].filter(Boolean).join(' ')}>
187
+ <div className={styles.inner}>
188
+ {head}
189
+ <div className={styles.grid} role="list">
190
+ {clients.map((c, i) => (
191
+ <div key={i} className={styles.cell} role="listitem">
192
+ <img
193
+ src={c.src}
194
+ alt={c.name}
195
+ title={c.name}
196
+ loading="lazy"
197
+ className={styles.logo}
198
+ />
199
+ </div>
200
+ ))}
201
+ </div>
202
+ </div>
203
+ </section>
204
+ );
205
+ }