@conduction/docusaurus-preset 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/MISSING_COMPONENTS.md +109 -0
  2. package/README.md +171 -0
  3. package/package.json +59 -0
  4. package/src/components/AgentTrace/AgentTrace.jsx +128 -0
  5. package/src/components/AgentTrace/AgentTrace.module.css +115 -0
  6. package/src/components/AppMock/AppMock.jsx +86 -0
  7. package/src/components/AppMock/AppMock.module.css +629 -0
  8. package/src/components/AppMock/variants/DeciDeskMock.jsx +71 -0
  9. package/src/components/AppMock/variants/DocuDeskMock.jsx +69 -0
  10. package/src/components/AppMock/variants/LarpingAppMock.jsx +59 -0
  11. package/src/components/AppMock/variants/MyDashBiMock.jsx +135 -0
  12. package/src/components/AppMock/variants/MyDashMock.jsx +96 -0
  13. package/src/components/AppMock/variants/MyDashTilesMock.jsx +103 -0
  14. package/src/components/AppMock/variants/MyDashWidgetsMock.jsx +123 -0
  15. package/src/components/AppMock/variants/NLDesignMock.jsx +70 -0
  16. package/src/components/AppMock/variants/OpenCatalogiMock.jsx +61 -0
  17. package/src/components/AppMock/variants/OpenConnectorMock.jsx +83 -0
  18. package/src/components/AppMock/variants/OpenRegisterMock.jsx +100 -0
  19. package/src/components/AppMock/variants/OpenWooMock.jsx +61 -0
  20. package/src/components/AppMock/variants/PipelinQMock.jsx +88 -0
  21. package/src/components/AppMock/variants/ProcestMock.jsx +87 -0
  22. package/src/components/AppMock/variants/SoftwareCatalogMock.jsx +71 -0
  23. package/src/components/AppMock/variants/ZaakAfhandelAppMock.jsx +71 -0
  24. package/src/components/AppsGrid/AppsGrid.jsx +84 -0
  25. package/src/components/AppsGrid/AppsGrid.module.css +46 -0
  26. package/src/components/AppsPreview/AppsPreview.jsx +85 -0
  27. package/src/components/AppsPreview/AppsPreview.module.css +128 -0
  28. package/src/components/Clients/Clients.jsx +205 -0
  29. package/src/components/Clients/Clients.module.css +166 -0
  30. package/src/components/ComposeBlock/ComposeBlock.jsx +70 -0
  31. package/src/components/ComposeBlock/ComposeBlock.module.css +74 -0
  32. package/src/components/ConductionBg/ConductionBg.jsx +150 -0
  33. package/src/components/ConductionBg/ConductionBg.module.css +41 -0
  34. package/src/components/ContentCard/ContentCard.jsx +126 -0
  35. package/src/components/ContentCard/ContentCard.module.css +84 -0
  36. package/src/components/ContentDetailHero/ContentDetailHero.jsx +136 -0
  37. package/src/components/ContentDetailHero/ContentDetailHero.module.css +96 -0
  38. package/src/components/ContentTypeFilter/ContentTypeFilter.jsx +103 -0
  39. package/src/components/ContentTypeFilter/ContentTypeFilter.module.css +60 -0
  40. package/src/components/ContentTypeFilter/contentTypes.js +58 -0
  41. package/src/components/CookieCli/CookieCli.jsx +223 -0
  42. package/src/components/CookieCli/CookieCli.module.css +166 -0
  43. package/src/components/CtaBanner/CtaBanner.jsx +61 -0
  44. package/src/components/CtaBanner/CtaBanner.module.css +65 -0
  45. package/src/components/DetailHero/DetailHero.jsx +143 -0
  46. package/src/components/DetailHero/DetailHero.module.css +154 -0
  47. package/src/components/Diagrams/Diagrams.jsx +148 -0
  48. package/src/components/EmployeeCard/EmployeeCard.jsx +127 -0
  49. package/src/components/EmployeeCard/EmployeeCard.module.css +144 -0
  50. package/src/components/ExternalAppShelf/ExternalAppShelf.jsx +61 -0
  51. package/src/components/ExternalAppShelf/ExternalAppShelf.module.css +90 -0
  52. package/src/components/FAQ/FAQ.jsx +42 -0
  53. package/src/components/FAQ/FAQ.module.css +74 -0
  54. package/src/components/FacetedFilters/FacetedFilters.jsx +125 -0
  55. package/src/components/FacetedFilters/FacetedFilters.module.css +133 -0
  56. package/src/components/FeatureGrid/FeatureGrid.jsx +94 -0
  57. package/src/components/FeatureGrid/FeatureGrid.module.css +114 -0
  58. package/src/components/FeatureList/FeatureList.jsx +54 -0
  59. package/src/components/FeatureList/FeatureList.module.css +52 -0
  60. package/src/components/FeaturedCard/FeaturedCard.jsx +101 -0
  61. package/src/components/FeaturedCard/FeaturedCard.module.css +98 -0
  62. package/src/components/GameModal/GameModal.jsx +197 -0
  63. package/src/components/GameModal/GameModal.module.css +184 -0
  64. package/src/components/Hero/Hero.jsx +101 -0
  65. package/src/components/Hero/Hero.module.css +95 -0
  66. package/src/components/HexBackground/HexBackground.jsx +56 -0
  67. package/src/components/HexBackground/HexBackground.module.css +73 -0
  68. package/src/components/HexNetwork/HexNetwork.jsx +141 -0
  69. package/src/components/HexNetwork/HexNetwork.module.css +187 -0
  70. package/src/components/HexRain/HexRain.jsx +81 -0
  71. package/src/components/HowSteps/HowSteps.jsx +57 -0
  72. package/src/components/HowSteps/HowSteps.module.css +52 -0
  73. package/src/components/ManagedCommonGround/ManagedCommonGround.jsx +78 -0
  74. package/src/components/ManagedCommonGround/ManagedCommonGround.module.css +16 -0
  75. package/src/components/NewsletterCta/NewsletterCta.jsx +83 -0
  76. package/src/components/NewsletterCta/NewsletterCta.module.css +103 -0
  77. package/src/components/PairCard/PairCard.jsx +58 -0
  78. package/src/components/PairCard/PairCard.module.css +54 -0
  79. package/src/components/PartnerCard/PartnerCard.jsx +130 -0
  80. package/src/components/PartnerCard/PartnerCard.module.css +198 -0
  81. package/src/components/PartnerDirectory/PartnerDirectory.jsx +122 -0
  82. package/src/components/PartnerDirectory/PartnerDirectory.module.css +25 -0
  83. package/src/components/PartnerSidecard/PartnerSidecard.jsx +116 -0
  84. package/src/components/PartnerSidecard/PartnerSidecard.module.css +185 -0
  85. package/src/components/Pipeline/Pipeline.jsx +198 -0
  86. package/src/components/Pipeline/Pipeline.module.css +206 -0
  87. package/src/components/PlatformDiagram/PlatformDiagram.jsx +110 -0
  88. package/src/components/PlatformOverview/PlatformOverview.jsx +68 -0
  89. package/src/components/PlatformOverview/PlatformOverview.module.css +71 -0
  90. package/src/components/ReferenceCard/ReferenceCard.jsx +44 -0
  91. package/src/components/ReferenceCard/ReferenceCard.module.css +57 -0
  92. package/src/components/RelatedPosts/RelatedPosts.jsx +58 -0
  93. package/src/components/RelatedPosts/RelatedPosts.module.css +51 -0
  94. package/src/components/RotatingCards/RotatingCards.jsx +98 -0
  95. package/src/components/RotatingCards/RotatingCards.module.css +153 -0
  96. package/src/components/Showcase/Showcase.jsx +129 -0
  97. package/src/components/Showcase/Showcase.module.css +168 -0
  98. package/src/components/SolutionCard/SolutionCard.jsx +83 -0
  99. package/src/components/SolutionCard/SolutionCard.module.css +99 -0
  100. package/src/components/StatsStrip/StatsStrip.jsx +38 -0
  101. package/src/components/StatsStrip/StatsStrip.module.css +53 -0
  102. package/src/components/WidgetShelf/WidgetShelf.jsx +67 -0
  103. package/src/components/WidgetShelf/WidgetShelf.module.css +73 -0
  104. package/src/components/index.js +96 -0
  105. package/src/components/primitives/AuthorByline.jsx +85 -0
  106. package/src/components/primitives/AuthorByline.module.css +57 -0
  107. package/src/components/primitives/BrandCitation.jsx +71 -0
  108. package/src/components/primitives/Button.jsx +46 -0
  109. package/src/components/primitives/Button.module.css +88 -0
  110. package/src/components/primitives/Card.jsx +42 -0
  111. package/src/components/primitives/Card.module.css +42 -0
  112. package/src/components/primitives/Eyebrow.jsx +37 -0
  113. package/src/components/primitives/Eyebrow.module.css +19 -0
  114. package/src/components/primitives/HexBullet.jsx +37 -0
  115. package/src/components/primitives/HexBullet.module.css +16 -0
  116. package/src/components/primitives/HexThumbnail.jsx +70 -0
  117. package/src/components/primitives/HexThumbnail.module.css +45 -0
  118. package/src/components/primitives/Pill.jsx +42 -0
  119. package/src/components/primitives/Pill.module.css +30 -0
  120. package/src/components/primitives/Section.jsx +51 -0
  121. package/src/components/primitives/Section.module.css +31 -0
  122. package/src/components/primitives/SectionHead.jsx +36 -0
  123. package/src/components/primitives/SectionHead.module.css +43 -0
  124. package/src/components/primitives/index.js +22 -0
  125. package/src/css/brand.css +158 -0
  126. package/src/css/tokens.css +12 -0
  127. package/src/data/app-downloads.js +42 -0
  128. package/src/diagrams/README.md +74 -0
  129. package/src/diagrams/cn-domain-tree.js +105 -0
  130. package/src/diagrams/cn-hex-prism.js +163 -0
  131. package/src/diagrams/cn-hex.js +181 -0
  132. package/src/diagrams/cn-honeycomb-bg.js +135 -0
  133. package/src/diagrams/cn-pipeline.js +150 -0
  134. package/src/diagrams/cn-platform.js +156 -0
  135. package/src/diagrams/cn-side-box.js +104 -0
  136. package/src/diagrams/index.js +28 -0
  137. package/src/index.js +183 -0
  138. package/src/theme/Footer/index.jsx +516 -0
  139. package/src/theme/MDXPage/index.jsx +134 -0
  140. package/src/theme/Navbar/index.jsx +120 -0
  141. package/src/theme/Navbar/styles.module.css +114 -0
  142. package/src/theme/brand.jsx +63 -0
  143. package/src/theme.js +45 -0
  144. package/src/utils/lazyScript.js +37 -0
  145. package/static/img/favicon.svg +14 -0
  146. package/static/img/honeycomb-scatter.svg +23 -0
  147. package/static/img/honeycomb-watermark.svg +108 -0
  148. package/static/img/logo-dark.svg +11 -0
  149. package/static/img/logo.svg +14 -0
  150. package/static/img/nextcloud-logo.svg +5 -0
  151. package/static/lib/canal-footer.css +418 -0
  152. package/static/lib/canal-footer.js +499 -0
  153. package/static/lib/clients-flow.js +317 -0
  154. package/static/lib/conduction-bg.css +50 -0
  155. package/static/lib/conduction-bg.js +122 -0
  156. package/static/lib/hex-rain.css +128 -0
  157. package/static/lib/hex-rain.js +284 -0
  158. package/static/lib/kade-cyclist.css +264 -0
  159. package/static/lib/kade-cyclist.js +420 -0
  160. package/static/lib/logo-memory.css +219 -0
  161. package/static/lib/logo-memory.js +540 -0
  162. package/static/lib/platform-diagram.css +458 -0
  163. package/static/lib/platform-diagram.js +414 -0
@@ -0,0 +1,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
+ }