@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,81 @@
1
+ /**
2
+ * <HexRain />
3
+ *
4
+ * The "Twelve apps" mini-game from preview/pages/landing.html. A
5
+ * decorative continuous spawn loop drops orange hexes from the top of
6
+ * the column; first click on any hex starts a 60-second collect-the-
7
+ * apps game (12 unique apps to find, score = unique apps collected).
8
+ * Game-end fires a `connext:gameend` event that the gaming-modal
9
+ * component picks up to update its cookie and reveal the cross-site
10
+ * progress panel.
11
+ *
12
+ * Mirrors preview/pages/landing.html .hex-rain. The actual game logic
13
+ * lives in sites/www/static/lib/hex-rain.js (266 lines of canvas-
14
+ * style imperative work that earns its place in plain JS, not JSX).
15
+ * This React wrapper:
16
+ * - Renders the mount point with the right class names so the
17
+ * runtime's selector matches
18
+ * - Lazy-loads the runtime CSS+JS via Head (idempotent across SPA
19
+ * route changes — the IIFE registers itself on first import,
20
+ * window.HexRain.hydrate() picks up new nodes)
21
+ * - Hooks into the runtime via useEffect so freshly-mounted nodes
22
+ * hydrate even when the SPA navigates between routes
23
+ *
24
+ * Usage in MDX:
25
+ *
26
+ * <Hero
27
+ * eyebrow="..."
28
+ * title="..."
29
+ * primaryCta={{...}}
30
+ * >
31
+ * <HexRain />
32
+ * </Hero>
33
+ */
34
+
35
+ import React, {useEffect, useRef} from 'react';
36
+ import Head from '@docusaurus/Head';
37
+ import useIsBrowser from '@docusaurus/useIsBrowser';
38
+ import {useLazyScript} from '../../utils/lazyScript';
39
+
40
+ const ASSET_BASE = '/lib';
41
+
42
+ export default function HexRain({ariaLabel = 'Twelve apps mini-game, click each falling icon to collect the app', className}) {
43
+ const isBrowser = useIsBrowser();
44
+ const ref = useRef(null);
45
+
46
+ /* hex-rain.js is loaded post-hydration via this hook so the runtime's
47
+ DOM mutations don't trip React's hydration mismatch. See
48
+ utils/lazyScript.js. */
49
+ useLazyScript(ASSET_BASE + '/hex-rain.js', 'hex-rain');
50
+
51
+ /* The runtime exposes window.HexRain.hydrate() once it has registered
52
+ itself. Poll one rAF tick per frame until it's there, then call it
53
+ to attach the spawn loop to this mount point. */
54
+ useEffect(() => {
55
+ if (!isBrowser) return;
56
+ let cancelled = false;
57
+ function tryHydrate() {
58
+ if (cancelled) return;
59
+ if (window.HexRain?.hydrate) {
60
+ window.HexRain.hydrate();
61
+ return;
62
+ }
63
+ requestAnimationFrame(tryHydrate);
64
+ }
65
+ tryHydrate();
66
+ return () => { cancelled = true; };
67
+ }, [isBrowser]);
68
+
69
+ return (
70
+ <>
71
+ <Head>
72
+ <link rel="stylesheet" href={ASSET_BASE + '/hex-rain.css'} />
73
+ </Head>
74
+ <div
75
+ ref={ref}
76
+ className={['hex-rain', className].filter(Boolean).join(' ')}
77
+ aria-label={ariaLabel}
78
+ />
79
+ </>
80
+ );
81
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * <HowSteps /> + <HowStep />
3
+ *
4
+ * Numbered process-step row, surfaced from preview/pages/support.html
5
+ * (.how-row, .how-step). Three or more cards in a row with a tinted
6
+ * hex number indicator and a heading + body. Used on:
7
+ * - /support ("How to get help, in three steps")
8
+ * - /install ("How install works, three steps")
9
+ * - /partners/become ("Partner programme, three tiers")
10
+ *
11
+ * Each step's number-hex tints alternately by tier: cobalt, KNVB
12
+ * orange, cobalt-700. Pass `tier="1|2|3"` to override.
13
+ *
14
+ * Usage:
15
+ *
16
+ * <HowSteps>
17
+ * <HowStep number="1" title="Read the docs first.">
18
+ * The /docs section covers most of what we'd answer in support.
19
+ * </HowStep>
20
+ * <HowStep number="2" title="Open a GitHub issue.">
21
+ * For bugs and feature requests. We triage daily.
22
+ * </HowStep>
23
+ * <HowStep number="3" title="Talk to a partner.">
24
+ * For implementation help, hosting, and rollout.
25
+ * </HowStep>
26
+ * </HowSteps>
27
+ */
28
+
29
+ import React, {Children, cloneElement, isValidElement} from 'react';
30
+ import styles from './HowSteps.module.css';
31
+
32
+ export function HowStep({number, tier, title, children, className}) {
33
+ const tierKey = tier || (number ? String(((parseInt(number, 10) - 1) % 3) + 1) : '1');
34
+ const composed = [styles.step, styles['tier-' + tierKey], className].filter(Boolean).join(' ');
35
+ return (
36
+ <div className={composed}>
37
+ {number && <div className={styles.num}>{number}</div>}
38
+ {title && <h3 className={styles.title}>{title}</h3>}
39
+ {/* div not p: MDX may wrap inline children in its own <p>, which
40
+ would be invalid nested inside another <p>. */}
41
+ {children && <div className={styles.body}>{children}</div>}
42
+ </div>
43
+ );
44
+ }
45
+
46
+ export default function HowSteps({columns = 3, children, className}) {
47
+ /* Auto-number children that don't pass an explicit `number`. Lets
48
+ callers write `<HowStep title="...">body</HowStep>` without
49
+ book-keeping. */
50
+ const numbered = Children.map(children, (child, i) => {
51
+ if (!isValidElement(child)) return child;
52
+ if (child.props.number != null) return child;
53
+ return cloneElement(child, {number: i + 1});
54
+ });
55
+ const composed = [styles.row, styles['cols-' + columns], className].filter(Boolean).join(' ');
56
+ return <div className={composed}>{numbered}</div>;
57
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * <HowSteps /> styles. Mirrors .how-row + .how-step from support.html.
3
+ */
4
+
5
+ .row { display: grid; gap: 24px; font-family: var(--conduction-typography-font-family-body); }
6
+ .cols-2 { grid-template-columns: repeat(2, 1fr); }
7
+ .cols-3 { grid-template-columns: repeat(3, 1fr); }
8
+ .cols-4 { grid-template-columns: repeat(4, 1fr); }
9
+ @media (max-width: 900px) { .row { grid-template-columns: 1fr; } }
10
+
11
+ .step {
12
+ background: white;
13
+ border: 1px solid var(--c-cobalt-100);
14
+ border-radius: var(--radius-lg);
15
+ padding: 32px;
16
+ position: relative;
17
+ }
18
+
19
+ .num {
20
+ width: 48px;
21
+ height: 56px;
22
+ clip-path: var(--hex-pointy-top);
23
+ background: var(--c-blue-cobalt);
24
+ color: white;
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ font-weight: 700;
29
+ font-size: 18px;
30
+ margin-bottom: 18px;
31
+ font-family: var(--conduction-typography-font-family-code);
32
+ }
33
+
34
+ .tier-1 .num { background: var(--c-blue-cobalt); }
35
+ .tier-2 .num { background: var(--c-orange-knvb); }
36
+ .tier-3 .num { background: var(--c-cobalt-700); }
37
+
38
+ .title {
39
+ font-size: 20px;
40
+ font-weight: 700;
41
+ letter-spacing: -0.01em;
42
+ margin: 0 0 10px;
43
+ color: var(--c-cobalt-900);
44
+ }
45
+
46
+ .body {
47
+ font-size: 14px;
48
+ color: var(--c-cobalt-700);
49
+ line-height: 1.6;
50
+ margin: 0;
51
+ }
52
+ .body :global(.next-blue) { color: var(--c-nextcloud-blue); }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * <ManagedCommonGround />
3
+ *
4
+ * Section that pitches the managed Common Ground+ tenant: a SectionHead,
5
+ * a centred CTA pair, a PairRow of the components on offer, and a
6
+ * compliance footnote. Same shape on /support and /commonground;
7
+ * keeping it as a shared component prevents copy drift between the
8
+ * two surfaces (price changes, new components, eligibility tweaks
9
+ * land once and propagate).
10
+ *
11
+ * Content is passed as props so callers can localise per page (NL +
12
+ * EN MDX twins). The component owns layout and brand-look only.
13
+ *
14
+ * Usage in MDX:
15
+ *
16
+ * <ManagedCommonGround
17
+ * eyebrow="Managed Common Ground"
18
+ * title={<>A government-only Nextcloud framework.<br/>€250 per component per month.</>}
19
+ * lede={<>For public-sector clients we run a managed <NextBlue>Nextcloud</NextBlue> tenant...</>}
20
+ * primaryCta={{label: 'Talk to us', href: 'mailto:info@conduction.nl'}}
21
+ * secondaryCta={{label: 'Compliance signals', href: '/iso'}}
22
+ * components={[
23
+ * {name: 'OpenRegister', href: '/apps/openregister', why: 'Typed register store...', icon: <svg>...</svg>},
24
+ * ...
25
+ * ]}
26
+ * footnote="Available to public-sector clients only..."
27
+ * />
28
+ */
29
+
30
+ import React from 'react';
31
+ import Section from '../primitives/Section';
32
+ import SectionHead from '../primitives/SectionHead';
33
+ import Button from '../primitives/Button';
34
+ import PairCard, {PairRow} from '../PairCard/PairCard';
35
+ import styles from './ManagedCommonGround.module.css';
36
+
37
+ export default function ManagedCommonGround({
38
+ eyebrow,
39
+ title,
40
+ lede,
41
+ primaryCta,
42
+ secondaryCta,
43
+ components = [],
44
+ footnote,
45
+ background,
46
+ spacing = 'default',
47
+ }) {
48
+ return (
49
+ <Section spacing={spacing} background={background}>
50
+ <SectionHead eyebrow={eyebrow} title={title} lede={lede} align="stack" />
51
+
52
+ {(primaryCta || secondaryCta) && (
53
+ <div className={styles.actions}>
54
+ {primaryCta && (
55
+ <Button variant="primary" size="lg" href={primaryCta.href}>
56
+ {primaryCta.label}
57
+ </Button>
58
+ )}
59
+ {secondaryCta && (
60
+ <Button variant="secondary" size="lg" href={secondaryCta.href}>
61
+ {secondaryCta.label}
62
+ </Button>
63
+ )}
64
+ </div>
65
+ )}
66
+
67
+ {components.length > 0 && (
68
+ <PairRow columns={3}>
69
+ {components.map((c, i) => (
70
+ <PairCard key={i} href={c.href} icon={c.icon} name={c.name} why={c.why} />
71
+ ))}
72
+ </PairRow>
73
+ )}
74
+
75
+ {footnote && <p className={styles.footnote}>{footnote}</p>}
76
+ </Section>
77
+ );
78
+ }
@@ -0,0 +1,16 @@
1
+ .actions {
2
+ display: flex;
3
+ gap: 14px;
4
+ flex-wrap: wrap;
5
+ margin-bottom: var(--space-8);
6
+ justify-content: center;
7
+ }
8
+
9
+ .footnote {
10
+ font-size: 13px;
11
+ color: var(--c-cobalt-700);
12
+ text-align: center;
13
+ margin: var(--space-5) auto 0;
14
+ max-width: 64ch;
15
+ line-height: 1.55;
16
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * <NewsletterCta />
3
+ *
4
+ * Cobalt-50 panel with title, lede, an email input, a submit button,
5
+ * and a fineprint line. Used at the bottom of the academy landing
6
+ * and on detail pages.
7
+ *
8
+ * The form is unwired by default (`onSubmit` no-op, `action` left
9
+ * empty). Pass an `action` URL or an `onSubmit` handler to wire it.
10
+ * Pass `inverse` to render on a cobalt-900 ground for use inside dark
11
+ * sections.
12
+ *
13
+ * Usage in MDX:
14
+ *
15
+ * <NewsletterCta
16
+ * title="New posts in your inbox, monthly."
17
+ * lede="One mail a month. New guides, case studies, webinars."
18
+ * placeholder="je@bedrijf.nl"
19
+ * submitLabel="Subscribe"
20
+ * fineprint="We mail vanuit info@conduction.nl. Geen lijstverkoop."
21
+ * />
22
+ */
23
+
24
+ import React from 'react';
25
+ import styles from './NewsletterCta.module.css';
26
+
27
+ export default function NewsletterCta({
28
+ title,
29
+ lede,
30
+ placeholder = 'jij@bedrijf.nl',
31
+ submitLabel = 'Aanmelden',
32
+ fineprint,
33
+ action,
34
+ method = 'POST',
35
+ name = 'email',
36
+ inverse = false,
37
+ onSubmit,
38
+ className,
39
+ }) {
40
+ const composed = [
41
+ styles.cta,
42
+ inverse ? styles.inverse : null,
43
+ className,
44
+ ].filter(Boolean).join(' ');
45
+
46
+ const handleSubmit = (event) => {
47
+ if (onSubmit) {
48
+ onSubmit(event);
49
+ } else if (!action) {
50
+ event.preventDefault();
51
+ }
52
+ };
53
+
54
+ return (
55
+ <section className={composed}>
56
+ <div className={styles.copy}>
57
+ {title && <h3 className={styles.title}>{title}</h3>}
58
+ {lede && <p className={styles.lede}>{lede}</p>}
59
+ </div>
60
+ <div className={styles.form}>
61
+ <form
62
+ className={styles.formRow}
63
+ action={action || undefined}
64
+ method={method}
65
+ onSubmit={handleSubmit}
66
+ >
67
+ <input
68
+ type="email"
69
+ name={name}
70
+ className={styles.input}
71
+ placeholder={placeholder}
72
+ aria-label={placeholder}
73
+ required
74
+ />
75
+ <button type="submit" className={styles.submit}>
76
+ {submitLabel}
77
+ </button>
78
+ </form>
79
+ {fineprint && <p className={styles.fineprint}>{fineprint}</p>}
80
+ </div>
81
+ </section>
82
+ );
83
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * <NewsletterCta /> styles. Mirrors .newsletter-cta in
3
+ * preview/components/academy.css.
4
+ */
5
+
6
+ .cta {
7
+ display: grid;
8
+ grid-template-columns: 1.1fr 1fr;
9
+ gap: var(--space-12);
10
+ padding: var(--space-12);
11
+ background: var(--c-cobalt-50);
12
+ border-radius: var(--radius-xl);
13
+ align-items: center;
14
+ font-family: var(--conduction-typography-font-family-body);
15
+ }
16
+ @media (max-width: 900px) {
17
+ .cta { grid-template-columns: 1fr; padding: var(--space-8); }
18
+ }
19
+
20
+ .copy { min-width: 0; }
21
+
22
+ .title {
23
+ font-size: 28px;
24
+ font-weight: 700;
25
+ letter-spacing: -0.02em;
26
+ color: var(--c-cobalt-900);
27
+ margin: 0 0 var(--space-3);
28
+ line-height: 1.2;
29
+ }
30
+
31
+ .lede {
32
+ font-size: 16px;
33
+ color: var(--c-cobalt-700);
34
+ margin: 0;
35
+ line-height: 1.55;
36
+ max-width: 42ch;
37
+ }
38
+
39
+ .form { min-width: 0; }
40
+
41
+ .formRow {
42
+ display: flex;
43
+ gap: 8px;
44
+ width: 100%;
45
+ }
46
+
47
+ .input {
48
+ flex: 1;
49
+ padding: 12px 16px;
50
+ border: 1px solid var(--c-cobalt-100);
51
+ border-radius: var(--radius-md);
52
+ background: white;
53
+ color: var(--c-cobalt-900);
54
+ font-family: var(--conduction-typography-font-family-body);
55
+ font-size: 14px;
56
+ min-width: 0;
57
+ }
58
+ .input::placeholder { color: var(--c-cobalt-400); }
59
+ .input:focus {
60
+ outline: none;
61
+ border-color: var(--c-blue-cobalt);
62
+ }
63
+
64
+ .submit {
65
+ padding: 12px 22px;
66
+ border: 0;
67
+ border-radius: var(--radius-md);
68
+ background: var(--c-blue-cobalt);
69
+ color: white;
70
+ font-weight: 600;
71
+ font-size: 14px;
72
+ font-family: var(--conduction-typography-font-family-body);
73
+ cursor: pointer;
74
+ transition: background 120ms ease;
75
+ flex-shrink: 0;
76
+ }
77
+ .submit:hover { background: var(--c-cobalt-700); }
78
+
79
+ .fineprint {
80
+ margin: var(--space-3) 0 0;
81
+ font-size: 12px;
82
+ color: var(--c-cobalt-400);
83
+ font-family: var(--conduction-typography-font-family-code);
84
+ letter-spacing: 0.04em;
85
+ }
86
+
87
+ /* Inverse variant for placement inside dark sections. */
88
+ .inverse {
89
+ background: var(--c-cobalt-900);
90
+ color: white;
91
+ }
92
+ .inverse .title { color: white; }
93
+ .inverse .lede { color: var(--c-cobalt-200); }
94
+ .inverse .input {
95
+ background: var(--c-cobalt-800);
96
+ border-color: var(--c-cobalt-700);
97
+ color: white;
98
+ }
99
+ .inverse .input::placeholder { color: var(--c-cobalt-300); }
100
+ .inverse .input:focus { border-color: white; }
101
+ .inverse .submit { background: white; color: var(--c-cobalt-900); }
102
+ .inverse .submit:hover { background: var(--c-cobalt-50); }
103
+ .inverse .fineprint { color: var(--c-cobalt-300); }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * <PairCard /> + <PairRow />
3
+ *
4
+ * Compact "related item" card from preview/components/pair-cards.html.
5
+ * Used as the "Pairs well with" / "See also" row on app and solution
6
+ * detail pages. Smaller and denser than <AppCard/>.
7
+ *
8
+ * Visual: cobalt icon-hex on the left, name + why on the right.
9
+ *
10
+ * Brand icons (Nextcloud, Common Ground+) ship with their own hex
11
+ * fill and ink colour per the design-system rule. Pass iconBg +
12
+ * iconColor to override the default cobalt + white pair, and the
13
+ * card switches to the "brand" icon-wrap variant: the SVG renders
14
+ * as a filled mark instead of a stroke-only glyph.
15
+ *
16
+ * Usage:
17
+ *
18
+ * <PairRow>
19
+ * <PairCard
20
+ * href="/apps/opencatalogi"
21
+ * icon={<svg>...</svg>}
22
+ * name="OpenCatalogi"
23
+ * why="Surfaces every register as a public, searchable catalog entry."
24
+ * />
25
+ * <PairCard
26
+ * href="/connext"
27
+ * icon={<svg>{nextcloud logo}</svg>}
28
+ * iconBg="var(--c-nextcloud-blue)"
29
+ * iconColor="white"
30
+ * name={<>Con<NextBlue>Next</NextBlue></>}
31
+ * why="..."
32
+ * />
33
+ * </PairRow>
34
+ */
35
+
36
+ import React from 'react';
37
+ import styles from './PairCard.module.css';
38
+
39
+ export function PairRow({columns = 3, children, className}) {
40
+ const composed = [styles.row, styles['cols-' + columns], className].filter(Boolean).join(' ');
41
+ return <div className={composed}>{children}</div>;
42
+ }
43
+
44
+ export default function PairCard({href, icon, iconBg, iconColor, name, why, className}) {
45
+ const Tag = href ? 'a' : 'div';
46
+ const isBrand = Boolean(iconBg || iconColor);
47
+ const wrapClass = [styles.iconWrap, isBrand && styles.iconWrapBrand].filter(Boolean).join(' ');
48
+ const wrapStyle = isBrand ? {background: iconBg, color: iconColor} : undefined;
49
+ return (
50
+ <Tag href={href} className={[styles.card, className].filter(Boolean).join(' ')}>
51
+ <div className={wrapClass} style={wrapStyle}>{icon}</div>
52
+ <div>
53
+ {name && <div className={styles.name}>{name}</div>}
54
+ {why && <div className={styles.why}>{why}</div>}
55
+ </div>
56
+ </Tag>
57
+ );
58
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * <PairCard /> styles. Mirrors pair-cards.css exactly.
3
+ */
4
+
5
+ .row { display: grid; gap: 16px; }
6
+ .cols-2 { grid-template-columns: repeat(2, 1fr); }
7
+ .cols-3 { grid-template-columns: repeat(3, 1fr); }
8
+ @media (max-width: 900px) { .row { grid-template-columns: 1fr; } }
9
+
10
+ .card {
11
+ background: white;
12
+ border: 1px solid var(--c-cobalt-100);
13
+ border-radius: var(--radius-lg);
14
+ padding: 20px;
15
+ text-decoration: none;
16
+ color: inherit;
17
+ display: flex;
18
+ gap: 14px;
19
+ align-items: flex-start;
20
+ transition: all 160ms ease;
21
+ font-family: var(--conduction-typography-font-family-body);
22
+ }
23
+ .card:hover {
24
+ border-color: var(--c-blue-cobalt);
25
+ transform: translateY(-1px);
26
+ text-decoration: none;
27
+ color: inherit;
28
+ }
29
+
30
+ .iconWrap {
31
+ width: 44px;
32
+ height: 50px;
33
+ clip-path: var(--hex-pointy-top);
34
+ background: var(--c-blue-cobalt);
35
+ color: white;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ flex-shrink: 0;
40
+ }
41
+ .iconWrap svg { width: 20px; height: 20px; stroke: currentColor; stroke-width: 2; fill: none; }
42
+
43
+ /* Brand-icon variant: the SVG is a filled brand mark (Nextcloud logo,
44
+ Common Ground logo) rather than a stroke-only glyph. Override the
45
+ stroke-only rule so the logo renders solid in its currentColor. */
46
+ .iconWrapBrand svg {
47
+ width: auto;
48
+ height: 22px;
49
+ fill: currentColor;
50
+ stroke: none;
51
+ }
52
+
53
+ .name { font-size: 15px; font-weight: 600; margin-bottom: 4px; color: var(--c-cobalt-900); }
54
+ .why { font-size: 13px; color: var(--c-cobalt-700); line-height: 1.5; }