@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.
- package/MISSING_COMPONENTS.md +109 -0
- package/README.md +171 -0
- package/package.json +59 -0
- package/src/components/AgentTrace/AgentTrace.jsx +128 -0
- package/src/components/AgentTrace/AgentTrace.module.css +115 -0
- package/src/components/AppMock/AppMock.jsx +86 -0
- package/src/components/AppMock/AppMock.module.css +629 -0
- package/src/components/AppMock/variants/DeciDeskMock.jsx +71 -0
- package/src/components/AppMock/variants/DocuDeskMock.jsx +69 -0
- package/src/components/AppMock/variants/LarpingAppMock.jsx +59 -0
- package/src/components/AppMock/variants/MyDashBiMock.jsx +135 -0
- package/src/components/AppMock/variants/MyDashMock.jsx +96 -0
- package/src/components/AppMock/variants/MyDashTilesMock.jsx +103 -0
- package/src/components/AppMock/variants/MyDashWidgetsMock.jsx +123 -0
- package/src/components/AppMock/variants/NLDesignMock.jsx +70 -0
- package/src/components/AppMock/variants/OpenCatalogiMock.jsx +61 -0
- package/src/components/AppMock/variants/OpenConnectorMock.jsx +83 -0
- package/src/components/AppMock/variants/OpenRegisterMock.jsx +100 -0
- package/src/components/AppMock/variants/OpenWooMock.jsx +61 -0
- package/src/components/AppMock/variants/PipelinQMock.jsx +88 -0
- package/src/components/AppMock/variants/ProcestMock.jsx +87 -0
- package/src/components/AppMock/variants/SoftwareCatalogMock.jsx +71 -0
- package/src/components/AppMock/variants/ZaakAfhandelAppMock.jsx +71 -0
- package/src/components/AppsGrid/AppsGrid.jsx +84 -0
- package/src/components/AppsGrid/AppsGrid.module.css +46 -0
- package/src/components/AppsPreview/AppsPreview.jsx +85 -0
- package/src/components/AppsPreview/AppsPreview.module.css +128 -0
- package/src/components/Clients/Clients.jsx +205 -0
- package/src/components/Clients/Clients.module.css +166 -0
- package/src/components/ComposeBlock/ComposeBlock.jsx +70 -0
- package/src/components/ComposeBlock/ComposeBlock.module.css +74 -0
- package/src/components/ConductionBg/ConductionBg.jsx +150 -0
- package/src/components/ConductionBg/ConductionBg.module.css +41 -0
- package/src/components/ContentCard/ContentCard.jsx +126 -0
- package/src/components/ContentCard/ContentCard.module.css +84 -0
- package/src/components/ContentDetailHero/ContentDetailHero.jsx +136 -0
- package/src/components/ContentDetailHero/ContentDetailHero.module.css +96 -0
- package/src/components/ContentTypeFilter/ContentTypeFilter.jsx +103 -0
- package/src/components/ContentTypeFilter/ContentTypeFilter.module.css +60 -0
- package/src/components/ContentTypeFilter/contentTypes.js +58 -0
- package/src/components/CookieCli/CookieCli.jsx +223 -0
- package/src/components/CookieCli/CookieCli.module.css +166 -0
- package/src/components/CtaBanner/CtaBanner.jsx +61 -0
- package/src/components/CtaBanner/CtaBanner.module.css +65 -0
- package/src/components/DetailHero/DetailHero.jsx +143 -0
- package/src/components/DetailHero/DetailHero.module.css +154 -0
- package/src/components/Diagrams/Diagrams.jsx +148 -0
- package/src/components/EmployeeCard/EmployeeCard.jsx +127 -0
- package/src/components/EmployeeCard/EmployeeCard.module.css +144 -0
- package/src/components/ExternalAppShelf/ExternalAppShelf.jsx +61 -0
- package/src/components/ExternalAppShelf/ExternalAppShelf.module.css +90 -0
- package/src/components/FAQ/FAQ.jsx +42 -0
- package/src/components/FAQ/FAQ.module.css +74 -0
- package/src/components/FacetedFilters/FacetedFilters.jsx +125 -0
- package/src/components/FacetedFilters/FacetedFilters.module.css +133 -0
- package/src/components/FeatureGrid/FeatureGrid.jsx +94 -0
- package/src/components/FeatureGrid/FeatureGrid.module.css +114 -0
- package/src/components/FeatureList/FeatureList.jsx +54 -0
- package/src/components/FeatureList/FeatureList.module.css +52 -0
- package/src/components/FeaturedCard/FeaturedCard.jsx +101 -0
- package/src/components/FeaturedCard/FeaturedCard.module.css +98 -0
- package/src/components/GameModal/GameModal.jsx +197 -0
- package/src/components/GameModal/GameModal.module.css +184 -0
- package/src/components/Hero/Hero.jsx +101 -0
- package/src/components/Hero/Hero.module.css +95 -0
- package/src/components/HexBackground/HexBackground.jsx +56 -0
- package/src/components/HexBackground/HexBackground.module.css +73 -0
- package/src/components/HexNetwork/HexNetwork.jsx +141 -0
- package/src/components/HexNetwork/HexNetwork.module.css +187 -0
- package/src/components/HexRain/HexRain.jsx +81 -0
- package/src/components/HowSteps/HowSteps.jsx +57 -0
- package/src/components/HowSteps/HowSteps.module.css +52 -0
- package/src/components/ManagedCommonGround/ManagedCommonGround.jsx +78 -0
- package/src/components/ManagedCommonGround/ManagedCommonGround.module.css +16 -0
- package/src/components/NewsletterCta/NewsletterCta.jsx +83 -0
- package/src/components/NewsletterCta/NewsletterCta.module.css +103 -0
- package/src/components/PairCard/PairCard.jsx +58 -0
- package/src/components/PairCard/PairCard.module.css +54 -0
- package/src/components/PartnerCard/PartnerCard.jsx +130 -0
- package/src/components/PartnerCard/PartnerCard.module.css +198 -0
- package/src/components/PartnerDirectory/PartnerDirectory.jsx +122 -0
- package/src/components/PartnerDirectory/PartnerDirectory.module.css +25 -0
- package/src/components/PartnerSidecard/PartnerSidecard.jsx +116 -0
- package/src/components/PartnerSidecard/PartnerSidecard.module.css +185 -0
- package/src/components/Pipeline/Pipeline.jsx +198 -0
- package/src/components/Pipeline/Pipeline.module.css +206 -0
- package/src/components/PlatformDiagram/PlatformDiagram.jsx +110 -0
- package/src/components/PlatformOverview/PlatformOverview.jsx +68 -0
- package/src/components/PlatformOverview/PlatformOverview.module.css +71 -0
- package/src/components/ReferenceCard/ReferenceCard.jsx +44 -0
- package/src/components/ReferenceCard/ReferenceCard.module.css +57 -0
- package/src/components/RelatedPosts/RelatedPosts.jsx +58 -0
- package/src/components/RelatedPosts/RelatedPosts.module.css +51 -0
- package/src/components/RotatingCards/RotatingCards.jsx +98 -0
- package/src/components/RotatingCards/RotatingCards.module.css +153 -0
- package/src/components/Showcase/Showcase.jsx +129 -0
- package/src/components/Showcase/Showcase.module.css +168 -0
- package/src/components/SolutionCard/SolutionCard.jsx +83 -0
- package/src/components/SolutionCard/SolutionCard.module.css +99 -0
- package/src/components/StatsStrip/StatsStrip.jsx +38 -0
- package/src/components/StatsStrip/StatsStrip.module.css +53 -0
- package/src/components/WidgetShelf/WidgetShelf.jsx +67 -0
- package/src/components/WidgetShelf/WidgetShelf.module.css +73 -0
- package/src/components/index.js +96 -0
- package/src/components/primitives/AuthorByline.jsx +85 -0
- package/src/components/primitives/AuthorByline.module.css +57 -0
- package/src/components/primitives/BrandCitation.jsx +71 -0
- package/src/components/primitives/Button.jsx +46 -0
- package/src/components/primitives/Button.module.css +88 -0
- package/src/components/primitives/Card.jsx +42 -0
- package/src/components/primitives/Card.module.css +42 -0
- package/src/components/primitives/Eyebrow.jsx +37 -0
- package/src/components/primitives/Eyebrow.module.css +19 -0
- package/src/components/primitives/HexBullet.jsx +37 -0
- package/src/components/primitives/HexBullet.module.css +16 -0
- package/src/components/primitives/HexThumbnail.jsx +70 -0
- package/src/components/primitives/HexThumbnail.module.css +45 -0
- package/src/components/primitives/Pill.jsx +42 -0
- package/src/components/primitives/Pill.module.css +30 -0
- package/src/components/primitives/Section.jsx +51 -0
- package/src/components/primitives/Section.module.css +31 -0
- package/src/components/primitives/SectionHead.jsx +36 -0
- package/src/components/primitives/SectionHead.module.css +43 -0
- package/src/components/primitives/index.js +22 -0
- package/src/css/brand.css +158 -0
- package/src/css/tokens.css +12 -0
- package/src/data/app-downloads.js +42 -0
- package/src/diagrams/README.md +74 -0
- package/src/diagrams/cn-domain-tree.js +105 -0
- package/src/diagrams/cn-hex-prism.js +163 -0
- package/src/diagrams/cn-hex.js +181 -0
- package/src/diagrams/cn-honeycomb-bg.js +135 -0
- package/src/diagrams/cn-pipeline.js +150 -0
- package/src/diagrams/cn-platform.js +156 -0
- package/src/diagrams/cn-side-box.js +104 -0
- package/src/diagrams/index.js +28 -0
- package/src/index.js +183 -0
- package/src/theme/Footer/index.jsx +516 -0
- package/src/theme/MDXPage/index.jsx +134 -0
- package/src/theme/Navbar/index.jsx +120 -0
- package/src/theme/Navbar/styles.module.css +114 -0
- package/src/theme/brand.jsx +63 -0
- package/src/theme.js +45 -0
- package/src/utils/lazyScript.js +37 -0
- package/static/img/favicon.svg +14 -0
- package/static/img/honeycomb-scatter.svg +23 -0
- package/static/img/honeycomb-watermark.svg +108 -0
- package/static/img/logo-dark.svg +11 -0
- package/static/img/logo.svg +14 -0
- package/static/img/nextcloud-logo.svg +5 -0
- package/static/lib/canal-footer.css +418 -0
- package/static/lib/canal-footer.js +499 -0
- package/static/lib/clients-flow.js +317 -0
- package/static/lib/conduction-bg.css +50 -0
- package/static/lib/conduction-bg.js +122 -0
- package/static/lib/hex-rain.css +128 -0
- package/static/lib/hex-rain.js +284 -0
- package/static/lib/kade-cyclist.css +264 -0
- package/static/lib/kade-cyclist.js +420 -0
- package/static/lib/logo-memory.css +219 -0
- package/static/lib/logo-memory.js +540 -0
- package/static/lib/platform-diagram.css +458 -0
- package/static/lib/platform-diagram.js +414 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CookieCli />
|
|
3
|
+
*
|
|
4
|
+
* Terminal-styled cookie consent banner. Mirrors the kit's
|
|
5
|
+
* preview/components/cookie-cli.html: black terminal panel with green
|
|
6
|
+
* prompt, IBM Plex Mono throughout, four cookie categories shown as
|
|
7
|
+
* [1] [2] [3] [4] options, three action buttons.
|
|
8
|
+
*
|
|
9
|
+
* Persists choice in localStorage under a configurable key. Won't
|
|
10
|
+
* re-render after a decision unless the page calls window
|
|
11
|
+
* .ConductionCookieCli.reset().
|
|
12
|
+
*
|
|
13
|
+
* Per huisstijl: terminal dark IS the accent here. The Plex Mono
|
|
14
|
+
* caption and the [1] [2] keys read as a Conduction tell.
|
|
15
|
+
*
|
|
16
|
+
* Usage in MDX (or anywhere in the app):
|
|
17
|
+
*
|
|
18
|
+
* <CookieCli
|
|
19
|
+
* siteHost="conduction.nl"
|
|
20
|
+
* categories={[
|
|
21
|
+
* {key: 'essential', label: 'essential', tag: 'required', required: true, defaultOn: true},
|
|
22
|
+
* {key: 'analytics', label: 'analytics', tag: 'opt-in', defaultOn: true},
|
|
23
|
+
* {key: 'preferences', label: 'preferences', tag: 'opt-in'},
|
|
24
|
+
* {key: 'marketing', label: 'marketing', tag: 'opt-in'},
|
|
25
|
+
* ]}
|
|
26
|
+
* onAccept={(selected) => { /* track or fire telemetry *\/ }}
|
|
27
|
+
* />
|
|
28
|
+
*
|
|
29
|
+
* The component reads + writes the selection from localStorage
|
|
30
|
+
* automatically; <onAccept/> is called with the resolved selection
|
|
31
|
+
* on every save.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import React, {useState, useEffect, useMemo, useCallback} from 'react';
|
|
35
|
+
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
36
|
+
import styles from './CookieCli.module.css';
|
|
37
|
+
|
|
38
|
+
const STORAGE_KEY = 'conduction:cookie-cli';
|
|
39
|
+
|
|
40
|
+
const DEFAULT_CATEGORIES = [
|
|
41
|
+
{key: 'essential', label: 'essential', tag: 'required', required: true, defaultOn: true},
|
|
42
|
+
{key: 'analytics', label: 'analytics', tag: 'opt-in', defaultOn: false},
|
|
43
|
+
{key: 'preferences', label: 'preferences', tag: 'opt-in', defaultOn: false},
|
|
44
|
+
{key: 'marketing', label: 'marketing', tag: 'opt-in', defaultOn: false},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function readStored() {
|
|
48
|
+
if (typeof window === 'undefined') return null;
|
|
49
|
+
try {
|
|
50
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
51
|
+
return raw ? JSON.parse(raw) : null;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeStored(selection) {
|
|
58
|
+
if (typeof window === 'undefined') return;
|
|
59
|
+
try {
|
|
60
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify({
|
|
61
|
+
...selection,
|
|
62
|
+
_ts: Date.now(),
|
|
63
|
+
}));
|
|
64
|
+
} catch (e) {
|
|
65
|
+
/* localStorage may be disabled (private mode, quota); fail open */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default function CookieCli({
|
|
70
|
+
siteHost = 'conduction.nl',
|
|
71
|
+
categories = DEFAULT_CATEGORIES,
|
|
72
|
+
onAccept,
|
|
73
|
+
className,
|
|
74
|
+
}) {
|
|
75
|
+
const isBrowser = useIsBrowser();
|
|
76
|
+
|
|
77
|
+
const initial = useMemo(() => {
|
|
78
|
+
const fromStore = readStored();
|
|
79
|
+
if (fromStore) return fromStore;
|
|
80
|
+
const fresh = {};
|
|
81
|
+
for (const c of categories) fresh[c.key] = !!c.defaultOn;
|
|
82
|
+
return fresh;
|
|
83
|
+
}, [categories]);
|
|
84
|
+
|
|
85
|
+
const [selected, setSelected] = useState(initial);
|
|
86
|
+
const [decided, setDecided] = useState(() => readStored() != null);
|
|
87
|
+
|
|
88
|
+
/* Sync local state to localStorage if the dataset arrived from
|
|
89
|
+
storage on a fresh mount. Skipped on SSR to keep hydration stable. */
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!isBrowser) return;
|
|
92
|
+
const stored = readStored();
|
|
93
|
+
if (stored) {
|
|
94
|
+
setSelected(stored);
|
|
95
|
+
setDecided(true);
|
|
96
|
+
}
|
|
97
|
+
}, [isBrowser]);
|
|
98
|
+
|
|
99
|
+
/* Expose a window.ConductionCookieCli.reset() so the privacy page
|
|
100
|
+
can re-show the prompt when a user wants to change their choice. */
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (!isBrowser) return;
|
|
103
|
+
window.ConductionCookieCli = {
|
|
104
|
+
reset: () => {
|
|
105
|
+
try { window.localStorage.removeItem(STORAGE_KEY); } catch (e) {/* */}
|
|
106
|
+
setDecided(false);
|
|
107
|
+
},
|
|
108
|
+
get: () => readStored(),
|
|
109
|
+
};
|
|
110
|
+
return () => { delete window.ConductionCookieCli; };
|
|
111
|
+
}, [isBrowser]);
|
|
112
|
+
|
|
113
|
+
const toggle = useCallback((cat) => {
|
|
114
|
+
if (cat.required) return;
|
|
115
|
+
setSelected((prev) => ({...prev, [cat.key]: !prev[cat.key]}));
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
const save = useCallback((finalSelection) => {
|
|
119
|
+
writeStored(finalSelection);
|
|
120
|
+
setSelected(finalSelection);
|
|
121
|
+
setDecided(true);
|
|
122
|
+
if (typeof onAccept === 'function') onAccept(finalSelection);
|
|
123
|
+
}, [onAccept]);
|
|
124
|
+
|
|
125
|
+
const acceptAll = useCallback(() => {
|
|
126
|
+
const all = {};
|
|
127
|
+
for (const c of categories) all[c.key] = true;
|
|
128
|
+
save(all);
|
|
129
|
+
}, [categories, save]);
|
|
130
|
+
|
|
131
|
+
const saveCurrent = useCallback(() => save(selected), [save, selected]);
|
|
132
|
+
|
|
133
|
+
const rejectNonEssential = useCallback(() => {
|
|
134
|
+
const minimal = {};
|
|
135
|
+
for (const c of categories) minimal[c.key] = !!c.required;
|
|
136
|
+
save(minimal);
|
|
137
|
+
}, [categories, save]);
|
|
138
|
+
|
|
139
|
+
/* Bind keyboard shortcuts: A = accept all, S = save, R = reject,
|
|
140
|
+
1-9 = toggle category n. */
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!isBrowser || decided) return;
|
|
143
|
+
function onKey(e) {
|
|
144
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
145
|
+
const k = e.key.toLowerCase();
|
|
146
|
+
if (k === 'a') { e.preventDefault(); acceptAll(); return; }
|
|
147
|
+
if (k === 's') { e.preventDefault(); saveCurrent(); return; }
|
|
148
|
+
if (k === 'r') { e.preventDefault(); rejectNonEssential(); return; }
|
|
149
|
+
const idx = parseInt(e.key, 10);
|
|
150
|
+
if (!Number.isNaN(idx) && idx >= 1 && idx <= categories.length) {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
toggle(categories[idx - 1]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
window.addEventListener('keydown', onKey);
|
|
156
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
157
|
+
}, [isBrowser, decided, acceptAll, saveCurrent, rejectNonEssential, toggle, categories]);
|
|
158
|
+
|
|
159
|
+
/* Hide the banner once the user has made a decision; the prompt
|
|
160
|
+
reappears via window.ConductionCookieCli.reset(). */
|
|
161
|
+
if (!isBrowser || decided) return null;
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className={[styles.term, className].filter(Boolean).join(' ')} role="dialog" aria-label="Cookie consent">
|
|
165
|
+
<div className={styles.bar}>
|
|
166
|
+
<div className={styles.dots}>
|
|
167
|
+
<span className={[styles.dot, styles.dotR].join(' ')} />
|
|
168
|
+
<span className={[styles.dot, styles.dotY].join(' ')} />
|
|
169
|
+
<span className={[styles.dot, styles.dotG].join(' ')} />
|
|
170
|
+
</div>
|
|
171
|
+
<div className={styles.title}>
|
|
172
|
+
{'— '}<b>{siteHost}</b>{' · cookie-consent — bash —'}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<div className={styles.body}>
|
|
176
|
+
<div className={styles.prompt}>
|
|
177
|
+
<span className={styles.user}>you</span>
|
|
178
|
+
<span className={styles.sigil}>@</span>
|
|
179
|
+
<span className={styles.host}>{siteHost}</span>
|
|
180
|
+
<span className={styles.sigil}>:</span>
|
|
181
|
+
<span className={styles.path}>~</span>
|
|
182
|
+
<span className={styles.sigil}>$</span>{' '}
|
|
183
|
+
<span className={styles.cmd}>cat ./cookies.toml</span>
|
|
184
|
+
</div>
|
|
185
|
+
<p className={styles.comment}># Pick what you're OK with. Essential cookies are required.</p>
|
|
186
|
+
|
|
187
|
+
<ul className={styles.opts}>
|
|
188
|
+
{categories.map((c, i) => {
|
|
189
|
+
const on = !!selected[c.key] || !!c.required;
|
|
190
|
+
return (
|
|
191
|
+
<li
|
|
192
|
+
key={c.key}
|
|
193
|
+
className={on ? styles.optOn : null}
|
|
194
|
+
onClick={() => toggle(c)}
|
|
195
|
+
role="button"
|
|
196
|
+
tabIndex={c.required ? -1 : 0}
|
|
197
|
+
aria-pressed={on}
|
|
198
|
+
style={c.required ? {cursor: 'default'} : undefined}
|
|
199
|
+
>
|
|
200
|
+
<span className={styles.optKey}>[{i + 1}]</span>
|
|
201
|
+
<span className={styles.optCheck}>{on ? '✓' : '☐'}</span>
|
|
202
|
+
<span className={styles.optLabel}>{c.label}</span>
|
|
203
|
+
{c.tag && <span className={styles.optTag}>{c.tag}</span>}
|
|
204
|
+
</li>
|
|
205
|
+
);
|
|
206
|
+
})}
|
|
207
|
+
</ul>
|
|
208
|
+
|
|
209
|
+
<div className={styles.actions}>
|
|
210
|
+
<button type="button" className={[styles.btn, styles.btnPrimary].join(' ')} onClick={acceptAll}>
|
|
211
|
+
<span className={styles.btnKey}>A</span>Accept all
|
|
212
|
+
</button>
|
|
213
|
+
<button type="button" className={styles.btn} onClick={saveCurrent}>
|
|
214
|
+
<span className={styles.btnKey}>S</span>Save selection
|
|
215
|
+
</button>
|
|
216
|
+
<button type="button" className={styles.btn} onClick={rejectNonEssential}>
|
|
217
|
+
<span className={styles.btnKey}>R</span>Reject non-essential
|
|
218
|
+
</button>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CookieCli /> styles. Mirrors preview/components/cookie-cli.html
|
|
3
|
+
* exactly: terminal dark panel, IBM Plex Mono throughout, three
|
|
4
|
+
* action buttons with key glyphs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
.term {
|
|
8
|
+
--term-bg: #0A0F1A;
|
|
9
|
+
--term-fg: #C9D4E5;
|
|
10
|
+
--term-fg-dim: #5F6E86;
|
|
11
|
+
--term-cobalt: #6892D9;
|
|
12
|
+
--term-green: #5FE39A;
|
|
13
|
+
--term-border: #1A2540;
|
|
14
|
+
|
|
15
|
+
position: fixed;
|
|
16
|
+
bottom: 24px;
|
|
17
|
+
left: 50%;
|
|
18
|
+
transform: translateX(-50%);
|
|
19
|
+
z-index: 9999;
|
|
20
|
+
|
|
21
|
+
background: var(--term-bg);
|
|
22
|
+
color: var(--term-fg);
|
|
23
|
+
border: 1px solid var(--term-border);
|
|
24
|
+
border-radius: 8px;
|
|
25
|
+
box-shadow:
|
|
26
|
+
0 0 0 1px rgba(33, 70, 139, 0.12),
|
|
27
|
+
0 24px 64px -16px rgba(10, 23, 47, 0.45),
|
|
28
|
+
inset 0 0 80px rgba(0, 0, 0, 0.4);
|
|
29
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
30
|
+
font-size: 13px;
|
|
31
|
+
line-height: 1.55;
|
|
32
|
+
width: calc(100% - 48px);
|
|
33
|
+
max-width: 640px;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.bar {
|
|
38
|
+
background: #060912;
|
|
39
|
+
padding: 8px 14px;
|
|
40
|
+
border-bottom: 1px solid var(--term-border);
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: 10px;
|
|
44
|
+
font-size: 11px;
|
|
45
|
+
color: var(--term-fg-dim);
|
|
46
|
+
}
|
|
47
|
+
.dots { display: flex; gap: 6px; }
|
|
48
|
+
.dot { width: 10px; height: 10px; border-radius: 50%; }
|
|
49
|
+
.dotR { background: #FF6B7A; }
|
|
50
|
+
.dotY { background: #F7C76F; }
|
|
51
|
+
.dotG { background: var(--term-green); }
|
|
52
|
+
.title { flex: 1; text-align: center; }
|
|
53
|
+
.title b { color: var(--term-fg); }
|
|
54
|
+
|
|
55
|
+
.body { padding: 18px 20px; }
|
|
56
|
+
|
|
57
|
+
.prompt { color: var(--term-fg); margin: 6px 0; }
|
|
58
|
+
.user { color: var(--term-green); }
|
|
59
|
+
.host { color: var(--term-cobalt); }
|
|
60
|
+
.sigil { color: var(--term-fg-dim); }
|
|
61
|
+
.path { color: var(--term-fg-dim); }
|
|
62
|
+
.cmd { color: var(--term-fg); }
|
|
63
|
+
|
|
64
|
+
.comment {
|
|
65
|
+
color: var(--term-fg-dim);
|
|
66
|
+
margin: 6px 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.opts {
|
|
70
|
+
list-style: none;
|
|
71
|
+
padding: 0;
|
|
72
|
+
margin: 8px 0;
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
gap: 4px;
|
|
76
|
+
}
|
|
77
|
+
.opts li {
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 10px;
|
|
81
|
+
padding: 4px 0;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
border: none;
|
|
84
|
+
background: transparent;
|
|
85
|
+
color: var(--term-fg);
|
|
86
|
+
font-family: inherit;
|
|
87
|
+
font-size: inherit;
|
|
88
|
+
text-align: left;
|
|
89
|
+
width: 100%;
|
|
90
|
+
}
|
|
91
|
+
.opts li:hover { background: rgba(255, 255, 255, 0.02); }
|
|
92
|
+
.opts li:focus-visible {
|
|
93
|
+
outline: 1px solid var(--term-cobalt);
|
|
94
|
+
outline-offset: 2px;
|
|
95
|
+
}
|
|
96
|
+
.optKey { color: var(--term-fg-dim); }
|
|
97
|
+
.optCheck { color: var(--term-fg-dim); width: 12px; }
|
|
98
|
+
.optOn .optCheck { color: var(--term-green); }
|
|
99
|
+
.optLabel { color: var(--term-fg); flex: 1; }
|
|
100
|
+
.optTag {
|
|
101
|
+
font-size: 10px;
|
|
102
|
+
color: var(--term-fg-dim);
|
|
103
|
+
border: 1px solid var(--term-border);
|
|
104
|
+
padding: 1px 7px;
|
|
105
|
+
border-radius: 3px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.actions {
|
|
109
|
+
display: flex;
|
|
110
|
+
gap: 8px;
|
|
111
|
+
margin-top: 14px;
|
|
112
|
+
padding-top: 12px;
|
|
113
|
+
border-top: 1px solid var(--term-border);
|
|
114
|
+
flex-wrap: wrap;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.btn {
|
|
118
|
+
background: transparent;
|
|
119
|
+
color: var(--term-fg);
|
|
120
|
+
border: 1px solid var(--term-border);
|
|
121
|
+
padding: 6px 12px;
|
|
122
|
+
font-family: inherit;
|
|
123
|
+
font-size: 12px;
|
|
124
|
+
border-radius: 3px;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
display: inline-flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: 6px;
|
|
129
|
+
transition: background 120ms ease, border-color 120ms ease;
|
|
130
|
+
}
|
|
131
|
+
.btn:hover {
|
|
132
|
+
background: rgba(33, 70, 139, 0.10);
|
|
133
|
+
border-color: var(--term-cobalt);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.btnKey {
|
|
137
|
+
background: var(--term-border);
|
|
138
|
+
color: var(--term-cobalt);
|
|
139
|
+
padding: 1px 5px;
|
|
140
|
+
border-radius: 2px;
|
|
141
|
+
font-size: 10px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.btnPrimary {
|
|
145
|
+
background: var(--c-orange-knvb);
|
|
146
|
+
color: white;
|
|
147
|
+
border-color: var(--c-orange-knvb);
|
|
148
|
+
}
|
|
149
|
+
.btnPrimary:hover {
|
|
150
|
+
background: var(--c-orange-knvb);
|
|
151
|
+
border-color: white;
|
|
152
|
+
filter: brightness(1.05);
|
|
153
|
+
}
|
|
154
|
+
.btnPrimary .btnKey {
|
|
155
|
+
background: rgba(0, 0, 0, 0.2);
|
|
156
|
+
color: white;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@media (max-width: 600px) {
|
|
160
|
+
.term {
|
|
161
|
+
bottom: 12px;
|
|
162
|
+
width: calc(100% - 24px);
|
|
163
|
+
}
|
|
164
|
+
.actions { flex-direction: column; }
|
|
165
|
+
.btn { justify-content: center; }
|
|
166
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CtaBanner />
|
|
3
|
+
*
|
|
4
|
+
* Cobalt-900 marketing panel with a centred title, lede, and a
|
|
5
|
+
* primary + secondary CTA pair. Used at the bottom of marketing
|
|
6
|
+
* pages ("Ready to install?", "Pick an app. Install it. Done.").
|
|
7
|
+
*
|
|
8
|
+
* Mirrors the cta-banner section in preview/pages/landing.html and
|
|
9
|
+
* the .cta-banner mock in preview/components.html.
|
|
10
|
+
*
|
|
11
|
+
* Composition:
|
|
12
|
+
* - <Button variant="on-dark-primary" /> for the white pill
|
|
13
|
+
* - <Button variant="on-dark-secondary" /> for the bordered ghost
|
|
14
|
+
* - <ConductionBg /> for the parallax hex layer
|
|
15
|
+
*
|
|
16
|
+
* Usage in MDX:
|
|
17
|
+
*
|
|
18
|
+
* <CtaBanner
|
|
19
|
+
* title="Pick an app. Install it. Done."
|
|
20
|
+
* lede={<>Drop OpenCatalogi into your <span className="next-blue">Nextcloud</span> in two minutes.</>}
|
|
21
|
+
* primaryCta={{ label: "Install from app store", href: "/apps" }}
|
|
22
|
+
* secondaryCta={{ label: "Get a demo via a partner", href: "/partners" }}
|
|
23
|
+
* />
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import React from 'react';
|
|
27
|
+
import ConductionBg from '../ConductionBg/ConductionBg';
|
|
28
|
+
import Button from '../primitives/Button';
|
|
29
|
+
import styles from './CtaBanner.module.css';
|
|
30
|
+
|
|
31
|
+
export default function CtaBanner({
|
|
32
|
+
title,
|
|
33
|
+
lede,
|
|
34
|
+
primaryCta,
|
|
35
|
+
secondaryCta,
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<section className={styles.section}>
|
|
39
|
+
<div className={styles.banner}>
|
|
40
|
+
<ConductionBg />
|
|
41
|
+
{title && <h2 className={styles.title}>{title}</h2>}
|
|
42
|
+
{lede && <p className={styles.lede}>{lede}</p>}
|
|
43
|
+
|
|
44
|
+
{(primaryCta || secondaryCta) && (
|
|
45
|
+
<div className={styles.actions}>
|
|
46
|
+
{primaryCta && (
|
|
47
|
+
<Button variant="on-dark-primary" href={primaryCta.href || '#'}>
|
|
48
|
+
{primaryCta.label}
|
|
49
|
+
</Button>
|
|
50
|
+
)}
|
|
51
|
+
{secondaryCta && (
|
|
52
|
+
<Button variant="on-dark-secondary" href={secondaryCta.href || '#'}>
|
|
53
|
+
{secondaryCta.label}
|
|
54
|
+
</Button>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CtaBanner /> styles. Mirrors the .cta-banner section from
|
|
3
|
+
* preview/pages/landing.html exactly: cobalt-blue (NOT cobalt-900)
|
|
4
|
+
* panel inside a 1280px container, centred copy, on-dark Buttons.
|
|
5
|
+
*
|
|
6
|
+
* Button + ConductionBg carry their own styling; this module only
|
|
7
|
+
* owns the panel chrome (frame, title, lede, actions row).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.section {
|
|
11
|
+
max-width: 1280px;
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
padding: 96px 64px;
|
|
14
|
+
background: white;
|
|
15
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
16
|
+
}
|
|
17
|
+
@media (max-width: 700px) {
|
|
18
|
+
.section { padding: 64px 16px; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.banner {
|
|
22
|
+
position: relative;
|
|
23
|
+
background: var(--c-blue-cobalt);
|
|
24
|
+
color: white;
|
|
25
|
+
border-radius: var(--radius-xl);
|
|
26
|
+
padding: 72px 64px;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
text-align: center;
|
|
29
|
+
}
|
|
30
|
+
@media (max-width: 700px) {
|
|
31
|
+
.banner { padding: 48px 24px; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.title {
|
|
35
|
+
position: relative;
|
|
36
|
+
color: white;
|
|
37
|
+
font-size: 40px;
|
|
38
|
+
font-weight: 700;
|
|
39
|
+
letter-spacing: -0.02em;
|
|
40
|
+
line-height: 1.15;
|
|
41
|
+
margin: 0 0 12px;
|
|
42
|
+
}
|
|
43
|
+
@media (max-width: 700px) {
|
|
44
|
+
.title { font-size: 28px; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.lede {
|
|
48
|
+
position: relative;
|
|
49
|
+
font-size: 17px;
|
|
50
|
+
color: rgba(255, 255, 255, 0.78);
|
|
51
|
+
line-height: 1.5;
|
|
52
|
+
max-width: 560px;
|
|
53
|
+
margin: 0 auto 32px;
|
|
54
|
+
}
|
|
55
|
+
.lede :global(.next-blue) { color: var(--c-nextcloud-cyan); }
|
|
56
|
+
.lede :global(.cg-yellow) { color: var(--c-commonground-yellow); }
|
|
57
|
+
|
|
58
|
+
.actions {
|
|
59
|
+
position: relative;
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
gap: 14px;
|
|
62
|
+
align-items: center;
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <DetailHero />
|
|
3
|
+
*
|
|
4
|
+
* Hero pattern shared by app-detail, solution-page, and partner-detail
|
|
5
|
+
* pages. Two-column layout: title + supporting copy on the left,
|
|
6
|
+
* an optional illustration (typically an <AppMock>) on the right.
|
|
7
|
+
*
|
|
8
|
+
* The page-subject icon renders as a small leading hex inline with
|
|
9
|
+
* the H1 title, *not* as a giant right-side panel. That frees the
|
|
10
|
+
* right column for an illustration that shows the product itself.
|
|
11
|
+
*
|
|
12
|
+
* [crumb]
|
|
13
|
+
* [status badges]
|
|
14
|
+
* [hex] [Title] [ illustration ]
|
|
15
|
+
* tagline
|
|
16
|
+
* [primary] [secondary] [tertiary]
|
|
17
|
+
*
|
|
18
|
+
* If no illustration is passed, the hero collapses to a single
|
|
19
|
+
* column and the title row stays inline-hex + h1.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
*
|
|
23
|
+
* <DetailHero
|
|
24
|
+
* appId="mydash"
|
|
25
|
+
* crumb={[{label: 'Apps', href: '/apps'}, 'MyDash']}
|
|
26
|
+
* status={{label: 'Beta', color: 'var(--c-orange-knvb)'}}
|
|
27
|
+
* version="v0.9"
|
|
28
|
+
* locales="NL · EN"
|
|
29
|
+
* title="MyDash"
|
|
30
|
+
* tagline="..."
|
|
31
|
+
* primaryCta={{label: 'Install from app store', href: '/install'}}
|
|
32
|
+
* icon={<svg>...</svg>}
|
|
33
|
+
* iconColor="var(--c-blue-cobalt)"
|
|
34
|
+
* illustration={<AppMock app="mydash" />}
|
|
35
|
+
* />
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import React from 'react';
|
|
39
|
+
import HexBullet from '../primitives/HexBullet';
|
|
40
|
+
import Button from '../primitives/Button';
|
|
41
|
+
import {downloadsForApp, formatDownloads} from '../../data/app-downloads';
|
|
42
|
+
import styles from './DetailHero.module.css';
|
|
43
|
+
|
|
44
|
+
export default function DetailHero({
|
|
45
|
+
crumb,
|
|
46
|
+
status,
|
|
47
|
+
version,
|
|
48
|
+
locales,
|
|
49
|
+
title,
|
|
50
|
+
tagline,
|
|
51
|
+
primaryCta,
|
|
52
|
+
secondaryCta,
|
|
53
|
+
tertiaryCta,
|
|
54
|
+
icon,
|
|
55
|
+
iconColor,
|
|
56
|
+
illustration,
|
|
57
|
+
className,
|
|
58
|
+
appId,
|
|
59
|
+
downloads,
|
|
60
|
+
}) {
|
|
61
|
+
const dlCount = downloads != null ? downloads : (appId ? downloadsForApp(appId) : 0);
|
|
62
|
+
const hasIllustration = Boolean(illustration);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<section className={[styles.head, hasIllustration && styles.withIllustration, className].filter(Boolean).join(' ')}>
|
|
66
|
+
{crumb && Array.isArray(crumb) && (
|
|
67
|
+
<div className={styles.crumb}>
|
|
68
|
+
{crumb.map((c, i) => {
|
|
69
|
+
const sep = i < crumb.length - 1 ? <span className={styles.sep}>/</span> : null;
|
|
70
|
+
if (typeof c === 'string') {
|
|
71
|
+
return <React.Fragment key={i}>{c}{sep}</React.Fragment>;
|
|
72
|
+
}
|
|
73
|
+
return (
|
|
74
|
+
<React.Fragment key={i}>
|
|
75
|
+
{c.href
|
|
76
|
+
? <a href={c.href}>{c.label}</a>
|
|
77
|
+
: <span>{c.label}</span>}
|
|
78
|
+
{sep}
|
|
79
|
+
</React.Fragment>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
<div className={styles.headInner}>
|
|
86
|
+
<div className={styles.copy}>
|
|
87
|
+
{(status || version || locales || dlCount > 0) && (
|
|
88
|
+
<div className={styles.badgeRow}>
|
|
89
|
+
{status && (
|
|
90
|
+
<span className={styles.badge}>
|
|
91
|
+
<HexBullet size="md" color={status.color || 'var(--c-mint-500)'} />
|
|
92
|
+
{status.label}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
95
|
+
{version && <span className={[styles.badge, styles.versionBadge].join(' ')}>{version}</span>}
|
|
96
|
+
{locales && <span className={[styles.badge, styles.versionBadge].join(' ')}>{locales}</span>}
|
|
97
|
+
{dlCount > 0 && (
|
|
98
|
+
<span
|
|
99
|
+
className={[styles.badge, styles.downloadsBadge].join(' ')}
|
|
100
|
+
title="Total release-asset downloads from GitHub. Updated weekdays at 09:00."
|
|
101
|
+
data-app-downloads={appId || ''}
|
|
102
|
+
>
|
|
103
|
+
<svg className={styles.downloadIcon} viewBox="0 0 24 24" aria-hidden="true">
|
|
104
|
+
<path d="M12 3v12m0 0l-5-5m5 5l5-5M5 21h14"/>
|
|
105
|
+
</svg>
|
|
106
|
+
{formatDownloads(dlCount)} downloads
|
|
107
|
+
</span>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{title && (
|
|
113
|
+
<h1 className={styles.title}>
|
|
114
|
+
{icon && (
|
|
115
|
+
<span
|
|
116
|
+
className={styles.titleIcon}
|
|
117
|
+
style={iconColor ? {background: iconColor} : undefined}
|
|
118
|
+
aria-hidden="true"
|
|
119
|
+
>
|
|
120
|
+
{icon}
|
|
121
|
+
</span>
|
|
122
|
+
)}
|
|
123
|
+
<span className={styles.titleText}>{title}</span>
|
|
124
|
+
</h1>
|
|
125
|
+
)}
|
|
126
|
+
{tagline && <p className={styles.tagline}>{tagline}</p>}
|
|
127
|
+
|
|
128
|
+
{(primaryCta || secondaryCta || tertiaryCta) && (
|
|
129
|
+
<div className={styles.actions}>
|
|
130
|
+
{primaryCta && <Button variant="primary" href={primaryCta.href}>{primaryCta.label}</Button>}
|
|
131
|
+
{secondaryCta && <Button variant="secondary" href={secondaryCta.href}>{secondaryCta.label}</Button>}
|
|
132
|
+
{tertiaryCta && <Button variant="ghost" href={tertiaryCta.href}>{tertiaryCta.label} →</Button>}
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{hasIllustration && (
|
|
138
|
+
<div className={styles.illustration}>{illustration}</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
</section>
|
|
142
|
+
);
|
|
143
|
+
}
|