@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,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <FeatureList /> + <FeatureItem />
|
|
3
|
+
*
|
|
4
|
+
* Long, single-column feature list from preview/components/feature-list.html.
|
|
5
|
+
* Used in the broad column on app and solution detail pages, scales
|
|
6
|
+
* cleanly to 6-12 items.
|
|
7
|
+
*
|
|
8
|
+
* Each feature pairs a cobalt-50 hex glyph with a heading and a short
|
|
9
|
+
* body paragraph.
|
|
10
|
+
*
|
|
11
|
+
* Usage in MDX:
|
|
12
|
+
*
|
|
13
|
+
* <FeatureList items={[
|
|
14
|
+
* {icon: <svg>...</svg>, title: 'Schemas you write once.', body: '...'},
|
|
15
|
+
* {icon: <svg>...</svg>, title: 'One backbone, every app.', body: '...'},
|
|
16
|
+
* ]} />
|
|
17
|
+
*
|
|
18
|
+
* Or with children for full MDX flexibility:
|
|
19
|
+
*
|
|
20
|
+
* <FeatureList>
|
|
21
|
+
* <FeatureItem icon={<svg/>} title="...">
|
|
22
|
+
* Body paragraph...
|
|
23
|
+
* </FeatureItem>
|
|
24
|
+
* </FeatureList>
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import React from 'react';
|
|
28
|
+
import styles from './FeatureList.module.css';
|
|
29
|
+
|
|
30
|
+
export function FeatureItem({icon, title, body, children}) {
|
|
31
|
+
return (
|
|
32
|
+
<div className={styles.feature}>
|
|
33
|
+
<div className={styles.glyph}>{icon}</div>
|
|
34
|
+
<div>
|
|
35
|
+
{title && <h3 className={styles.title}>{title}</h3>}
|
|
36
|
+
{/* `body` prop is a string or inline JSX, wrap in <p> for typography.
|
|
37
|
+
`children` come from MDX which already wraps loose text in <p>;
|
|
38
|
+
using <div> here avoids the nested <p> DOM-validation warning. */}
|
|
39
|
+
{body && <p className={styles.body}>{body}</p>}
|
|
40
|
+
{!body && children && <div className={styles.body}>{children}</div>}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default function FeatureList({items, children, className}) {
|
|
47
|
+
return (
|
|
48
|
+
<div className={[styles.list, className].filter(Boolean).join(' ')}>
|
|
49
|
+
{items
|
|
50
|
+
? items.map((it, i) => <FeatureItem key={i} {...it} />)
|
|
51
|
+
: children}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <FeatureList /> styles. Mirrors feature-list.css exactly.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.list {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: 40px;
|
|
9
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.feature {
|
|
13
|
+
display: grid;
|
|
14
|
+
grid-template-columns: 64px 1fr;
|
|
15
|
+
gap: 24px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.glyph {
|
|
19
|
+
width: 56px;
|
|
20
|
+
height: 64px;
|
|
21
|
+
clip-path: var(--hex-pointy-top);
|
|
22
|
+
background: var(--c-cobalt-50);
|
|
23
|
+
color: var(--c-blue-cobalt);
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
}
|
|
29
|
+
.glyph svg {
|
|
30
|
+
width: 26px;
|
|
31
|
+
height: 26px;
|
|
32
|
+
stroke: currentColor;
|
|
33
|
+
stroke-width: 2;
|
|
34
|
+
fill: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.title {
|
|
38
|
+
font-size: 22px;
|
|
39
|
+
font-weight: 700;
|
|
40
|
+
letter-spacing: -0.01em;
|
|
41
|
+
margin: 0 0 8px;
|
|
42
|
+
color: var(--c-cobalt-900);
|
|
43
|
+
}
|
|
44
|
+
.title :global(.next-blue) { color: var(--c-nextcloud-blue); }
|
|
45
|
+
|
|
46
|
+
.body {
|
|
47
|
+
font-size: 15px;
|
|
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,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <FeaturedCard />
|
|
3
|
+
*
|
|
4
|
+
* Hero spot at the top of the academy landing. Cobalt-900 ground, body
|
|
5
|
+
* copy on the left, a large pointy-top hex with two satellite hexes on
|
|
6
|
+
* the right. Used to surface one editorial pick per page.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors the .featured-card section in preview/components/academy.html.
|
|
9
|
+
*
|
|
10
|
+
* Per huisstijl, the satellite-3 hex is the page's single KNVB orange
|
|
11
|
+
* accent; the rest is solid cobalt + white. Pass `accent="cobalt"` to
|
|
12
|
+
* suppress the orange accent on screens that already use orange
|
|
13
|
+
* elsewhere.
|
|
14
|
+
*
|
|
15
|
+
* Usage in MDX:
|
|
16
|
+
*
|
|
17
|
+
* <FeaturedCard
|
|
18
|
+
* href="/posts/install-opencatalogi"
|
|
19
|
+
* eyebrow="Featured guide"
|
|
20
|
+
* title="Install OpenCatalogi in two minutes."
|
|
21
|
+
* lede="From app store to first register, no terminal required."
|
|
22
|
+
* ctaLabel="Read the guide"
|
|
23
|
+
* author={{ name: "Ruben van der Linde" }}
|
|
24
|
+
* date="2026-05-05"
|
|
25
|
+
* thumbnail={{ icon: <svg>...</svg> }}
|
|
26
|
+
* />
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import React from 'react';
|
|
30
|
+
import HexThumbnail from '../primitives/HexThumbnail';
|
|
31
|
+
import HexBullet from '../primitives/HexBullet';
|
|
32
|
+
import AuthorByline from '../primitives/AuthorByline';
|
|
33
|
+
import styles from './FeaturedCard.module.css';
|
|
34
|
+
|
|
35
|
+
export default function FeaturedCard({
|
|
36
|
+
href,
|
|
37
|
+
eyebrow,
|
|
38
|
+
title,
|
|
39
|
+
lede,
|
|
40
|
+
ctaLabel = 'Read more',
|
|
41
|
+
author,
|
|
42
|
+
date,
|
|
43
|
+
dateLabel,
|
|
44
|
+
locale,
|
|
45
|
+
thumbnail,
|
|
46
|
+
accent = 'orange',
|
|
47
|
+
className,
|
|
48
|
+
}) {
|
|
49
|
+
const Tag = href ? 'a' : 'div';
|
|
50
|
+
const composed = [styles.card, className].filter(Boolean).join(' ');
|
|
51
|
+
const thumbProps = thumbnail || {};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Tag href={href} className={composed}>
|
|
55
|
+
<div className={styles.body}>
|
|
56
|
+
{eyebrow && (
|
|
57
|
+
<span className={styles.eyebrow}>
|
|
58
|
+
<HexBullet size="md" color="var(--c-orange-knvb)" />
|
|
59
|
+
<span>{eyebrow}</span>
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
62
|
+
{title && <h2 className={styles.title}>{title}</h2>}
|
|
63
|
+
{lede && <p className={styles.lede}>{lede}</p>}
|
|
64
|
+
|
|
65
|
+
{(author || date) && (
|
|
66
|
+
<div className={styles.meta}>
|
|
67
|
+
<AuthorByline
|
|
68
|
+
name={author && author.name}
|
|
69
|
+
avatarSrc={author && author.avatarSrc}
|
|
70
|
+
initials={author && author.initials}
|
|
71
|
+
date={date}
|
|
72
|
+
dateLabel={dateLabel}
|
|
73
|
+
locale={locale}
|
|
74
|
+
tone="on-dark"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
{ctaLabel && (
|
|
80
|
+
<span className={styles.cta}>
|
|
81
|
+
<span>{ctaLabel}</span>
|
|
82
|
+
<span aria-hidden="true">→</span>
|
|
83
|
+
</span>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className={styles.visual} aria-hidden="true">
|
|
88
|
+
{thumbProps.src
|
|
89
|
+
? <HexThumbnail size="xl" tone="cobalt" src={thumbProps.src} alt={thumbProps.alt} />
|
|
90
|
+
: <HexThumbnail size="xl" tone={thumbProps.tone || 'cobalt'}>{thumbProps.icon}</HexThumbnail>}
|
|
91
|
+
<span className={[styles.satellite, styles.s1].join(' ')} />
|
|
92
|
+
<span className={[styles.satellite, styles.s2].join(' ')} />
|
|
93
|
+
<span className={[
|
|
94
|
+
styles.satellite,
|
|
95
|
+
styles.s3,
|
|
96
|
+
accent === 'orange' ? styles.s3Orange : styles.s3Cobalt,
|
|
97
|
+
].join(' ')} />
|
|
98
|
+
</div>
|
|
99
|
+
</Tag>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <FeaturedCard /> styles. Mirrors .featured-card in
|
|
3
|
+
* preview/components/academy.css.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.card {
|
|
7
|
+
display: grid;
|
|
8
|
+
grid-template-columns: 1fr 1fr;
|
|
9
|
+
gap: var(--space-12);
|
|
10
|
+
padding: var(--space-12);
|
|
11
|
+
background: var(--c-cobalt-900);
|
|
12
|
+
color: white;
|
|
13
|
+
border-radius: var(--radius-xl);
|
|
14
|
+
align-items: center;
|
|
15
|
+
position: relative;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
text-decoration: none;
|
|
18
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
19
|
+
}
|
|
20
|
+
.card:hover { text-decoration: none; color: white; }
|
|
21
|
+
|
|
22
|
+
@media (max-width: 900px) {
|
|
23
|
+
.card { grid-template-columns: 1fr; padding: var(--space-8); }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.body { min-width: 0; }
|
|
27
|
+
|
|
28
|
+
.eyebrow {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 8px;
|
|
32
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
33
|
+
font-size: 12px;
|
|
34
|
+
letter-spacing: 0.1em;
|
|
35
|
+
text-transform: uppercase;
|
|
36
|
+
color: var(--c-cobalt-300);
|
|
37
|
+
margin-bottom: var(--space-4);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.title {
|
|
41
|
+
font-size: 36px;
|
|
42
|
+
font-weight: 700;
|
|
43
|
+
letter-spacing: -0.02em;
|
|
44
|
+
line-height: 1.15;
|
|
45
|
+
color: white;
|
|
46
|
+
margin: 0 0 var(--space-4);
|
|
47
|
+
}
|
|
48
|
+
@media (max-width: 700px) { .title { font-size: 28px; } }
|
|
49
|
+
|
|
50
|
+
.lede {
|
|
51
|
+
font-size: 17px;
|
|
52
|
+
color: var(--c-cobalt-200);
|
|
53
|
+
line-height: 1.55;
|
|
54
|
+
margin: 0 0 var(--space-6);
|
|
55
|
+
max-width: 50ch;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.meta { margin-bottom: var(--space-5); }
|
|
59
|
+
|
|
60
|
+
.cta {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 8px;
|
|
64
|
+
padding: 12px 22px;
|
|
65
|
+
background: white;
|
|
66
|
+
color: var(--c-cobalt-900);
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
font-size: 14px;
|
|
69
|
+
border-radius: var(--radius-pill);
|
|
70
|
+
text-decoration: none;
|
|
71
|
+
transition: background 120ms ease;
|
|
72
|
+
}
|
|
73
|
+
.card:hover .cta { background: var(--c-cobalt-50); }
|
|
74
|
+
|
|
75
|
+
.visual {
|
|
76
|
+
display: flex;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
align-items: center;
|
|
79
|
+
position: relative;
|
|
80
|
+
min-height: 300px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.satellite {
|
|
84
|
+
position: absolute;
|
|
85
|
+
width: 64px;
|
|
86
|
+
height: 74px;
|
|
87
|
+
clip-path: var(--hex-pointy-top);
|
|
88
|
+
}
|
|
89
|
+
.s1 { top: 8px; left: calc(50% - 160px); background: var(--c-cobalt-700); }
|
|
90
|
+
.s2 { bottom: 12px; right: calc(50% - 168px); background: var(--c-cobalt-700); }
|
|
91
|
+
.s3 { top: 40%; right: calc(50% - 172px); }
|
|
92
|
+
.s3Orange { background: var(--c-orange-knvb); }
|
|
93
|
+
.s3Cobalt { background: var(--c-cobalt-700); }
|
|
94
|
+
|
|
95
|
+
@media (max-width: 900px) {
|
|
96
|
+
.visual { min-height: 240px; }
|
|
97
|
+
.satellite { width: 48px; height: 56px; }
|
|
98
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <GameModal />
|
|
3
|
+
*
|
|
4
|
+
* The end-of-game dialog for the Conduction mini-games (hex-rain, the
|
|
5
|
+
* canal-footer boats, future invaders / domino / hex-tetris). Mounts
|
|
6
|
+
* once per page; listens for `connext:gameend` CustomEvents (the event
|
|
7
|
+
* name is a brand-internal identifier, retained for compatibility),
|
|
8
|
+
* opens
|
|
9
|
+
* with the matching copy, tracks total games found in localStorage so
|
|
10
|
+
* the cross-site progress bar is consistent.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors preview/components/game-modal.html. The previous JS-only
|
|
13
|
+
* version (game-modal.js) gets replaced by this component on any
|
|
14
|
+
* Docusaurus surface that mounts <GameModal/>.
|
|
15
|
+
*
|
|
16
|
+
* Game payload shape, fired by the playing component:
|
|
17
|
+
*
|
|
18
|
+
* window.dispatchEvent(new CustomEvent('connext:gameend', {
|
|
19
|
+
* detail: {
|
|
20
|
+
* id: 'hexrain',
|
|
21
|
+
* won: true, // or false
|
|
22
|
+
* score: 12, // numeric
|
|
23
|
+
* summary: '12 / 12 collected'
|
|
24
|
+
* }
|
|
25
|
+
* }));
|
|
26
|
+
*
|
|
27
|
+
* Usage in MDX (mount once on the layout, not per page):
|
|
28
|
+
*
|
|
29
|
+
* import {GameModal} from '@conduction/docusaurus-preset/components';
|
|
30
|
+
* <GameModal />
|
|
31
|
+
*
|
|
32
|
+
* Custom games table (default covers the five planned mini-games):
|
|
33
|
+
*
|
|
34
|
+
* <GameModal games={[
|
|
35
|
+
* {id: 'hexrain', label: 'Twelve apps'},
|
|
36
|
+
* {id: 'boats', label: 'Sink the boats'},
|
|
37
|
+
* {id: 'invaders', label: 'Hex-vaders'},
|
|
38
|
+
* {id: 'domino', label: 'Hex-domino'},
|
|
39
|
+
* {id: 'tetris', label: 'Hex-tris'},
|
|
40
|
+
* ]} />
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import React, {useEffect, useState, useCallback, useMemo} from 'react';
|
|
44
|
+
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
45
|
+
import styles from './GameModal.module.css';
|
|
46
|
+
|
|
47
|
+
const STORAGE_KEY = 'conduction:minigames';
|
|
48
|
+
|
|
49
|
+
const DEFAULT_GAMES = [
|
|
50
|
+
{id: 'hexrain', label: 'Twelve apps · hex rain'},
|
|
51
|
+
{id: 'boats', label: 'Sink the boats · footer canal'},
|
|
52
|
+
{id: 'invaders', label: 'Hex-vaders · cookie CLI'},
|
|
53
|
+
{id: 'logo-memory', label: 'Logo memory · clients marquee'},
|
|
54
|
+
{id: 'kade-cyclist', label: 'Kade cyclist · footer kade'},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function readFound() {
|
|
58
|
+
if (typeof window === 'undefined') return {};
|
|
59
|
+
try {
|
|
60
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
61
|
+
return raw ? JSON.parse(raw) : {};
|
|
62
|
+
} catch (e) { return {}; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeFound(found) {
|
|
66
|
+
if (typeof window === 'undefined') return;
|
|
67
|
+
try {
|
|
68
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(found));
|
|
69
|
+
} catch (e) {/* fail open */}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default function GameModal({games = DEFAULT_GAMES, className}) {
|
|
73
|
+
const isBrowser = useIsBrowser();
|
|
74
|
+
const [open, setOpen] = useState(false);
|
|
75
|
+
const [event, setEvent] = useState(null);
|
|
76
|
+
const [found, setFound] = useState({});
|
|
77
|
+
|
|
78
|
+
/* On mount: read found-games table from localStorage and subscribe
|
|
79
|
+
to the `connext:gameend` event. Each event opens the modal with
|
|
80
|
+
the supplied copy and (if won) marks the game as found. */
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!isBrowser) return;
|
|
83
|
+
setFound(readFound());
|
|
84
|
+
|
|
85
|
+
function onEnd(e) {
|
|
86
|
+
const detail = e.detail || {};
|
|
87
|
+
setEvent(detail);
|
|
88
|
+
setOpen(true);
|
|
89
|
+
/* Discovery vs. victory: any game-end counts the game as "found"
|
|
90
|
+
because a few of the games (kade-cyclist, future endless
|
|
91
|
+
runners) never reach a clean win state. The scoreboard pill
|
|
92
|
+
still reflects the actual performance for that round. */
|
|
93
|
+
if (detail.id) {
|
|
94
|
+
setFound((prev) => {
|
|
95
|
+
if (prev[detail.id]) return prev;
|
|
96
|
+
const next = {...prev, [detail.id]: true};
|
|
97
|
+
writeFound(next);
|
|
98
|
+
return next;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
window.addEventListener('connext:gameend', onEnd);
|
|
103
|
+
return () => window.removeEventListener('connext:gameend', onEnd);
|
|
104
|
+
}, [isBrowser]);
|
|
105
|
+
|
|
106
|
+
/* Escape closes the modal. */
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!isBrowser || !open) return;
|
|
109
|
+
function onKey(e) { if (e.key === 'Escape') setOpen(false); }
|
|
110
|
+
window.addEventListener('keydown', onKey);
|
|
111
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
112
|
+
}, [isBrowser, open]);
|
|
113
|
+
|
|
114
|
+
const close = useCallback(() => {
|
|
115
|
+
/* Notify the playing runtime so it can tear down its in-page UI
|
|
116
|
+
(lifted hexes, kade stage, etc.) and restore the original
|
|
117
|
+
surface. Without this the marquee or kade stays in its game-
|
|
118
|
+
over visual until the next page load. */
|
|
119
|
+
if (event?.id) {
|
|
120
|
+
Promise.resolve().then(() => {
|
|
121
|
+
window.dispatchEvent(new CustomEvent('connext:gameclose', {detail: {id: event.id}}));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
setOpen(false);
|
|
125
|
+
}, [event]);
|
|
126
|
+
const replay = useCallback(() => {
|
|
127
|
+
/* Fire a `connext:gamereplay` event so the playing component (which
|
|
128
|
+
listens for it) can re-init. We don't dispatch from inside an
|
|
129
|
+
event handler that came from React, so use a microtask to keep
|
|
130
|
+
the call stack clean. */
|
|
131
|
+
if (event?.id) {
|
|
132
|
+
Promise.resolve().then(() => {
|
|
133
|
+
window.dispatchEvent(new CustomEvent('connext:gamereplay', {detail: {id: event.id}}));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
setOpen(false);
|
|
137
|
+
}, [event]);
|
|
138
|
+
|
|
139
|
+
const foundCount = useMemo(() => Object.values(found).filter(Boolean).length, [found]);
|
|
140
|
+
const total = games.length;
|
|
141
|
+
const percent = total > 0 ? Math.round((foundCount / total) * 100) : 0;
|
|
142
|
+
|
|
143
|
+
if (!isBrowser || !open || !event) return null;
|
|
144
|
+
|
|
145
|
+
const eyebrow = event.won ? 'Mini-game complete' : 'Game over';
|
|
146
|
+
const title = event.title || (event.won ? 'Nice run.' : "That's all of them.");
|
|
147
|
+
const subtitle = event.subtitle ||
|
|
148
|
+
(event.won
|
|
149
|
+
? "You've cleared a hidden Conduction mini-game."
|
|
150
|
+
: "Try again any time, the rain doesn't stop.");
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div className={[styles.modal, className].filter(Boolean).join(' ')} role="dialog" aria-modal="true" aria-labelledby="gm-title">
|
|
154
|
+
<div className={styles.overlay} onClick={close} />
|
|
155
|
+
<div className={styles.panel}>
|
|
156
|
+
<button type="button" className={styles.close} onClick={close} aria-label="Close">×</button>
|
|
157
|
+
<p className={styles.eyebrow}>{eyebrow}</p>
|
|
158
|
+
<h2 className={styles.title} id="gm-title">{title}</h2>
|
|
159
|
+
<p className={styles.subtitle}>{subtitle}</p>
|
|
160
|
+
|
|
161
|
+
{typeof event.score !== 'undefined' && (
|
|
162
|
+
<span className={styles.scorePill}>{event.summary || `score: ${event.score}`}</span>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
<div className={styles.progress}>
|
|
166
|
+
<div className={styles.progressLabel}>
|
|
167
|
+
<span><strong>{foundCount}</strong> / {total} mini-games found</span>
|
|
168
|
+
<span>{percent}%</span>
|
|
169
|
+
</div>
|
|
170
|
+
<div className={styles.progressBar}>
|
|
171
|
+
<div className={styles.progressFill} style={{width: percent + '%'}} />
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<ul className={styles.grid}>
|
|
176
|
+
{games.map((g) => (
|
|
177
|
+
<li key={g.id} className={found[g.id] ? styles.gridItemFound : styles.gridItem}>
|
|
178
|
+
<span className={styles.gridHex} aria-hidden="true" />
|
|
179
|
+
<span>{g.label}</span>
|
|
180
|
+
</li>
|
|
181
|
+
))}
|
|
182
|
+
</ul>
|
|
183
|
+
|
|
184
|
+
<p className={styles.cta}>
|
|
185
|
+
{foundCount < total
|
|
186
|
+
? `${total - foundCount} more game${total - foundCount === 1 ? '' : 's'} hidden somewhere. Keep clicking.`
|
|
187
|
+
: "All five found. You read the kit."}
|
|
188
|
+
</p>
|
|
189
|
+
|
|
190
|
+
<div className={styles.actions}>
|
|
191
|
+
<button type="button" className={styles.btnSecondary} onClick={close}>Close</button>
|
|
192
|
+
<button type="button" className={styles.btnPrimary} onClick={replay}>Play again</button>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <GameModal /> styles. Mirrors preview/components/game-modal.css
|
|
3
|
+
* for the panel + overlay; cobalt-on-white per huisstijl.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.modal {
|
|
7
|
+
position: fixed;
|
|
8
|
+
inset: 0;
|
|
9
|
+
z-index: 10000;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.overlay {
|
|
17
|
+
position: absolute;
|
|
18
|
+
inset: 0;
|
|
19
|
+
background: rgba(10, 23, 47, 0.55);
|
|
20
|
+
backdrop-filter: blur(2px);
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.panel {
|
|
25
|
+
position: relative;
|
|
26
|
+
background: white;
|
|
27
|
+
color: var(--c-cobalt-700);
|
|
28
|
+
border-radius: var(--radius-xl);
|
|
29
|
+
padding: 40px 36px 32px;
|
|
30
|
+
width: calc(100% - 48px);
|
|
31
|
+
max-width: 520px;
|
|
32
|
+
box-shadow: var(--shadow-3, 0 30px 80px -20px rgba(10, 23, 47, 0.4));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.close {
|
|
36
|
+
position: absolute;
|
|
37
|
+
top: 14px;
|
|
38
|
+
right: 14px;
|
|
39
|
+
width: 32px;
|
|
40
|
+
height: 32px;
|
|
41
|
+
border-radius: 50%;
|
|
42
|
+
border: none;
|
|
43
|
+
background: var(--c-cobalt-50);
|
|
44
|
+
color: var(--c-cobalt-700);
|
|
45
|
+
font-size: 20px;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
line-height: 1;
|
|
48
|
+
font-family: inherit;
|
|
49
|
+
transition: background 120ms ease;
|
|
50
|
+
}
|
|
51
|
+
.close:hover { background: var(--c-cobalt-100); }
|
|
52
|
+
|
|
53
|
+
.eyebrow {
|
|
54
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
55
|
+
font-size: 11px;
|
|
56
|
+
letter-spacing: 0.14em;
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
color: var(--c-orange-knvb);
|
|
59
|
+
margin: 0 0 8px;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.title {
|
|
64
|
+
font-size: 28px;
|
|
65
|
+
font-weight: 700;
|
|
66
|
+
letter-spacing: -0.02em;
|
|
67
|
+
color: var(--c-cobalt-900);
|
|
68
|
+
margin: 0 0 8px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.subtitle {
|
|
72
|
+
font-size: 15px;
|
|
73
|
+
color: var(--c-cobalt-700);
|
|
74
|
+
line-height: 1.55;
|
|
75
|
+
margin: 0 0 16px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.scorePill {
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 6px;
|
|
82
|
+
background: var(--c-cobalt-50);
|
|
83
|
+
color: var(--c-cobalt-900);
|
|
84
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
letter-spacing: 0.04em;
|
|
87
|
+
padding: 6px 12px;
|
|
88
|
+
border-radius: var(--radius-pill);
|
|
89
|
+
margin-bottom: 24px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.progress {
|
|
93
|
+
margin: 24px 0 16px;
|
|
94
|
+
}
|
|
95
|
+
.progressLabel {
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: space-between;
|
|
98
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
99
|
+
font-size: 11px;
|
|
100
|
+
letter-spacing: 0.06em;
|
|
101
|
+
text-transform: uppercase;
|
|
102
|
+
color: var(--c-cobalt-400);
|
|
103
|
+
margin-bottom: 8px;
|
|
104
|
+
}
|
|
105
|
+
.progressLabel strong { color: var(--c-cobalt-900); font-weight: 700; }
|
|
106
|
+
|
|
107
|
+
.progressBar {
|
|
108
|
+
height: 6px;
|
|
109
|
+
background: var(--c-cobalt-50);
|
|
110
|
+
border-radius: 3px;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
}
|
|
113
|
+
.progressFill {
|
|
114
|
+
height: 100%;
|
|
115
|
+
background: var(--c-orange-knvb);
|
|
116
|
+
transition: width 240ms ease;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.grid {
|
|
120
|
+
list-style: none;
|
|
121
|
+
padding: 0;
|
|
122
|
+
margin: 0 0 20px;
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
gap: 6px;
|
|
126
|
+
}
|
|
127
|
+
.gridItem,
|
|
128
|
+
.gridItemFound {
|
|
129
|
+
display: flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
gap: 10px;
|
|
132
|
+
font-size: 14px;
|
|
133
|
+
color: var(--c-cobalt-400);
|
|
134
|
+
}
|
|
135
|
+
.gridItemFound { color: var(--c-cobalt-900); font-weight: 500; }
|
|
136
|
+
.gridHex {
|
|
137
|
+
width: 10px;
|
|
138
|
+
height: 12px;
|
|
139
|
+
clip-path: var(--hex-pointy-top);
|
|
140
|
+
background: var(--c-cobalt-200);
|
|
141
|
+
flex-shrink: 0;
|
|
142
|
+
}
|
|
143
|
+
.gridItemFound .gridHex { background: var(--c-mint-500); }
|
|
144
|
+
|
|
145
|
+
.cta {
|
|
146
|
+
font-size: 13px;
|
|
147
|
+
color: var(--c-cobalt-400);
|
|
148
|
+
margin: 16px 0 24px;
|
|
149
|
+
font-style: italic;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.actions {
|
|
153
|
+
display: flex;
|
|
154
|
+
gap: 8px;
|
|
155
|
+
justify-content: flex-end;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.btnSecondary {
|
|
159
|
+
background: white;
|
|
160
|
+
color: var(--c-cobalt-700);
|
|
161
|
+
border: 1px solid var(--c-cobalt-200);
|
|
162
|
+
padding: 10px 18px;
|
|
163
|
+
border-radius: var(--radius-md);
|
|
164
|
+
font-weight: 500;
|
|
165
|
+
font-size: 14px;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
font-family: inherit;
|
|
168
|
+
transition: border-color 120ms ease;
|
|
169
|
+
}
|
|
170
|
+
.btnSecondary:hover { border-color: var(--c-blue-cobalt); color: var(--c-blue-cobalt); }
|
|
171
|
+
|
|
172
|
+
.btnPrimary {
|
|
173
|
+
background: var(--c-blue-cobalt);
|
|
174
|
+
color: white;
|
|
175
|
+
border: 1px solid var(--c-blue-cobalt);
|
|
176
|
+
padding: 10px 18px;
|
|
177
|
+
border-radius: var(--radius-md);
|
|
178
|
+
font-weight: 500;
|
|
179
|
+
font-size: 14px;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
font-family: inherit;
|
|
182
|
+
transition: background 120ms ease;
|
|
183
|
+
}
|
|
184
|
+
.btnPrimary:hover { background: var(--c-cobalt-700); border-color: var(--c-cobalt-700); }
|