@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,129 @@
1
+ /**
2
+ * <Showcase />
3
+ *
4
+ * Two-pane explainer: a stack of expandable items on the left, a
5
+ * panel on the right that reflects whichever item is open. Reference:
6
+ * honeycomb.io/use-cases/ai-llm-observability ("Observability with AI
7
+ * Agents" accordion + product-mock pattern), translated to Conduction
8
+ * tokens.
9
+ *
10
+ * The right panel can be anything — an <AppMock>, a custom JSX
11
+ * illustration, an image. The component owns nothing about the panel
12
+ * shape; it just swaps which one is mounted when the user expands a
13
+ * different item.
14
+ *
15
+ * Used for two surfaces today:
16
+ * - "The Conduction Difference" (5 rules, one panel per rule)
17
+ * - "Solutions for every team" (1 use case per audience tier)
18
+ *
19
+ * Same shape, different labels. Set `layout="stack"` to render the
20
+ * items as horizontal tabs above the panel instead of as a vertical
21
+ * accordion left of it (the "Solutions for every team" variant).
22
+ *
23
+ * Usage in MDX:
24
+ *
25
+ * <Showcase
26
+ * eyebrow="The Conduction Difference"
27
+ * title="Five rules, one workspace."
28
+ * items={[
29
+ * {
30
+ * id: 'open',
31
+ * title: 'Open by default.',
32
+ * summary: 'Every line is EUPL-1.2 on GitHub.',
33
+ * cta: {label: 'Browse on GitHub', href: 'https://github.com/ConductionNL'},
34
+ * panel: <AppMock app="opencatalogi" />,
35
+ * },
36
+ * ...
37
+ * ]}
38
+ * defaultOpen="open"
39
+ * />
40
+ */
41
+
42
+ import React, {useState} from 'react';
43
+ import styles from './Showcase.module.css';
44
+
45
+ export default function Showcase({
46
+ eyebrow,
47
+ title,
48
+ lede,
49
+ items = [],
50
+ defaultOpen,
51
+ layout = 'side',
52
+ className,
53
+ }) {
54
+ const initialId = defaultOpen || items[0]?.id;
55
+ const [openId, setOpenId] = useState(initialId);
56
+ /* The right-side panel always shows *something*. When the user toggles
57
+ the open item closed, fall back to the most recently opened panel
58
+ so the showcase doesn't go blank. lastPanelId tracks that. */
59
+ const [lastPanelId, setLastPanelId] = useState(initialId);
60
+ const open = items.find(it => it.id === openId) || null;
61
+ const panelItem = items.find(it => it.id === (openId || lastPanelId)) || items[0];
62
+
63
+ const toggle = (id) => {
64
+ if (openId === id) {
65
+ setOpenId(null);
66
+ } else {
67
+ setOpenId(id);
68
+ setLastPanelId(id);
69
+ }
70
+ };
71
+
72
+ return (
73
+ <section className={[styles.showcase, styles[`layout-${layout}`], className].filter(Boolean).join(' ')}>
74
+ {(eyebrow || title || lede) && (
75
+ <header className={styles.head}>
76
+ {eyebrow && <div className={styles.eyebrow}><span className={styles.h}></span>{eyebrow}</div>}
77
+ {title && <h2 className={styles.title}>{title}</h2>}
78
+ {lede && <p className={styles.lede}>{lede}</p>}
79
+ </header>
80
+ )}
81
+ <div className={styles.body}>
82
+ <div className={styles.list} role="tablist" aria-orientation={layout === 'side' ? 'vertical' : 'horizontal'}>
83
+ {items.map((it) => {
84
+ const isOpen = it.id === open?.id;
85
+ return (
86
+ <div key={it.id} className={[styles.item, isOpen && styles.itemOpen].filter(Boolean).join(' ')}>
87
+ <button
88
+ role="tab"
89
+ aria-selected={isOpen}
90
+ aria-expanded={isOpen}
91
+ aria-controls={`showcase-panel-${it.id}`}
92
+ className={styles.itemHead}
93
+ onClick={() => toggle(it.id)}
94
+ type="button"
95
+ >
96
+ <span className={styles.titleGroup}>
97
+ {it.icon && (
98
+ <span className={styles.itemIcon} aria-hidden="true">
99
+ {typeof it.icon === 'string'
100
+ ? <img src={it.icon} alt="" className={styles.itemIconImg} />
101
+ : it.icon}
102
+ </span>
103
+ )}
104
+ <h3 className={styles.itemTitle}>{it.title}</h3>
105
+ </span>
106
+ <span className={styles.chevron} aria-hidden="true">{isOpen ? '−' : '+'}</span>
107
+ </button>
108
+ {isOpen && (
109
+ <div className={styles.itemBody}>
110
+ {it.summary && <p className={styles.itemSummary}>{it.summary}</p>}
111
+ {it.cta && (
112
+ <a href={it.cta.href} className={styles.cta}>
113
+ {it.cta.label}
114
+ </a>
115
+ )}
116
+ </div>
117
+ )}
118
+ </div>
119
+ );
120
+ })}
121
+ </div>
122
+ <div className={styles.panel} role="tabpanel" id={`showcase-panel-${panelItem?.id}`} aria-label={panelItem?.title}>
123
+ <div className={styles.panelHexBg} aria-hidden="true"></div>
124
+ <div className={styles.panelInner}>{panelItem?.panel}</div>
125
+ </div>
126
+ </div>
127
+ </section>
128
+ );
129
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * <Showcase /> styles. Two layouts share the same atoms:
3
+ * layout="side" → accordion left, panel right (Conduction Difference)
4
+ * layout="stack" → tabs on top, panel below (Solutions for every team)
5
+ */
6
+
7
+ .showcase {
8
+ max-width: 1280px;
9
+ margin: 0 auto;
10
+ padding: var(--space-12) var(--space-12);
11
+ font-family: var(--conduction-typography-font-family-body);
12
+ }
13
+
14
+ /* ---------- Header ---------- */
15
+ .head { max-width: 70ch; margin: 0 0 var(--space-8); }
16
+ .eyebrow {
17
+ display: inline-flex; align-items: center; gap: var(--space-2);
18
+ font-family: var(--conduction-typography-font-family-code);
19
+ font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase;
20
+ color: var(--c-orange-knvb);
21
+ margin-bottom: var(--space-3);
22
+ }
23
+ .eyebrow .h { width: 10px; height: 12px; clip-path: var(--hex-pointy-top); background: var(--c-orange-knvb); }
24
+ .title {
25
+ font-size: 40px; font-weight: 700; letter-spacing: -0.02em;
26
+ line-height: 1.1; color: var(--c-cobalt-900); margin: 0 0 var(--space-3);
27
+ }
28
+ .lede { font-size: 17px; line-height: 1.5; color: var(--c-cobalt-700); margin: 0; }
29
+
30
+ /* ---------- Body ---------- */
31
+ .body { display: grid; gap: var(--space-8); align-items: stretch; }
32
+ .layout-side .body { grid-template-columns: 1fr 1.2fr; }
33
+ .layout-stack .body { grid-template-columns: 1fr; gap: var(--space-6); }
34
+ @media (max-width: 900px) { .layout-side .body { grid-template-columns: 1fr; } }
35
+
36
+ /* ---------- List of items ---------- */
37
+ .list { display: flex; flex-direction: column; gap: var(--space-3); }
38
+ .layout-stack .list {
39
+ flex-direction: row; flex-wrap: wrap; gap: var(--space-2);
40
+ }
41
+
42
+ .item {
43
+ background: white;
44
+ border: 1px solid var(--c-cobalt-100);
45
+ border-radius: var(--radius-lg);
46
+ padding: var(--space-4) var(--space-5);
47
+ transition: border-color 160ms, box-shadow 160ms, background 160ms;
48
+ display: flex; flex-direction: column; gap: var(--space-3);
49
+ }
50
+ .item:hover { border-color: var(--c-cobalt-300); }
51
+
52
+ .itemHead {
53
+ /* button reset */
54
+ appearance: none; -webkit-appearance: none;
55
+ background: transparent; border: 0; padding: 0;
56
+ font: inherit; color: inherit; cursor: pointer;
57
+ width: 100%;
58
+ text-align: left;
59
+ display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
60
+ }
61
+ .itemHead:focus-visible { outline: 2px solid var(--c-orange-knvb); outline-offset: 2px; border-radius: var(--radius-sm); }
62
+
63
+ .titleGroup {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ gap: var(--space-3);
67
+ min-width: 0;
68
+ }
69
+
70
+ .itemIcon {
71
+ flex: 0 0 auto;
72
+ width: 32px;
73
+ height: 36px;
74
+ clip-path: var(--hex-pointy-top);
75
+ background: var(--c-cobalt-50);
76
+ display: inline-flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ color: var(--c-cobalt-700);
80
+ transition: background 160ms, color 160ms;
81
+ }
82
+ .itemIcon svg {
83
+ width: 18px;
84
+ height: 18px;
85
+ stroke: currentColor;
86
+ stroke-width: 1.6;
87
+ fill: none;
88
+ stroke-linecap: round;
89
+ stroke-linejoin: round;
90
+ }
91
+ .itemIconImg {
92
+ max-width: 60%;
93
+ max-height: 60%;
94
+ object-fit: contain;
95
+ }
96
+ .itemOpen .itemIcon { background: var(--c-blue-cobalt); color: white; }
97
+
98
+ .itemTitle { font-size: 18px; font-weight: 600; letter-spacing: -0.01em; color: var(--c-cobalt-900); margin: 0; }
99
+ .chevron {
100
+ font-family: var(--conduction-typography-font-family-code);
101
+ font-size: 22px; line-height: 1; color: var(--c-cobalt-400);
102
+ width: 22px; text-align: center;
103
+ }
104
+
105
+ .itemOpen {
106
+ border-color: var(--c-blue-cobalt);
107
+ box-shadow: var(--shadow-2);
108
+ }
109
+ .itemOpen .chevron { color: var(--c-blue-cobalt); }
110
+ .itemOpen .itemTitle { color: var(--c-blue-cobalt); }
111
+
112
+ .itemBody { display: flex; flex-direction: column; gap: var(--space-3); }
113
+ .itemSummary { font-size: 14px; line-height: 1.55; color: var(--c-cobalt-700); margin: 0; }
114
+
115
+ .cta {
116
+ align-self: flex-start;
117
+ display: inline-flex; align-items: center;
118
+ background: var(--c-blue-cobalt); color: white;
119
+ padding: 9px 16px;
120
+ border-radius: var(--radius-md);
121
+ font-size: 13px; font-weight: 600; letter-spacing: 0.04em;
122
+ text-transform: uppercase;
123
+ text-decoration: none;
124
+ transition: background 160ms;
125
+ }
126
+ .cta:hover { background: var(--c-cobalt-700); color: white; }
127
+
128
+ /* Stack-layout tabs are denser */
129
+ .layout-stack .item { flex: 1 1 180px; padding: var(--space-3) var(--space-4); }
130
+ .layout-stack .itemTitle { font-size: 15px; }
131
+ .layout-stack .itemBody { display: none; }
132
+
133
+ /* ---------- Right panel ---------- */
134
+ .panel {
135
+ background: var(--c-cobalt-50);
136
+ border-radius: var(--radius-xl);
137
+ padding: var(--space-8);
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ position: relative;
142
+ min-height: 360px;
143
+ overflow: hidden;
144
+ }
145
+
146
+ /* Big pointy-top hex sitting behind the panel content. The hex is
147
+ over-sized and pushed up so its top point and shoulder corners
148
+ crest the panel's top edge — two of the hex's six corners visibly
149
+ poke up into the layout, brand-identifying without being literal.
150
+ The lower half of the hex provides the cobalt-100 wash behind the
151
+ AppMock. */
152
+ .panelHexBg {
153
+ position: absolute;
154
+ width: 130%;
155
+ aspect-ratio: 1 / 1.155;
156
+ top: -22%;
157
+ left: 50%;
158
+ transform: translateX(-50%);
159
+ clip-path: var(--hex-pointy-top);
160
+ background: var(--c-cobalt-100);
161
+ z-index: 0;
162
+ pointer-events: none;
163
+ }
164
+ .panelInner {
165
+ position: relative; z-index: 1;
166
+ width: 100%;
167
+ display: flex; align-items: center; justify-content: center;
168
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * <SolutionCard /> + <SolutionGrid />
3
+ *
4
+ * Solution card from preview/components/solution-cards.html.
5
+ *
6
+ * A card represents a *solution*, never an app. The visual structure:
7
+ *
8
+ * ┌─────────────────────────────┐
9
+ * │ ⬢ icon-hex SECTOR-TAG │ ← top row
10
+ * │ │
11
+ * │ Title goes here. │
12
+ * │ Outcome paragraph below. │
13
+ * │ ────────────── │ ← divider above pills
14
+ * │ ⬢ App ⬢ App ⬢ App │ ← built-on pills
15
+ * └─────────────────────────────┘
16
+ *
17
+ * Sector tints the icon-hex (public=cobalt, mkb=cobalt-700, health=
18
+ * orange) but everything else stays the same.
19
+ *
20
+ * Usage in MDX:
21
+ *
22
+ * <SolutionGrid columns={2}>
23
+ * <SolutionCard
24
+ * href="/solutions/woo"
25
+ * sector="public"
26
+ * sectorLabel="Publieke sector"
27
+ * icon={<svg>...</svg>}
28
+ * title="WOO compliance, by Friday."
29
+ * outcome={<>A live WOO portal at your <span className="next-blue">Nextcloud</span>-hosted domain.</>}
30
+ * builtOn={['OpenCatalogi', 'OpenRegister', 'OpenConnector']}
31
+ * />
32
+ * </SolutionGrid>
33
+ */
34
+
35
+ import React from 'react';
36
+ import Pill from '../primitives/Pill';
37
+ import styles from './SolutionCard.module.css';
38
+
39
+ const SECTOR_DEFAULT_LABELS = {
40
+ public: 'Publieke sector',
41
+ mkb: 'MKB',
42
+ health: 'Zorg',
43
+ };
44
+
45
+ export function SolutionGrid({columns = 2, children, className}) {
46
+ const classes = [styles.grid, styles['cols-' + columns], className].filter(Boolean).join(' ');
47
+ return <div className={classes}>{children}</div>;
48
+ }
49
+
50
+ export default function SolutionCard({
51
+ href,
52
+ sector = 'public',
53
+ sectorLabel,
54
+ icon,
55
+ title,
56
+ outcome,
57
+ builtOn = [],
58
+ className,
59
+ }) {
60
+ const composed = [styles.card, styles['sector-' + sector], className].filter(Boolean).join(' ');
61
+ const Tag = href ? 'a' : 'div';
62
+ return (
63
+ <Tag href={href} className={composed}>
64
+ <div className={styles.top}>
65
+ <div className={styles.iconHex}>{icon}</div>
66
+ <span className={styles.sectorTag}>
67
+ {sectorLabel || SECTOR_DEFAULT_LABELS[sector] || sector}
68
+ </span>
69
+ </div>
70
+
71
+ {title && <h3 className={styles.title}>{title}</h3>}
72
+ {outcome && <p className={styles.outcome}>{outcome}</p>}
73
+
74
+ {builtOn.length > 0 && (
75
+ <div className={styles.builtOn}>
76
+ {builtOn.map((app, i) => (
77
+ <Pill key={i} bullet bulletColor="var(--c-blue-cobalt)">{app}</Pill>
78
+ ))}
79
+ </div>
80
+ )}
81
+ </Tag>
82
+ );
83
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * <SolutionCard /> styles. Mirrors solution-cards.css exactly.
3
+ */
4
+
5
+ .grid { display: grid; gap: 20px; }
6
+ .cols-2 { grid-template-columns: repeat(2, 1fr); }
7
+ .cols-3 { grid-template-columns: repeat(3, 1fr); }
8
+ @media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
9
+
10
+ .card {
11
+ background: white;
12
+ border: 1px solid var(--c-cobalt-100);
13
+ border-radius: var(--radius-lg);
14
+ padding: 28px;
15
+ text-decoration: none;
16
+ color: inherit;
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: 18px;
20
+ transition: all 160ms ease;
21
+ font-family: var(--conduction-typography-font-family-body);
22
+ }
23
+ .card:hover {
24
+ border-color: var(--c-blue-cobalt);
25
+ transform: translateY(-2px);
26
+ box-shadow: var(--shadow-2);
27
+ text-decoration: none;
28
+ color: inherit;
29
+ }
30
+
31
+ .top {
32
+ display: flex;
33
+ align-items: flex-start;
34
+ justify-content: space-between;
35
+ gap: 18px;
36
+ }
37
+
38
+ .iconHex {
39
+ width: 56px;
40
+ height: 64px;
41
+ clip-path: var(--hex-pointy-top);
42
+ background: var(--c-blue-cobalt);
43
+ color: white;
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ flex-shrink: 0;
48
+ }
49
+ .iconHex svg {
50
+ width: 24px;
51
+ height: 24px;
52
+ stroke: currentColor;
53
+ stroke-width: 2;
54
+ fill: none;
55
+ }
56
+
57
+ .sector-public .iconHex { background: var(--c-blue-cobalt); }
58
+ .sector-mkb .iconHex { background: var(--c-cobalt-700); }
59
+ .sector-health .iconHex { background: var(--c-orange-knvb); }
60
+
61
+ .sectorTag {
62
+ display: inline-flex;
63
+ align-items: center;
64
+ gap: 6px;
65
+ font-family: var(--conduction-typography-font-family-code);
66
+ font-size: 10px;
67
+ letter-spacing: 0.1em;
68
+ text-transform: uppercase;
69
+ color: var(--c-cobalt-700);
70
+ background: var(--c-cobalt-50);
71
+ padding: 4px 10px;
72
+ border-radius: var(--radius-pill);
73
+ }
74
+
75
+ .title {
76
+ font-size: 22px;
77
+ font-weight: 700;
78
+ letter-spacing: -0.01em;
79
+ color: var(--c-cobalt-900);
80
+ margin: 0;
81
+ }
82
+
83
+ .outcome {
84
+ font-size: 15px;
85
+ color: var(--c-cobalt-700);
86
+ line-height: 1.55;
87
+ margin: 0;
88
+ }
89
+ .outcome :global(.next-blue) { color: var(--c-nextcloud-blue); }
90
+ .outcome :global(.cg-yellow) { color: var(--c-commonground-yellow); }
91
+
92
+ .builtOn {
93
+ display: flex;
94
+ gap: 6px;
95
+ flex-wrap: wrap;
96
+ margin-top: auto;
97
+ padding-top: 18px;
98
+ border-top: 1px solid var(--c-cobalt-100);
99
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * <StatsStrip />
3
+ *
4
+ * Four-up tile of headline numbers, sub-hero band on cobalt-50.
5
+ * Each stat is { value, label }; label can be a string or ReactNode
6
+ * (so MDX can pass <>line one<br/>line two</> for the two-line look).
7
+ *
8
+ * Mirrors preview/components/stats-strip.html.
9
+ *
10
+ * Usage in MDX:
11
+ *
12
+ * <StatsStrip stats={[
13
+ * { value: '24', label: <>apps in the ecosystem,<br/>all open-source</> },
14
+ * { value: '2 min', label: 'install time' },
15
+ * { value: '€0', label: 'license fee, support is optional' },
16
+ * { value: 'EUPL', label: 'no vendor lock-in' },
17
+ * ]} />
18
+ */
19
+
20
+ import React from 'react';
21
+ import styles from './StatsStrip.module.css';
22
+
23
+ export default function StatsStrip({stats = []}) {
24
+ if (!stats.length) return null;
25
+
26
+ return (
27
+ <section className={styles.strip} aria-label="Key statistics">
28
+ <div className={styles.inner} style={{'--stats-count': stats.length}}>
29
+ {stats.map((stat, i) => (
30
+ <div key={i} className={styles.stat}>
31
+ <div className={styles.value}>{stat.value}</div>
32
+ <div className={styles.label}>{stat.label}</div>
33
+ </div>
34
+ ))}
35
+ </div>
36
+ </section>
37
+ );
38
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * <StatsStrip /> styles. Mirrors the .strip section in
3
+ * preview/pages/landing.html: cobalt-50 band with top + bottom
4
+ * cobalt-100 borders, four-up grid inside a 1280px max-width
5
+ * inner container.
6
+ */
7
+
8
+ .strip {
9
+ background: var(--c-cobalt-50);
10
+ border-top: 1px solid var(--c-cobalt-100);
11
+ border-bottom: 1px solid var(--c-cobalt-100);
12
+ font-family: var(--conduction-typography-font-family-body);
13
+ }
14
+
15
+ .inner {
16
+ max-width: 1280px;
17
+ margin: 0 auto;
18
+ padding: 48px 64px;
19
+ display: grid;
20
+ grid-template-columns: repeat(var(--stats-count, 4), minmax(0, 1fr));
21
+ gap: 32px;
22
+ }
23
+
24
+ @media (max-width: 1100px) {
25
+ .inner {
26
+ grid-template-columns: repeat(min(var(--stats-count, 4), 4), minmax(0, 1fr));
27
+ }
28
+ }
29
+ @media (max-width: 900px) {
30
+ .inner {
31
+ grid-template-columns: repeat(2, minmax(0, 1fr));
32
+ padding: 32px 24px;
33
+ gap: 24px;
34
+ }
35
+ }
36
+ @media (max-width: 500px) {
37
+ .inner { grid-template-columns: 1fr; }
38
+ }
39
+
40
+ .value {
41
+ font-size: 42px;
42
+ font-weight: 700;
43
+ letter-spacing: -0.02em;
44
+ color: var(--c-blue-cobalt);
45
+ line-height: 1;
46
+ }
47
+
48
+ .label {
49
+ font-size: 14px;
50
+ color: var(--c-cobalt-700);
51
+ margin-top: 8px;
52
+ line-height: 1.4;
53
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * <WidgetShelf />
3
+ *
4
+ * Section that lists the dashboard widgets a Conduction app ships
5
+ * for Nextcloud. Each widget gets a small token-built preview, a
6
+ * headline, and a one-sentence description. Used on every app
7
+ * detail page to show "what widgets you get on the home screen
8
+ * the moment this app is installed".
9
+ *
10
+ * Each Conduction app that registers a Nextcloud-dashboard widget
11
+ * shows up automatically in MyDash; this section makes that promise
12
+ * concrete by drawing the widgets the app actually contributes.
13
+ *
14
+ * Usage in MDX:
15
+ *
16
+ * <WidgetShelf
17
+ * eyebrow="Widgets we ship"
18
+ * title="On every Nextcloud dashboard."
19
+ * lede="Install Procest and your team gets these widgets..."
20
+ * widgets={[
21
+ * {
22
+ * title: 'Werkvoorraad',
23
+ * desc: 'Active cases for the logged-in case-worker...',
24
+ * panel: <div className="w w-werkvoorraad">...</div>,
25
+ * },
26
+ * ...
27
+ * ]}
28
+ * />
29
+ *
30
+ * Layout: 2 or 3 columns depending on widget count, responsive
31
+ * to viewport. Each card has the panel (preview) at the top,
32
+ * title + description below.
33
+ */
34
+
35
+ import React from 'react';
36
+ import styles from './WidgetShelf.module.css';
37
+
38
+ export default function WidgetShelf({
39
+ eyebrow,
40
+ title,
41
+ lede,
42
+ widgets = [],
43
+ columns,
44
+ className,
45
+ }) {
46
+ const cols = columns || (widgets.length >= 4 ? 3 : Math.min(widgets.length, 3));
47
+ return (
48
+ <section className={[styles.shelf, className].filter(Boolean).join(' ')}>
49
+ {(eyebrow || title || lede) && (
50
+ <header className={styles.head}>
51
+ {eyebrow && <div className={styles.eyebrow}><span className={styles.h}></span>{eyebrow}</div>}
52
+ {title && <h2 className={styles.title}>{title}</h2>}
53
+ {lede && <p className={styles.lede}>{lede}</p>}
54
+ </header>
55
+ )}
56
+ <div className={[styles.grid, styles[`cols-${cols}`]].join(' ')}>
57
+ {widgets.map((w, i) => (
58
+ <article key={i} className={styles.card}>
59
+ <div className={styles.panel}>{w.panel}</div>
60
+ {w.title && <h3 className={styles.cardTitle}>{w.title}</h3>}
61
+ {w.desc && <p className={styles.cardDesc}>{w.desc}</p>}
62
+ </article>
63
+ ))}
64
+ </div>
65
+ </section>
66
+ );
67
+ }