@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,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <PartnerCard /> + <PartnerGrid />
|
|
3
|
+
*
|
|
4
|
+
* Partner card from preview/components/partner-cards.html.
|
|
5
|
+
*
|
|
6
|
+
* Three tiers:
|
|
7
|
+
* - partner (default, white panel + cobalt-50 tier pill)
|
|
8
|
+
* - certified (white panel + gold tier pill)
|
|
9
|
+
* - strategic (cobalt-fill inverse + white tier pill)
|
|
10
|
+
*
|
|
11
|
+
* Visual structure:
|
|
12
|
+
* ┌─────────────────────────────┐
|
|
13
|
+
* │ [logo plate] TIER │
|
|
14
|
+
* │ Name │
|
|
15
|
+
* │ Summary paragraph │
|
|
16
|
+
* │ ────────── │
|
|
17
|
+
* │ ⬢ App ⬢ App ⬢ App │ ← apps-used pills
|
|
18
|
+
* └─────────────────────────────┘
|
|
19
|
+
*
|
|
20
|
+
* The companion <BecomePartner/> card slots into the trailing grid
|
|
21
|
+
* cell as the call-to-action.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
*
|
|
25
|
+
* <PartnerGrid>
|
|
26
|
+
* <PartnerCard
|
|
27
|
+
* href="/partners/yard"
|
|
28
|
+
* tier="partner"
|
|
29
|
+
* name="YARD"
|
|
30
|
+
* logo="/img/partners/yard.png"
|
|
31
|
+
* summary={<>Digital design- en development-bureau uit Utrecht...</>}
|
|
32
|
+
* apps={['MyDash', 'OpenCatalogi']}
|
|
33
|
+
* />
|
|
34
|
+
* <BecomePartner href="/partners/become" />
|
|
35
|
+
* </PartnerGrid>
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import React from 'react';
|
|
39
|
+
import HexBullet from '../primitives/HexBullet';
|
|
40
|
+
import styles from './PartnerCard.module.css';
|
|
41
|
+
|
|
42
|
+
const TIER_LABELS = {partner: 'Partner', certified: 'Certified', strategic: 'Strategic'};
|
|
43
|
+
|
|
44
|
+
export function PartnerGrid({columns = 3, children, className}) {
|
|
45
|
+
const composed = [styles.grid, styles['cols-' + columns], className].filter(Boolean).join(' ');
|
|
46
|
+
return <div className={composed}>{children}</div>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default function PartnerCard({
|
|
50
|
+
variant = 'full',
|
|
51
|
+
href,
|
|
52
|
+
tier = 'partner',
|
|
53
|
+
name,
|
|
54
|
+
logo,
|
|
55
|
+
logoAlt,
|
|
56
|
+
summary,
|
|
57
|
+
why,
|
|
58
|
+
apps = [],
|
|
59
|
+
className,
|
|
60
|
+
}) {
|
|
61
|
+
/* Compact "other" variant: small mini-avatar (44x50 hex) on the
|
|
62
|
+
left, name + why on the right. Used at the bottom of partner-
|
|
63
|
+
detail pages to point to a handful of related partners. The
|
|
64
|
+
mini-avatar gets a tier-coloured ring to echo the tier styling
|
|
65
|
+
on the full card. */
|
|
66
|
+
if (variant === 'other') {
|
|
67
|
+
const composed = [
|
|
68
|
+
styles.other,
|
|
69
|
+
styles['tier-' + tier],
|
|
70
|
+
className,
|
|
71
|
+
].filter(Boolean).join(' ');
|
|
72
|
+
const Tag = href ? 'a' : 'div';
|
|
73
|
+
return (
|
|
74
|
+
<Tag href={href} className={composed}>
|
|
75
|
+
{logo && (
|
|
76
|
+
<div className={styles.miniAvatar}>
|
|
77
|
+
<img src={logo} alt={logoAlt || name + ' logo'} />
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
<div>
|
|
81
|
+
{name && <div className={styles.otherName}>{name}</div>}
|
|
82
|
+
{(why || summary) && <div className={styles.otherWhy}>{why || summary}</div>}
|
|
83
|
+
</div>
|
|
84
|
+
</Tag>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Default: full partner card */
|
|
89
|
+
const composed = [
|
|
90
|
+
styles.card,
|
|
91
|
+
styles['tier-' + tier],
|
|
92
|
+
className,
|
|
93
|
+
].filter(Boolean).join(' ');
|
|
94
|
+
const Tag = href ? 'a' : 'div';
|
|
95
|
+
const bulletColor = tier === 'strategic' ? 'var(--c-orange-knvb)' : 'var(--c-blue-cobalt)';
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Tag href={href} className={composed}>
|
|
99
|
+
<span className={styles.tier}>{TIER_LABELS[tier] || tier}</span>
|
|
100
|
+
{logo && (
|
|
101
|
+
<div className={styles.avatar}>
|
|
102
|
+
<img src={logo} alt={logoAlt || name + ' logo'} />
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
{name && <div className={styles.name}>{name}</div>}
|
|
106
|
+
{summary && <div className={styles.summary}>{summary}</div>}
|
|
107
|
+
{apps.length > 0 && (
|
|
108
|
+
<div className={styles.apps}>
|
|
109
|
+
{apps.map((app, i) => (
|
|
110
|
+
<span key={i} className={styles.appPill}>
|
|
111
|
+
<HexBullet size="sm" color={bulletColor} />
|
|
112
|
+
{app}
|
|
113
|
+
</span>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
</Tag>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function BecomePartner({href, eyebrow = 'Become a partner', title, body, ctaLabel}) {
|
|
122
|
+
return (
|
|
123
|
+
<a href={href} className={[styles.card, styles.become].join(' ')}>
|
|
124
|
+
<div className={styles.becomeEyebrow}>{eyebrow}</div>
|
|
125
|
+
{title && <h4 className={styles.becomeTitle}>{title}</h4>}
|
|
126
|
+
{body && <p className={styles.becomeBody}>{body}</p>}
|
|
127
|
+
{ctaLabel && <span className={styles.becomeArrow}>{ctaLabel} →</span>}
|
|
128
|
+
</a>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <PartnerCard /> styles. Mirrors partner-cards.css exactly.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.grid {
|
|
6
|
+
display: grid;
|
|
7
|
+
gap: 24px;
|
|
8
|
+
align-items: stretch;
|
|
9
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
10
|
+
}
|
|
11
|
+
.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
|
12
|
+
.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
|
13
|
+
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
|
|
14
|
+
|
|
15
|
+
.card {
|
|
16
|
+
background: white;
|
|
17
|
+
border: 1px solid var(--c-cobalt-100);
|
|
18
|
+
border-radius: var(--radius-lg);
|
|
19
|
+
padding: 28px;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
gap: 16px;
|
|
23
|
+
box-shadow: var(--shadow-1);
|
|
24
|
+
text-decoration: none;
|
|
25
|
+
color: inherit;
|
|
26
|
+
transition: all 160ms ease;
|
|
27
|
+
position: relative;
|
|
28
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
29
|
+
}
|
|
30
|
+
.card:hover {
|
|
31
|
+
transform: translateY(-2px);
|
|
32
|
+
box-shadow: var(--shadow-2);
|
|
33
|
+
text-decoration: none;
|
|
34
|
+
color: inherit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.tier {
|
|
38
|
+
position: absolute;
|
|
39
|
+
top: 16px;
|
|
40
|
+
right: 16px;
|
|
41
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
42
|
+
font-size: 10px;
|
|
43
|
+
letter-spacing: 0.1em;
|
|
44
|
+
text-transform: uppercase;
|
|
45
|
+
padding: 4px 9px;
|
|
46
|
+
border-radius: var(--radius-pill);
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
background: var(--c-cobalt-50);
|
|
49
|
+
color: var(--c-cobalt-700);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.tier-certified .tier { background: var(--c-gold-500); color: var(--c-cobalt-900); }
|
|
53
|
+
.tier-strategic { background: var(--c-blue-cobalt); color: white; border-color: var(--c-blue-cobalt); }
|
|
54
|
+
.tier-strategic .tier { background: white; color: var(--c-blue-cobalt); }
|
|
55
|
+
|
|
56
|
+
.avatar {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
align-self: flex-start;
|
|
60
|
+
background: white;
|
|
61
|
+
padding: 8px 12px;
|
|
62
|
+
border-radius: var(--radius-sm);
|
|
63
|
+
height: 36px;
|
|
64
|
+
box-sizing: content-box;
|
|
65
|
+
}
|
|
66
|
+
.avatar img {
|
|
67
|
+
max-height: 36px;
|
|
68
|
+
max-width: 140px;
|
|
69
|
+
width: auto;
|
|
70
|
+
height: auto;
|
|
71
|
+
object-fit: contain;
|
|
72
|
+
display: block;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.name { font-size: 18px; font-weight: 700; color: var(--c-cobalt-900); }
|
|
76
|
+
.tier-strategic .name { color: white; }
|
|
77
|
+
|
|
78
|
+
.summary { font-size: 14px; line-height: 1.55; color: var(--c-cobalt-700); }
|
|
79
|
+
.summary :global(.next-blue) { color: var(--c-nextcloud-blue); }
|
|
80
|
+
.tier-strategic .summary { color: rgba(255, 255, 255, 0.78); }
|
|
81
|
+
.tier-strategic .summary :global(.next-blue) { color: var(--c-nextcloud-cyan); }
|
|
82
|
+
|
|
83
|
+
.apps {
|
|
84
|
+
margin-top: auto;
|
|
85
|
+
padding-top: 16px;
|
|
86
|
+
border-top: 1px solid var(--c-cobalt-100);
|
|
87
|
+
display: flex;
|
|
88
|
+
gap: 6px;
|
|
89
|
+
flex-wrap: wrap;
|
|
90
|
+
}
|
|
91
|
+
.tier-strategic .apps { border-top-color: rgba(255, 255, 255, 0.18); }
|
|
92
|
+
|
|
93
|
+
.appPill {
|
|
94
|
+
display: inline-flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: 6px;
|
|
97
|
+
background: var(--c-cobalt-50);
|
|
98
|
+
color: var(--c-cobalt-700);
|
|
99
|
+
padding: 4px 10px;
|
|
100
|
+
border-radius: var(--radius-pill);
|
|
101
|
+
font-size: 12px;
|
|
102
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
103
|
+
}
|
|
104
|
+
.tier-strategic .appPill { background: rgba(255, 255, 255, 0.12); color: white; }
|
|
105
|
+
|
|
106
|
+
/* Become-a-partner CTA tile */
|
|
107
|
+
.become {
|
|
108
|
+
background: var(--c-cobalt-50);
|
|
109
|
+
border: 1px dashed var(--c-cobalt-300);
|
|
110
|
+
box-shadow: none;
|
|
111
|
+
align-items: flex-start;
|
|
112
|
+
justify-content: center;
|
|
113
|
+
padding: 32px;
|
|
114
|
+
}
|
|
115
|
+
.become:hover { border-color: var(--c-blue-cobalt); transform: none; box-shadow: var(--shadow-1); }
|
|
116
|
+
|
|
117
|
+
.becomeEyebrow {
|
|
118
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
119
|
+
font-size: 11px;
|
|
120
|
+
letter-spacing: 0.12em;
|
|
121
|
+
text-transform: uppercase;
|
|
122
|
+
color: var(--c-cobalt-400);
|
|
123
|
+
margin-bottom: 8px;
|
|
124
|
+
}
|
|
125
|
+
.becomeTitle {
|
|
126
|
+
font-size: 22px;
|
|
127
|
+
font-weight: 700;
|
|
128
|
+
letter-spacing: -0.01em;
|
|
129
|
+
margin: 0 0 6px;
|
|
130
|
+
color: var(--c-cobalt-900);
|
|
131
|
+
}
|
|
132
|
+
.becomeBody {
|
|
133
|
+
font-size: 14px;
|
|
134
|
+
color: var(--c-cobalt-700);
|
|
135
|
+
line-height: 1.55;
|
|
136
|
+
margin: 0 0 14px;
|
|
137
|
+
max-width: 56ch;
|
|
138
|
+
}
|
|
139
|
+
.becomeArrow {
|
|
140
|
+
color: var(--c-blue-cobalt);
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
font-size: 14px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Compact "other" variant. Used at the bottom of partner-detail
|
|
146
|
+
pages ("Adjacent specialists") and anywhere else a small dense
|
|
147
|
+
reference to a partner makes sense. Mini-avatar on the left,
|
|
148
|
+
name + why on the right; tier-coloured ring echoes the full
|
|
149
|
+
card's tier styling. */
|
|
150
|
+
.other {
|
|
151
|
+
background: white;
|
|
152
|
+
border: 1px solid var(--c-cobalt-100);
|
|
153
|
+
border-radius: var(--radius-lg);
|
|
154
|
+
padding: 20px;
|
|
155
|
+
text-decoration: none;
|
|
156
|
+
color: inherit;
|
|
157
|
+
display: flex;
|
|
158
|
+
gap: 14px;
|
|
159
|
+
align-items: flex-start;
|
|
160
|
+
transition: all 160ms ease;
|
|
161
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
162
|
+
}
|
|
163
|
+
.other:hover {
|
|
164
|
+
border-color: var(--c-blue-cobalt);
|
|
165
|
+
transform: translateY(-1px);
|
|
166
|
+
text-decoration: none;
|
|
167
|
+
color: inherit;
|
|
168
|
+
}
|
|
169
|
+
.miniAvatar {
|
|
170
|
+
width: 44px;
|
|
171
|
+
height: 50px;
|
|
172
|
+
clip-path: var(--hex-pointy-top);
|
|
173
|
+
background: white;
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
flex-shrink: 0;
|
|
178
|
+
overflow: hidden;
|
|
179
|
+
}
|
|
180
|
+
.miniAvatar img {
|
|
181
|
+
max-width: 28px;
|
|
182
|
+
max-height: 28px;
|
|
183
|
+
object-fit: contain;
|
|
184
|
+
}
|
|
185
|
+
.other.tier-certified .miniAvatar { box-shadow: inset 0 0 0 2px var(--c-gold-500); }
|
|
186
|
+
.other.tier-strategic .miniAvatar { box-shadow: inset 0 0 0 2px var(--c-orange-knvb); }
|
|
187
|
+
|
|
188
|
+
.otherName {
|
|
189
|
+
font-size: 15px;
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
color: var(--c-cobalt-900);
|
|
192
|
+
margin-bottom: 4px;
|
|
193
|
+
}
|
|
194
|
+
.otherWhy {
|
|
195
|
+
font-size: 13px;
|
|
196
|
+
color: var(--c-cobalt-700);
|
|
197
|
+
line-height: 1.5;
|
|
198
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <PartnerDirectory />
|
|
3
|
+
*
|
|
4
|
+
* Filterable partner directory: composes <FacetedFilters/> (sidebar),
|
|
5
|
+
* <PartnerGrid/>, <PartnerCard/>, and an optional <BecomePartner/> CTA.
|
|
6
|
+
*
|
|
7
|
+
* Facets are auto-derived from the partners array:
|
|
8
|
+
* - tier: Strategic / Certified / Partner (only shown when ≥1 partner)
|
|
9
|
+
* - app: union of every partners[].apps[] value, sorted by count
|
|
10
|
+
*
|
|
11
|
+
* Filter logic: a partner is shown when it matches every active facet.
|
|
12
|
+
* Within a facet, multiple selections are OR-ed (any-of). Across
|
|
13
|
+
* facets, selections are AND-ed (all-of).
|
|
14
|
+
*
|
|
15
|
+
* Usage in MDX:
|
|
16
|
+
*
|
|
17
|
+
* <PartnerDirectory
|
|
18
|
+
* partners={[
|
|
19
|
+
* {href: '/partners/acato', tier: 'strategic', name: 'Acato',
|
|
20
|
+
* logo: '/img/partners/acato.png',
|
|
21
|
+
* summary: <>Nederlandse open-source specialist…</>,
|
|
22
|
+
* apps: ['OpenRegister', 'OpenCatalogi', 'DocuDesk']},
|
|
23
|
+
* …
|
|
24
|
+
* ]}
|
|
25
|
+
* becomePartner={{
|
|
26
|
+
* href: '#become-a-partner',
|
|
27
|
+
* title: 'Ship Conduction to your customers.',
|
|
28
|
+
* body: 'Join as Partner, Certified, or Strategic.',
|
|
29
|
+
* ctaLabel: 'Apply below',
|
|
30
|
+
* }}
|
|
31
|
+
* />
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import React, {useState, useMemo} from 'react';
|
|
35
|
+
import FacetedFilters from '../FacetedFilters/FacetedFilters';
|
|
36
|
+
import PartnerCard, {PartnerGrid, BecomePartner} from '../PartnerCard/PartnerCard';
|
|
37
|
+
import styles from './PartnerDirectory.module.css';
|
|
38
|
+
|
|
39
|
+
const TIER_LABELS = {partner: 'Partner', certified: 'Certified', strategic: 'Strategic'};
|
|
40
|
+
const TIER_ORDER = ['strategic', 'certified', 'partner'];
|
|
41
|
+
|
|
42
|
+
function deriveFacets(partners) {
|
|
43
|
+
const tierItems = TIER_ORDER
|
|
44
|
+
.map(value => ({
|
|
45
|
+
value,
|
|
46
|
+
label: TIER_LABELS[value],
|
|
47
|
+
count: partners.filter(p => p.tier === value).length,
|
|
48
|
+
}))
|
|
49
|
+
.filter(item => item.count > 0);
|
|
50
|
+
|
|
51
|
+
const appCounts = {};
|
|
52
|
+
for (const p of partners) {
|
|
53
|
+
for (const a of p.apps || []) {
|
|
54
|
+
appCounts[a] = (appCounts[a] || 0) + 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const appItems = Object.entries(appCounts)
|
|
58
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
59
|
+
.map(([value, count]) => ({value, label: value, count}));
|
|
60
|
+
|
|
61
|
+
const facets = [];
|
|
62
|
+
if (tierItems.length > 0) facets.push({key: 'tier', label: 'Partner tier', items: tierItems});
|
|
63
|
+
if (appItems.length > 0) facets.push({key: 'app', label: 'Apps & services', items: appItems});
|
|
64
|
+
return facets;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function partnerMatches(partner, selected) {
|
|
68
|
+
const tierFilter = selected.tier || [];
|
|
69
|
+
if (tierFilter.length > 0 && !tierFilter.includes(partner.tier)) return false;
|
|
70
|
+
|
|
71
|
+
const appFilter = selected.app || [];
|
|
72
|
+
if (appFilter.length > 0) {
|
|
73
|
+
const partnerApps = partner.apps || [];
|
|
74
|
+
if (!appFilter.some(a => partnerApps.includes(a))) return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default function PartnerDirectory({
|
|
81
|
+
partners = [],
|
|
82
|
+
becomePartner,
|
|
83
|
+
gridColumns = 2,
|
|
84
|
+
emptyState = 'No partners match the current filters.',
|
|
85
|
+
className,
|
|
86
|
+
}) {
|
|
87
|
+
const [selected, setSelected] = useState({});
|
|
88
|
+
const facets = useMemo(() => deriveFacets(partners), [partners]);
|
|
89
|
+
const filtered = useMemo(
|
|
90
|
+
() => partners.filter(p => partnerMatches(p, selected)),
|
|
91
|
+
[partners, selected],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className={[styles.layout, className].filter(Boolean).join(' ')}>
|
|
96
|
+
<FacetedFilters
|
|
97
|
+
facets={facets}
|
|
98
|
+
selected={selected}
|
|
99
|
+
onChange={setSelected}
|
|
100
|
+
/>
|
|
101
|
+
<div className={styles.results}>
|
|
102
|
+
{filtered.length > 0 ? (
|
|
103
|
+
<PartnerGrid columns={gridColumns}>
|
|
104
|
+
{filtered.map((p, i) => (
|
|
105
|
+
<PartnerCard key={p.href || p.name || i} {...p} />
|
|
106
|
+
))}
|
|
107
|
+
{becomePartner && <BecomePartner {...becomePartner} />}
|
|
108
|
+
</PartnerGrid>
|
|
109
|
+
) : (
|
|
110
|
+
<>
|
|
111
|
+
<p className={styles.empty}>{emptyState}</p>
|
|
112
|
+
{becomePartner && (
|
|
113
|
+
<PartnerGrid columns={gridColumns}>
|
|
114
|
+
<BecomePartner {...becomePartner} />
|
|
115
|
+
</PartnerGrid>
|
|
116
|
+
)}
|
|
117
|
+
</>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.layout {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-columns: 240px 1fr;
|
|
4
|
+
gap: 56px;
|
|
5
|
+
align-items: start;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.results {
|
|
9
|
+
min-width: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.empty {
|
|
13
|
+
font-size: 16px;
|
|
14
|
+
color: var(--c-cobalt-700);
|
|
15
|
+
text-align: center;
|
|
16
|
+
padding: 64px 0 32px;
|
|
17
|
+
margin: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (max-width: 900px) {
|
|
21
|
+
.layout {
|
|
22
|
+
grid-template-columns: 1fr;
|
|
23
|
+
gap: 32px;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <PartnerSidecard />
|
|
3
|
+
*
|
|
4
|
+
* Narrow info panel for the partner detail page right column. Three
|
|
5
|
+
* stacked sections:
|
|
6
|
+
* 1. Tier badge. Pulls the same colour treatment as <PartnerCard>
|
|
7
|
+
* (Partner = cobalt-50 pill, Certified = gold pill
|
|
8
|
+
* with a small gold Conduction avatar to read as a
|
|
9
|
+
* Conduction-issued credential, Strategic = white
|
|
10
|
+
* pill on a cobalt-fill card).
|
|
11
|
+
* 2. Apps supported. Chip row from partner.apps.
|
|
12
|
+
* 3. Solutions shipped. Chip row from solutionsBySlugs(partner.solutions),
|
|
13
|
+
* each linking to its /solutions/<slug> page.
|
|
14
|
+
*
|
|
15
|
+
* Optional `cta` renders a primary button at the bottom of the card —
|
|
16
|
+
* usually the same "Talk to {partner}" target as the hero's primary CTA,
|
|
17
|
+
* so the rail is self-sufficient when the user scrolls past the hero.
|
|
18
|
+
*
|
|
19
|
+
* The card itself doesn't position-stick. The consuming page wraps a
|
|
20
|
+
* 2-col layout (main + rail) and applies the sticky behaviour to the
|
|
21
|
+
* column wrapper, so the rail starts after the hero and trails the
|
|
22
|
+
* scrolling body content.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
*
|
|
26
|
+
* import {PARTNERS} from '@site/src/data/partners-catalog';
|
|
27
|
+
* import {solutionsBySlugs} from '@site/src/data/solutions-catalog';
|
|
28
|
+
*
|
|
29
|
+
* const partner = PARTNERS.find(p => p.name === 'Acato');
|
|
30
|
+
* const solutions = solutionsBySlugs(partner.solutions);
|
|
31
|
+
*
|
|
32
|
+
* <PartnerSidecard
|
|
33
|
+
* partner={partner}
|
|
34
|
+
* solutions={solutions}
|
|
35
|
+
* cta={{label: 'Talk to Acato', href: 'mailto:hello@acato.nl'}}
|
|
36
|
+
* />
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import React from 'react';
|
|
40
|
+
import styles from './PartnerSidecard.module.css';
|
|
41
|
+
|
|
42
|
+
const TIER_LABELS = {
|
|
43
|
+
partner: 'Partner',
|
|
44
|
+
certified: 'Certified',
|
|
45
|
+
strategic: 'Strategic',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const TIER_BLURB = {
|
|
49
|
+
partner:
|
|
50
|
+
'Partner-tier teams have shipped Conduction in production at one or two clients. Listed in the directory, on the community Slack, joining quarterly office hours.',
|
|
51
|
+
certified:
|
|
52
|
+
'Certified per app by Conduction. Joint go-to-market on tenders and a higher SLA on shared customers. Carries the Conduction credential.',
|
|
53
|
+
strategic:
|
|
54
|
+
'Strategic partner with shared roadmap commitments and joint engineering. By invitation. Co-sale on government tenders, co-branded hosting offerings.',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default function PartnerSidecard({
|
|
58
|
+
partner,
|
|
59
|
+
solutions = [],
|
|
60
|
+
cta,
|
|
61
|
+
className,
|
|
62
|
+
}) {
|
|
63
|
+
if (!partner) return null;
|
|
64
|
+
const tier = partner.tier || 'partner';
|
|
65
|
+
const tierLabel = TIER_LABELS[tier] || tier;
|
|
66
|
+
const composed = [styles.rail, styles[`tier-${tier}`], className].filter(Boolean).join(' ');
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<aside className={composed}>
|
|
70
|
+
<div className={styles.tierCard}>
|
|
71
|
+
{tier === 'certified' && (
|
|
72
|
+
<img
|
|
73
|
+
className={styles.tierBadge}
|
|
74
|
+
src="/img/brand/avatar-conduction-gold-on-white.svg"
|
|
75
|
+
alt="Conduction-certified mark"
|
|
76
|
+
width="44"
|
|
77
|
+
height="44"
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
80
|
+
<div className={styles.tierLabel}>{tier === 'partner' ? 'Partner' : `${tierLabel} partner`}</div>
|
|
81
|
+
<p className={styles.tierBlurb}>{TIER_BLURB[tier]}</p>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{partner.apps && partner.apps.length > 0 && (
|
|
85
|
+
<div className={styles.section}>
|
|
86
|
+
<h4 className={styles.sectionTitle}>Apps they ship</h4>
|
|
87
|
+
<div className={styles.chipRow}>
|
|
88
|
+
{partner.apps.map((app) => (
|
|
89
|
+
<span key={app} className={styles.chip}>{app}</span>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{solutions.length > 0 && (
|
|
96
|
+
<div className={styles.section}>
|
|
97
|
+
<h4 className={styles.sectionTitle}>Solutions they deliver</h4>
|
|
98
|
+
<div className={styles.chipColumn}>
|
|
99
|
+
{solutions.map((s) => (
|
|
100
|
+
<a key={s.slug} href={s.href} className={styles.solutionLink}>
|
|
101
|
+
<span className={styles.solutionIcon} aria-hidden="true">{s.icon}</span>
|
|
102
|
+
<span className={styles.solutionTitle}>{s.shortTitle || s.title}</span>
|
|
103
|
+
</a>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{cta && (
|
|
110
|
+
<a href={cta.href} className={styles.cta}>
|
|
111
|
+
{cta.label}
|
|
112
|
+
</a>
|
|
113
|
+
)}
|
|
114
|
+
</aside>
|
|
115
|
+
);
|
|
116
|
+
}
|