@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,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <Hero />
|
|
3
|
+
*
|
|
4
|
+
* Two-column landing hero, mirrors preview/pages/landing.html exactly.
|
|
5
|
+
* Left column: optional eyebrow, headline, lede paragraph, action
|
|
6
|
+
* cluster (primary + secondary + ghost + optional meta). Right column:
|
|
7
|
+
* children (typically <div className="hex-rain"/>, a cn-platform
|
|
8
|
+
* diagram, or any visual that fills the row).
|
|
9
|
+
*
|
|
10
|
+
* Each CTA has the shape { label, href }. The ghost CTA renders as a
|
|
11
|
+
* plain link with an arrow; the meta block is a vertical "headline +
|
|
12
|
+
* subhead" caption that sits inline with the actions.
|
|
13
|
+
*
|
|
14
|
+
* Composes from primitives:
|
|
15
|
+
* <Eyebrow/> for the orange-bullet caption
|
|
16
|
+
* <Button/> for the three action variants (primary/secondary/ghost)
|
|
17
|
+
*
|
|
18
|
+
* Usage in MDX:
|
|
19
|
+
*
|
|
20
|
+
* <Hero
|
|
21
|
+
* eyebrow={<>Twelve open-source apps · one <span className="next-blue">Nextcloud</span></>}
|
|
22
|
+
* title={<>Install your stack.<br/>In two minutes.</>}
|
|
23
|
+
* lede={<>Twelve open-source apps that plug into <span className="next-blue">Nextcloud</span>...</>}
|
|
24
|
+
* primaryCta={{label: "Install from Nextcloud app store", href: "/apps"}}
|
|
25
|
+
* secondaryCta={{label: "Get a demo via a partner", href: "/partners"}}
|
|
26
|
+
* tertiaryCta={{label: "View on GitHub", href: "https://github.com/..."}}
|
|
27
|
+
* >
|
|
28
|
+
* <div className="hex-rain" aria-label="..."></div>
|
|
29
|
+
* </Hero>
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import React, {useEffect} from 'react';
|
|
33
|
+
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
34
|
+
import Eyebrow from '../primitives/Eyebrow';
|
|
35
|
+
import Button from '../primitives/Button';
|
|
36
|
+
import styles from './Hero.module.css';
|
|
37
|
+
|
|
38
|
+
export default function Hero({
|
|
39
|
+
eyebrow,
|
|
40
|
+
title,
|
|
41
|
+
lede,
|
|
42
|
+
primaryCta,
|
|
43
|
+
secondaryCta,
|
|
44
|
+
tertiaryCta,
|
|
45
|
+
meta,
|
|
46
|
+
children,
|
|
47
|
+
}) {
|
|
48
|
+
const isBrowser = useIsBrowser();
|
|
49
|
+
|
|
50
|
+
/* Lazy-register the diagrams web components on the client so any
|
|
51
|
+
<cn-*> elements passed as children render. The import is no-op
|
|
52
|
+
once registered (customElements.define guards). */
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (isBrowser) {
|
|
55
|
+
import('../../diagrams/index.js').catch(() => {
|
|
56
|
+
/* Defensive: dynamic import should always succeed since the
|
|
57
|
+
diagrams runtime ships in the same package. */
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}, [isBrowser]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<section className={styles.hero}>
|
|
64
|
+
<div className={styles.copy}>
|
|
65
|
+
{eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
|
|
66
|
+
{title && <h1 className={styles.title}>{title}</h1>}
|
|
67
|
+
{lede && <p className={styles.lede}>{lede}</p>}
|
|
68
|
+
|
|
69
|
+
{(primaryCta || secondaryCta || tertiaryCta || meta) && (
|
|
70
|
+
<div className={styles.actions}>
|
|
71
|
+
{primaryCta && (
|
|
72
|
+
<Button variant="primary" href={primaryCta.href || '#'}>
|
|
73
|
+
{primaryCta.label}
|
|
74
|
+
</Button>
|
|
75
|
+
)}
|
|
76
|
+
{secondaryCta && (
|
|
77
|
+
<Button variant="secondary" href={secondaryCta.href || '#'}>
|
|
78
|
+
{secondaryCta.label}
|
|
79
|
+
</Button>
|
|
80
|
+
)}
|
|
81
|
+
{tertiaryCta && (
|
|
82
|
+
<Button variant="ghost" href={tertiaryCta.href || '#'}>
|
|
83
|
+
{tertiaryCta.label} →
|
|
84
|
+
</Button>
|
|
85
|
+
)}
|
|
86
|
+
{meta && (
|
|
87
|
+
<div className={styles.meta}>
|
|
88
|
+
{meta.primary && <strong>{meta.primary}</strong>}
|
|
89
|
+
{meta.secondary && <span>{meta.secondary}</span>}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className={styles.visual}>
|
|
97
|
+
{children}
|
|
98
|
+
</div>
|
|
99
|
+
</section>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <Hero /> styles. Mirrors preview/pages/landing.html .hero section
|
|
3
|
+
* exactly: 1.1fr/1fr grid inside a 1280px max-width container, 72px
|
|
4
|
+
* headline, lede 21px, three actions (primary + secondary + ghost),
|
|
5
|
+
* right column stretches to fill so .hex-rain can occupy 520px+ tall.
|
|
6
|
+
*
|
|
7
|
+
* Eyebrow + Button styles live in the primitive modules now; this
|
|
8
|
+
* module only owns the hero-specific layout (grid, title, lede,
|
|
9
|
+
* actions row, meta block, visual slot).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
.hero {
|
|
13
|
+
padding: 0 64px;
|
|
14
|
+
max-width: 1280px;
|
|
15
|
+
margin: 0 auto;
|
|
16
|
+
display: grid;
|
|
17
|
+
grid-template-columns: 1.1fr 1fr;
|
|
18
|
+
gap: 72px;
|
|
19
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@media (max-width: 900px) {
|
|
23
|
+
.hero {
|
|
24
|
+
grid-template-columns: 1fr;
|
|
25
|
+
padding: 0 24px;
|
|
26
|
+
gap: 32px;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.copy {
|
|
31
|
+
padding-block: 96px;
|
|
32
|
+
align-self: center;
|
|
33
|
+
}
|
|
34
|
+
@media (max-width: 900px) {
|
|
35
|
+
.copy { padding-block: 56px; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.title {
|
|
39
|
+
font-size: 72px;
|
|
40
|
+
font-weight: 700;
|
|
41
|
+
letter-spacing: -0.025em;
|
|
42
|
+
line-height: 1.02;
|
|
43
|
+
margin: 0 0 24px;
|
|
44
|
+
color: var(--c-cobalt-900);
|
|
45
|
+
}
|
|
46
|
+
@media (max-width: 1100px) { .title { font-size: 56px; } }
|
|
47
|
+
@media (max-width: 700px) { .title { font-size: 44px; } }
|
|
48
|
+
|
|
49
|
+
.title :global(.next-blue) { color: var(--c-nextcloud-blue); }
|
|
50
|
+
.title :global(.cg-yellow) { color: var(--c-commonground-yellow); }
|
|
51
|
+
|
|
52
|
+
.lede {
|
|
53
|
+
font-size: 21px;
|
|
54
|
+
color: var(--c-cobalt-700);
|
|
55
|
+
line-height: 1.5;
|
|
56
|
+
max-width: 52ch;
|
|
57
|
+
margin: 0 0 32px;
|
|
58
|
+
}
|
|
59
|
+
@media (max-width: 700px) { .lede { font-size: 17px; } }
|
|
60
|
+
.lede :global(.next-blue) { color: var(--c-nextcloud-blue); }
|
|
61
|
+
.lede :global(.cg-yellow) { color: var(--c-commonground-yellow); }
|
|
62
|
+
|
|
63
|
+
.actions {
|
|
64
|
+
display: flex;
|
|
65
|
+
gap: 14px;
|
|
66
|
+
align-items: center;
|
|
67
|
+
flex-wrap: wrap;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.meta {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
gap: 2px;
|
|
74
|
+
font-size: 13px;
|
|
75
|
+
color: var(--c-cobalt-400);
|
|
76
|
+
margin-left: 8px;
|
|
77
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
78
|
+
}
|
|
79
|
+
.meta strong {
|
|
80
|
+
color: var(--c-cobalt-700);
|
|
81
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
82
|
+
font-size: 14px;
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Visual column, default 'stretch' so children with min-height
|
|
87
|
+
(e.g. .hex-rain at 520px) fill the row evenly with the copy column. */
|
|
88
|
+
.visual {
|
|
89
|
+
position: relative;
|
|
90
|
+
min-height: 0;
|
|
91
|
+
}
|
|
92
|
+
.visual > * {
|
|
93
|
+
width: 100%;
|
|
94
|
+
height: 100%;
|
|
95
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <HexBackground />
|
|
3
|
+
*
|
|
4
|
+
* Brand-recognisable abstract backdrop. A large pointy-top hex (or two
|
|
5
|
+
* stacked) sitting behind the content, sized so a portion of the hex
|
|
6
|
+
* crests the visible bounds — the upper two shoulder corners poke into
|
|
7
|
+
* the layout, the rest fills the panel as a colour wash. The component
|
|
8
|
+
* is just chrome; it expects to be a position:absolute child inside a
|
|
9
|
+
* relatively-positioned parent that owns the actual content above it.
|
|
10
|
+
*
|
|
11
|
+
* Used as the wash behind:
|
|
12
|
+
* - <Showcase> right panel (cobalt-100 hex behind the AppMock)
|
|
13
|
+
* - <RotatingCards> coloured card image-side
|
|
14
|
+
* - any future "abstract product mock on a panel" layout
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
*
|
|
18
|
+
* <div style={{position: 'relative'}}>
|
|
19
|
+
* <HexBackground tone="cobalt-100" position="top" />
|
|
20
|
+
* <div style={{position: 'relative', zIndex: 1}}>... content ...</div>
|
|
21
|
+
* </div>
|
|
22
|
+
*
|
|
23
|
+
* Props:
|
|
24
|
+
* - tone: 'cobalt-100' (default) | 'cobalt-50' | 'mint-100' |
|
|
25
|
+
* 'lavender-100' | 'forest-100' | 'terracotta-100' |
|
|
26
|
+
* 'coral-100' | 'cobalt-700' | 'workspace-100'
|
|
27
|
+
* - position: 'top' (default; hex crests the top edge) |
|
|
28
|
+
* 'centre' | 'bottom-right' | 'left'
|
|
29
|
+
* - size: 'md' (default) | 'lg' (oversized) | 'sm'
|
|
30
|
+
* - density: 1 (default) | 2 (offset twin hex behind, +depth)
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import React from 'react';
|
|
34
|
+
import styles from './HexBackground.module.css';
|
|
35
|
+
|
|
36
|
+
export default function HexBackground({
|
|
37
|
+
tone = 'cobalt-100',
|
|
38
|
+
position = 'top',
|
|
39
|
+
size = 'md',
|
|
40
|
+
density = 1,
|
|
41
|
+
className,
|
|
42
|
+
}) {
|
|
43
|
+
const composed = [
|
|
44
|
+
styles.bg,
|
|
45
|
+
styles[`tone-${tone}`],
|
|
46
|
+
styles[`pos-${position}`],
|
|
47
|
+
styles[`size-${size}`],
|
|
48
|
+
className,
|
|
49
|
+
].filter(Boolean).join(' ');
|
|
50
|
+
return (
|
|
51
|
+
<span className={composed} aria-hidden="true">
|
|
52
|
+
<span className={styles.hexA}></span>
|
|
53
|
+
{density >= 2 && <span className={styles.hexB}></span>}
|
|
54
|
+
</span>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <HexBackground /> styles.
|
|
3
|
+
*
|
|
4
|
+
* The component lays out as an absolute-positioned span filling its
|
|
5
|
+
* relatively-positioned parent, carrying one (or two) clip-path hexes
|
|
6
|
+
* positioned so a corner crests the parent's visible bounds.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
.bg {
|
|
10
|
+
position: absolute;
|
|
11
|
+
inset: 0;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
pointer-events: none;
|
|
14
|
+
z-index: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.hexA, .hexB {
|
|
18
|
+
position: absolute;
|
|
19
|
+
clip-path: var(--hex-pointy-top);
|
|
20
|
+
display: block;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ---------- Tones ---------- */
|
|
24
|
+
.tone-cobalt-100 .hexA { background: var(--c-cobalt-100); }
|
|
25
|
+
.tone-cobalt-50 .hexA { background: var(--c-cobalt-50); }
|
|
26
|
+
.tone-cobalt-700 .hexA { background: var(--c-cobalt-700); }
|
|
27
|
+
.tone-mint-100 .hexA { background: var(--c-mint-100); }
|
|
28
|
+
.tone-lavender-100 .hexA { background: var(--c-lavender-100); }
|
|
29
|
+
.tone-forest-100 .hexA { background: var(--c-forest-100); }
|
|
30
|
+
.tone-terracotta-100 .hexA { background: var(--c-terracotta-100); }
|
|
31
|
+
.tone-coral-100 .hexA { background: var(--c-coral-100); }
|
|
32
|
+
.tone-workspace-100 .hexA { background: var(--c-workspace-100); }
|
|
33
|
+
|
|
34
|
+
/* The second hex (density=2) carries a darker tone of the same family
|
|
35
|
+
for layered depth. */
|
|
36
|
+
.tone-cobalt-100 .hexB { background: var(--c-cobalt-200); }
|
|
37
|
+
.tone-cobalt-50 .hexB { background: var(--c-cobalt-100); }
|
|
38
|
+
.tone-cobalt-700 .hexB { background: var(--c-cobalt-900); }
|
|
39
|
+
.tone-mint-100 .hexB { background: var(--c-mint-300); }
|
|
40
|
+
.tone-lavender-100 .hexB { background: var(--c-lavender-300); }
|
|
41
|
+
.tone-forest-100 .hexB { background: var(--c-forest-300); }
|
|
42
|
+
.tone-terracotta-100 .hexB { background: var(--c-terracotta-300); }
|
|
43
|
+
.tone-coral-100 .hexB { background: var(--c-coral-300); }
|
|
44
|
+
.tone-workspace-100 .hexB { background: var(--c-workspace-300); }
|
|
45
|
+
|
|
46
|
+
/* ---------- Sizes ---------- */
|
|
47
|
+
.size-md .hexA { width: 130%; aspect-ratio: 1 / 1.155; }
|
|
48
|
+
.size-md .hexB { width: 90%; aspect-ratio: 1 / 1.155; }
|
|
49
|
+
|
|
50
|
+
.size-lg .hexA { width: 170%; aspect-ratio: 1 / 1.155; }
|
|
51
|
+
.size-lg .hexB { width: 120%; aspect-ratio: 1 / 1.155; }
|
|
52
|
+
|
|
53
|
+
.size-sm .hexA { width: 95%; aspect-ratio: 1 / 1.155; }
|
|
54
|
+
.size-sm .hexB { width: 60%; aspect-ratio: 1 / 1.155; }
|
|
55
|
+
|
|
56
|
+
/* ---------- Positions — anchor where the hex sits relative to the parent ---------- */
|
|
57
|
+
|
|
58
|
+
/* top: hex centred horizontally, top edge above the parent so the
|
|
59
|
+
hex's TOP point and SHOULDER corners crest into the visible area. */
|
|
60
|
+
.pos-top .hexA { top: -22%; left: 50%; transform: translateX(-50%); }
|
|
61
|
+
.pos-top .hexB { top: -8%; left: 18%; transform: translateX(-50%); opacity: 0.4; }
|
|
62
|
+
|
|
63
|
+
/* centre: the entire hex visible, centred. */
|
|
64
|
+
.pos-centre .hexA { top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
|
65
|
+
.pos-centre .hexB { top: 60%; left: 64%; transform: translate(-50%, -50%); opacity: 0.35; }
|
|
66
|
+
|
|
67
|
+
/* bottom-right: hex point pokes out the bottom-right corner. */
|
|
68
|
+
.pos-bottom-right .hexA { bottom: -30%; right: -20%; }
|
|
69
|
+
.pos-bottom-right .hexB { bottom: -10%; right: 20%; opacity: 0.4; }
|
|
70
|
+
|
|
71
|
+
/* left: hex pokes in from the left, points to the right. */
|
|
72
|
+
.pos-left .hexA { top: 50%; left: -30%; transform: translateY(-50%); }
|
|
73
|
+
.pos-left .hexB { top: 30%; left: -10%; transform: translateY(-50%); opacity: 0.4; }
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <HexNetwork />
|
|
3
|
+
*
|
|
4
|
+
* Honeycomb-pattern grid of pointy-top hex tiles. Each tile holds a
|
|
5
|
+
* logo or icon and an optional caption. One tile in the centre can be
|
|
6
|
+
* highlighted as the brand anchor (Conduction hex, the workspace, the
|
|
7
|
+
* thing the surrounding network connects to).
|
|
8
|
+
*
|
|
9
|
+
* Reference visuals:
|
|
10
|
+
* - honeycomb.io/partners (the cloud-provider hex grid around the
|
|
11
|
+
* central Honeycomb logo)
|
|
12
|
+
* - the existing Conduction honeycomb backdrop in
|
|
13
|
+
* <cn-honeycomb-bg>, but as a content-bearing grid not a backdrop.
|
|
14
|
+
*
|
|
15
|
+
* Two surfaces:
|
|
16
|
+
* - Integrations: connect ConNext to mail, calendar, XWiki, Decks,
|
|
17
|
+
* OpenProject etc. Centre = ConNext / Conduction logo.
|
|
18
|
+
* - Partners: same shape, partner logos around the Conduction hex.
|
|
19
|
+
*
|
|
20
|
+
* Usage in MDX:
|
|
21
|
+
*
|
|
22
|
+
* <HexNetwork
|
|
23
|
+
* center={{name: 'ConNext', logo: '/img/connext-mark.svg'}}
|
|
24
|
+
* cells={[
|
|
25
|
+
* {name: 'Mail', logo: '/img/integrations/mail.svg'},
|
|
26
|
+
* {name: 'Calendar', logo: '/img/integrations/calendar.svg'},
|
|
27
|
+
* {name: 'XWiki', logo: '/img/integrations/xwiki.svg'},
|
|
28
|
+
* {name: 'Decks', logo: '/img/integrations/decks.svg'},
|
|
29
|
+
* {name: 'OpenProject', logo: '/img/integrations/openproject.svg'},
|
|
30
|
+
* {name: 'Talk', logo: '/img/integrations/talk.svg'},
|
|
31
|
+
* ]}
|
|
32
|
+
* layout="3-3-3"
|
|
33
|
+
* />
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import React from 'react';
|
|
37
|
+
import styles from './HexNetwork.module.css';
|
|
38
|
+
|
|
39
|
+
const LAYOUTS = {
|
|
40
|
+
'3-3-3': [3, 3, 3], // 9 cells, centre row[1][1]
|
|
41
|
+
'2-3-2': [2, 3, 2], // 7 cells, centre row[1][1]
|
|
42
|
+
'3-4-3': [3, 4, 3], // 10 cells, no natural centre slot
|
|
43
|
+
'4-3-4': [4, 3, 4], // 11 cells, used for the full /support partner wall
|
|
44
|
+
'3-3-3-3': [3, 3, 3, 3], // 12 cells, centre row[1][1]
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function Cell({cell, highlighted}) {
|
|
48
|
+
if (!cell) {
|
|
49
|
+
return <div className={[styles.cell, styles.empty].filter(Boolean).join(' ')} aria-hidden="true"><div className={styles.cellInner} /></div>;
|
|
50
|
+
}
|
|
51
|
+
const inner = cell.logo
|
|
52
|
+
? (typeof cell.logo === 'string'
|
|
53
|
+
? <img src={cell.logo} alt={cell.name || ''} className={styles.logo} />
|
|
54
|
+
: <span className={styles.logo}>{cell.logo}</span>)
|
|
55
|
+
: (cell.name && <span className={styles.wordmark}>{cell.name}</span>);
|
|
56
|
+
return (
|
|
57
|
+
<a
|
|
58
|
+
className={[styles.cell, highlighted && styles.center, cell.href && styles.linked].filter(Boolean).join(' ')}
|
|
59
|
+
href={cell.href || undefined}
|
|
60
|
+
title={cell.name || undefined}
|
|
61
|
+
aria-label={cell.name || undefined}
|
|
62
|
+
>
|
|
63
|
+
<div className={styles.cellInner}>{inner}</div>
|
|
64
|
+
</a>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default function HexNetwork({
|
|
69
|
+
center,
|
|
70
|
+
cells = [],
|
|
71
|
+
layout = '3-3-3',
|
|
72
|
+
background = 'transparent',
|
|
73
|
+
scroll = 'none', // 'none' | 'down' | 'up'
|
|
74
|
+
scrollSpeed = 60, // seconds per cycle
|
|
75
|
+
visibleRows = 3, // rows visible inside the scroll viewport
|
|
76
|
+
fade = true, // soft mask-gradient on viewport top/bottom
|
|
77
|
+
className,
|
|
78
|
+
}) {
|
|
79
|
+
const rowCounts = LAYOUTS[layout] || LAYOUTS['3-3-3'];
|
|
80
|
+
/* Distribute cells into rows, threading the centre into the middle row
|
|
81
|
+
(floor((rows-1)/2)) so it stays visually anchored regardless of how
|
|
82
|
+
many rows the layout has. The centre prop wins over any cell that
|
|
83
|
+
would otherwise occupy that slot. */
|
|
84
|
+
const centreRow = Math.floor((rowCounts.length - 1) / 2);
|
|
85
|
+
const rows = [];
|
|
86
|
+
let idx = 0;
|
|
87
|
+
rowCounts.forEach((count, rowIdx) => {
|
|
88
|
+
const row = [];
|
|
89
|
+
for (let c = 0; c < count; c++) {
|
|
90
|
+
const isCentreSlot = rowIdx === centreRow && c === Math.floor(count / 2) && center;
|
|
91
|
+
if (isCentreSlot) {
|
|
92
|
+
row.push({cell: center, highlighted: true});
|
|
93
|
+
} else {
|
|
94
|
+
row.push({cell: cells[idx], highlighted: false});
|
|
95
|
+
idx += 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
rows.push(row);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const composed = [styles.network, styles[`bg-${background}`], className].filter(Boolean).join(' ');
|
|
102
|
+
const renderRows = (rowsToRender, keyPrefix = '') => rowsToRender.map((row, ri) => (
|
|
103
|
+
<div
|
|
104
|
+
key={`${keyPrefix}${ri}`}
|
|
105
|
+
className={[styles.row, ri % 2 === 1 && styles.offset].filter(Boolean).join(' ')}
|
|
106
|
+
>
|
|
107
|
+
{row.map((entry, ci) => (
|
|
108
|
+
<Cell key={ci} cell={entry.cell} highlighted={entry.highlighted} />
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
));
|
|
112
|
+
|
|
113
|
+
/* Static render: just the grid. */
|
|
114
|
+
if (scroll === 'none') {
|
|
115
|
+
return (
|
|
116
|
+
<div className={composed}>
|
|
117
|
+
<div className={styles.grid}>{renderRows(rows)}</div>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Scrolling render: viewport with mask-faded edges, track that holds
|
|
123
|
+
the rows twice for a seamless loop. The track animates translateY;
|
|
124
|
+
'down' shows content moving downward (rows enter at top, exit at
|
|
125
|
+
bottom), 'up' is the reverse (credits-roll). Hover and focus pause
|
|
126
|
+
the loop, prefers-reduced-motion turns it off. */
|
|
127
|
+
const scrollClass = scroll === 'down' ? styles.scrollDown : styles.scrollUp;
|
|
128
|
+
const trackStyle = {animationDuration: `${scrollSpeed}s`};
|
|
129
|
+
const viewportStyle = {'--hex-visible-rows': visibleRows};
|
|
130
|
+
const viewportClass = [styles.viewport, fade && styles.viewportFade].filter(Boolean).join(' ');
|
|
131
|
+
return (
|
|
132
|
+
<div className={composed}>
|
|
133
|
+
<div className={viewportClass} style={viewportStyle}>
|
|
134
|
+
<div className={[styles.track, scrollClass].join(' ')} style={trackStyle}>
|
|
135
|
+
{renderRows(rows, 'a-')}
|
|
136
|
+
{renderRows(rows, 'b-')}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <HexNetwork /> styles. Honeycomb-pattern hex grid with logos.
|
|
3
|
+
*
|
|
4
|
+
* Geometry: pointy-top hex (sqrt(3) ratio). Rows tessellate by
|
|
5
|
+
* - horizontal pitch in a row: cell-width + ROW_GAP
|
|
6
|
+
* - vertical pitch between rows: 3/4 of cell-height (rows nest 1/4 deep)
|
|
7
|
+
* - alternate rows offset by half a horizontal pitch so points interlock
|
|
8
|
+
*
|
|
9
|
+
* Tunables (kept in one place so the math stays consistent):
|
|
10
|
+
* --hex-cell-w: 132px;
|
|
11
|
+
* --hex-cell-h: 152px; (≈ 1.155 × cell-w, the sqrt(3) ratio)
|
|
12
|
+
* --hex-row-gap: 8px; (gap between adjacent cells in a row)
|
|
13
|
+
*
|
|
14
|
+
* Brand rule: partner and client logos always sit on a white surface,
|
|
15
|
+
* with the white hex acting as the framing. Never tint the hex fill
|
|
16
|
+
* for logo cells. The brand-anchor center cell is the only solid-fill
|
|
17
|
+
* exception (cobalt). See design-system CLAUDE.md.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
.network {
|
|
21
|
+
--hex-cell-w: 132px;
|
|
22
|
+
--hex-cell-h: 152px;
|
|
23
|
+
--hex-row-gap: 10px;
|
|
24
|
+
/* Vertical pitch is 3/4 of cell height (= --hex-cell-h / 4 overlap).
|
|
25
|
+
Adding (--hex-row-gap × cos 30°) gives a perpendicular row spacing
|
|
26
|
+
that matches the horizontal gap, so all six neighbour gaps look equal. */
|
|
27
|
+
--hex-row-overlap: calc((var(--hex-cell-h) / 4) - (var(--hex-row-gap) * 0.866));
|
|
28
|
+
--hex-row-offset: calc((var(--hex-cell-w) + var(--hex-row-gap)) / 2);
|
|
29
|
+
|
|
30
|
+
width: 100%;
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
position: relative;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Optional panel bg, kept for callers that want a contained tinted
|
|
38
|
+
surface. Default is transparent: most heroes provide their own
|
|
39
|
+
backdrop so the white hexes read against it. */
|
|
40
|
+
.bg-transparent { background: transparent; padding: 0; }
|
|
41
|
+
.bg-tinted { background: var(--c-cobalt-50); padding: var(--space-10) var(--space-6); border-radius: var(--radius-xl); }
|
|
42
|
+
.bg-white { background: white; padding: var(--space-10) var(--space-6); border-radius: var(--radius-xl); }
|
|
43
|
+
.bg-inverse { background: var(--c-blue-cobalt); padding: var(--space-10) var(--space-6); border-radius: var(--radius-xl); }
|
|
44
|
+
|
|
45
|
+
.grid {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
align-items: flex-start;
|
|
49
|
+
gap: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.row {
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: var(--hex-row-gap);
|
|
55
|
+
align-items: center;
|
|
56
|
+
width: max-content;
|
|
57
|
+
}
|
|
58
|
+
.row + .row {
|
|
59
|
+
margin-top: calc(-1 * var(--hex-row-overlap));
|
|
60
|
+
}
|
|
61
|
+
.row.offset {
|
|
62
|
+
/* Shift this row by half a horizontal pitch; the next row resets so
|
|
63
|
+
the zig-zag stays anchored to a single column on alternating rows. */
|
|
64
|
+
margin-left: var(--hex-row-offset);
|
|
65
|
+
}
|
|
66
|
+
.row.offset + .row {
|
|
67
|
+
margin-left: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.cell {
|
|
71
|
+
position: relative;
|
|
72
|
+
text-decoration: none;
|
|
73
|
+
color: inherit;
|
|
74
|
+
display: block;
|
|
75
|
+
transition: transform 160ms ease;
|
|
76
|
+
/* drop-shadow respects the clip-path (box-shadow would get clipped),
|
|
77
|
+
and gives the white hexes a soft separation against tinted surfaces. */
|
|
78
|
+
filter: drop-shadow(0 1px 2px rgba(7, 25, 60, 0.06)) drop-shadow(0 2px 6px rgba(7, 25, 60, 0.04));
|
|
79
|
+
}
|
|
80
|
+
.cell.linked { cursor: pointer; }
|
|
81
|
+
.cell.linked:hover { transform: translateY(-2px); }
|
|
82
|
+
|
|
83
|
+
.cellInner {
|
|
84
|
+
width: var(--hex-cell-w);
|
|
85
|
+
height: var(--hex-cell-h);
|
|
86
|
+
clip-path: var(--hex-pointy-top);
|
|
87
|
+
background: white;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
position: relative;
|
|
92
|
+
}
|
|
93
|
+
.cell.empty .cellInner { background: white; opacity: 0.55; }
|
|
94
|
+
|
|
95
|
+
/* Highlighted center: solid cobalt fill, only ever applied to the
|
|
96
|
+
brand-anchor cell. */
|
|
97
|
+
.cell.center .cellInner { background: var(--c-blue-cobalt); }
|
|
98
|
+
|
|
99
|
+
.logo {
|
|
100
|
+
display: block;
|
|
101
|
+
max-width: 56%;
|
|
102
|
+
max-height: 56%;
|
|
103
|
+
object-fit: contain;
|
|
104
|
+
color: var(--c-blue-cobalt);
|
|
105
|
+
}
|
|
106
|
+
.cell.center .logo {
|
|
107
|
+
color: white;
|
|
108
|
+
max-width: 64%;
|
|
109
|
+
max-height: 64%;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.wordmark {
|
|
113
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
114
|
+
font-weight: 700;
|
|
115
|
+
font-size: 14px;
|
|
116
|
+
letter-spacing: 0.04em;
|
|
117
|
+
color: var(--c-cobalt-700);
|
|
118
|
+
text-align: center;
|
|
119
|
+
padding: 0 var(--space-3);
|
|
120
|
+
word-break: break-word;
|
|
121
|
+
}
|
|
122
|
+
.cell.center .wordmark { color: white; }
|
|
123
|
+
.bg-inverse .wordmark { color: var(--c-cobalt-100); }
|
|
124
|
+
|
|
125
|
+
@media (max-width: 720px) {
|
|
126
|
+
.network {
|
|
127
|
+
--hex-cell-w: 96px;
|
|
128
|
+
--hex-cell-h: 110px;
|
|
129
|
+
--hex-row-gap: 6px;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ============ Scroll variant ============
|
|
134
|
+
* Vertical marquee for the partner-wall pattern. The viewport caps the
|
|
135
|
+
* visible height to N rows; the track holds the rows twice so the
|
|
136
|
+
* translateY animation can loop seamlessly. Mask gradient softens the
|
|
137
|
+
* edges where rows enter/exit. Pauses on hover and focus. Honours
|
|
138
|
+
* prefers-reduced-motion. Mirrors the horizontal Clients marquee. */
|
|
139
|
+
|
|
140
|
+
.viewport {
|
|
141
|
+
--hex-visible-rows: 3;
|
|
142
|
+
/* Pitch (top-of-row to top-of-next-row) = cell-h - row-overlap. The
|
|
143
|
+
viewport spans <visible-rows> pitches plus the trailing row's
|
|
144
|
+
uncovered remainder (= row-overlap). */
|
|
145
|
+
height: calc(
|
|
146
|
+
var(--hex-visible-rows) * (var(--hex-cell-h) - var(--hex-row-overlap))
|
|
147
|
+
+ var(--hex-row-overlap)
|
|
148
|
+
);
|
|
149
|
+
overflow: hidden;
|
|
150
|
+
position: relative;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Soft top/bottom fade-out so rows ease in/out of the viewport. Opt-in
|
|
154
|
+
via the fade prop on <HexNetwork>; off when callers want a hard
|
|
155
|
+
crop. */
|
|
156
|
+
.viewportFade {
|
|
157
|
+
-webkit-mask-image: linear-gradient(to bottom, transparent, black 12%, black 88%, transparent);
|
|
158
|
+
mask-image: linear-gradient(to bottom, transparent, black 12%, black 88%, transparent);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.track {
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-direction: column;
|
|
164
|
+
align-items: flex-start;
|
|
165
|
+
width: max-content;
|
|
166
|
+
margin: 0 auto;
|
|
167
|
+
will-change: transform;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.scrollDown { animation: hexScrollDown 60s linear infinite; }
|
|
171
|
+
.scrollUp { animation: hexScrollUp 60s linear infinite; }
|
|
172
|
+
|
|
173
|
+
@keyframes hexScrollDown {
|
|
174
|
+
from { transform: translateY(-50%); }
|
|
175
|
+
to { transform: translateY(0); }
|
|
176
|
+
}
|
|
177
|
+
@keyframes hexScrollUp {
|
|
178
|
+
from { transform: translateY(0); }
|
|
179
|
+
to { transform: translateY(-50%); }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.viewport:hover .track,
|
|
183
|
+
.viewport:focus-within .track { animation-play-state: paused; }
|
|
184
|
+
|
|
185
|
+
@media (prefers-reduced-motion: reduce) {
|
|
186
|
+
.scrollDown, .scrollUp { animation: none; transform: translateY(-25%); }
|
|
187
|
+
}
|