@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,284 @@
|
|
|
1
|
+
/* Hex-rain hydrator + "Twelve apps" mini-game. The rain itself is a
|
|
2
|
+
decorative continuous spawn loop; first click on a hex starts the
|
|
3
|
+
game (60s timer, score = unique apps collected). Clicking a hex of
|
|
4
|
+
an already-collected app DESELECTS that app (the twist), so
|
|
5
|
+
misclicks during the late-game chaos punish you. Win = 12 / 12.
|
|
6
|
+
Game ends fire `connext:gameend` so the gaming-modal component
|
|
7
|
+
can update its cookie + reveal the cross-site progress panel. */
|
|
8
|
+
(function () {
|
|
9
|
+
const ICONS = [
|
|
10
|
+
/* 0 Catalogi */ '<path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4"/><path d="M3 17l9 4 9-4"/>',
|
|
11
|
+
/* 1 Register */ '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/>',
|
|
12
|
+
/* 2 Connector */ '<circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/>',
|
|
13
|
+
/* 3 DocuDesk */ '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>',
|
|
14
|
+
/* 4 MyDash */ '<rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/>',
|
|
15
|
+
/* 5 AI Bridge */ '<path d="M12 2a4 4 0 0 0-4 4v3"/><rect x="6" y="9" width="12" height="13" rx="2"/>',
|
|
16
|
+
/* 6 Pipeline */ '<path d="M4 7h6l4 5h6"/><path d="M4 17h6l4-5"/>',
|
|
17
|
+
/* 7 Calendar */ '<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/>',
|
|
18
|
+
/* 8 Lock */ '<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>',
|
|
19
|
+
/* 9 Mail */ '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/>',
|
|
20
|
+
/* 10 Cloud */ '<path d="M7 18a4 4 0 0 1 0-8 5 5 0 0 1 9.6-2A4 4 0 0 1 17 18z"/>',
|
|
21
|
+
/* 11 People */ '<circle cx="9" cy="8" r="3"/><path d="M3 20a6 6 0 0 1 12 0"/><circle cx="17" cy="9" r="2.5"/><path d="M14 20a4 4 0 0 1 7 0"/>',
|
|
22
|
+
];
|
|
23
|
+
const APP_NAMES = [
|
|
24
|
+
'OpenCatalogi', 'OpenRegister', 'OpenConnector', 'DocuDesk',
|
|
25
|
+
'MyDash', 'AI Bridge', 'PipelinQ', 'OpenCalendar',
|
|
26
|
+
'OpenSAML', 'OpenMail', 'NextCloud', 'OpenZaak',
|
|
27
|
+
];
|
|
28
|
+
const APP_COUNT = ICONS.length;
|
|
29
|
+
const GAME_SECONDS = 60;
|
|
30
|
+
|
|
31
|
+
const rand = (a, b) => Math.random() * (b - a) + a;
|
|
32
|
+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
33
|
+
|
|
34
|
+
/* Wrap the per-container init in a named function so we can expose
|
|
35
|
+
it as window.HexRain.hydrate() for SPA route changes that mount
|
|
36
|
+
fresh .hex-rain nodes. The dataset.rainHydrated guard makes
|
|
37
|
+
repeated calls a no-op for already-mounted containers. */
|
|
38
|
+
function hydrateContainer(container) {
|
|
39
|
+
if (container.dataset.rainHydrated === '1') return;
|
|
40
|
+
container.dataset.rainHydrated = '1';
|
|
41
|
+
|
|
42
|
+
// ===== HUD =====
|
|
43
|
+
const hud = document.createElement('div');
|
|
44
|
+
hud.className = 'rain-hud';
|
|
45
|
+
hud.innerHTML =
|
|
46
|
+
'<div class="hud-top">' +
|
|
47
|
+
'<div class="hud-block hud-counter"><span class="hud-num" data-counter>0</span><span class="hud-label">Apps / 12</span></div>' +
|
|
48
|
+
'<div class="hud-block hud-timer"><span class="hud-num" data-timer>' + GAME_SECONDS + '</span><span class="hud-label">Seconds</span></div>' +
|
|
49
|
+
'</div>' +
|
|
50
|
+
'<div class="hud-icons" data-icons></div>';
|
|
51
|
+
container.appendChild(hud);
|
|
52
|
+
const hudCounter = hud.querySelector('[data-counter]');
|
|
53
|
+
const hudTimer = hud.querySelector('[data-timer]');
|
|
54
|
+
const hudCounterBlock = hud.querySelector('.hud-counter');
|
|
55
|
+
const hudTimerBlock = hud.querySelector('.hud-timer');
|
|
56
|
+
const hudIcons = hud.querySelector('[data-icons]');
|
|
57
|
+
for (let i = 0; i < APP_COUNT; i++) {
|
|
58
|
+
const slot = document.createElement('span');
|
|
59
|
+
slot.className = 'hud-icon';
|
|
60
|
+
slot.dataset.app = String(i);
|
|
61
|
+
slot.title = APP_NAMES[i];
|
|
62
|
+
slot.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true">' + ICONS[i] + '</svg>';
|
|
63
|
+
hudIcons.appendChild(slot);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ===== Game state =====
|
|
67
|
+
function newGame() {
|
|
68
|
+
return { started: false, over: false, timeLeft: GAME_SECONDS, collected: new Set() };
|
|
69
|
+
}
|
|
70
|
+
let game = newGame();
|
|
71
|
+
let timerInt = null;
|
|
72
|
+
let spawnTimer = null;
|
|
73
|
+
|
|
74
|
+
function updateHud() {
|
|
75
|
+
hudCounter.textContent = String(game.collected.size);
|
|
76
|
+
hudTimer.textContent = String(Math.max(0, game.timeLeft));
|
|
77
|
+
if (game.timeLeft <= 5 && game.started) hudTimerBlock.classList.add('urgent');
|
|
78
|
+
else hudTimerBlock.classList.remove('urgent');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function flashCounter(direction) {
|
|
82
|
+
hudCounterBlock.classList.remove('hud-tick', 'hud-untick');
|
|
83
|
+
void hudCounterBlock.offsetWidth;
|
|
84
|
+
hudCounterBlock.classList.add(direction === 'up' ? 'hud-tick' : 'hud-untick');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function startGame() {
|
|
88
|
+
if (game.started || game.over) return;
|
|
89
|
+
game.started = true;
|
|
90
|
+
hud.classList.add('active');
|
|
91
|
+
updateHud();
|
|
92
|
+
timerInt = setInterval(() => {
|
|
93
|
+
if (game.over) return;
|
|
94
|
+
game.timeLeft -= 1;
|
|
95
|
+
updateHud();
|
|
96
|
+
if (game.timeLeft <= 0) endGame(false);
|
|
97
|
+
}, 1000);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function endGame(won) {
|
|
101
|
+
if (game.over) return;
|
|
102
|
+
game.over = true;
|
|
103
|
+
if (timerInt) { clearInterval(timerInt); timerInt = null; }
|
|
104
|
+
hud.classList.remove('active');
|
|
105
|
+
const elapsed = GAME_SECONDS - game.timeLeft;
|
|
106
|
+
const summary = won
|
|
107
|
+
? '12 / 12 in ' + elapsed + 's'
|
|
108
|
+
: game.collected.size + ' / 12 collected';
|
|
109
|
+
window.dispatchEvent(new CustomEvent('connext:gameend', {
|
|
110
|
+
detail: { id: 'hexrain', won, score: game.collected.size, summary },
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resetGame() {
|
|
115
|
+
if (timerInt) { clearInterval(timerInt); timerInt = null; }
|
|
116
|
+
game = newGame();
|
|
117
|
+
hud.classList.remove('active');
|
|
118
|
+
hudTimerBlock.classList.remove('urgent');
|
|
119
|
+
hudIcons.querySelectorAll('.hud-icon').forEach((el) => el.classList.remove('collected', 'just'));
|
|
120
|
+
// Clean any in-flight clicked hexes from a previous round
|
|
121
|
+
container.querySelectorAll('.h.clicking').forEach((el) => el.remove());
|
|
122
|
+
updateHud();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
window.addEventListener('connext:gamereplay', (e) => {
|
|
126
|
+
if (e.detail && e.detail.id === 'hexrain') resetGame();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ===== Spawn loop. Idle (pre-game): slow constant rain.
|
|
130
|
+
// Once the game starts, spawn rate + fall speed ramp with progress. =====
|
|
131
|
+
function progress() {
|
|
132
|
+
return game.started ? (GAME_SECONDS - game.timeLeft) / GAME_SECONDS : 0;
|
|
133
|
+
}
|
|
134
|
+
function getSpawnInterval() {
|
|
135
|
+
if (!game.started) return rand(900, 1400);
|
|
136
|
+
return Math.max(280, 1200 - progress() * 920);
|
|
137
|
+
}
|
|
138
|
+
function getFallDuration() {
|
|
139
|
+
// Idle: 38-55s. Game peak: 14-22s.
|
|
140
|
+
const base = 50 - progress() * 30;
|
|
141
|
+
return Math.max(12, base + rand(-4, 4));
|
|
142
|
+
}
|
|
143
|
+
function pickAppId() {
|
|
144
|
+
// Bias spawns toward apps the player hasn't collected yet so
|
|
145
|
+
// they get a fair shot at finding all twelve in 60s.
|
|
146
|
+
const uncollected = [];
|
|
147
|
+
for (let i = 0; i < APP_COUNT; i++) if (!game.collected.has(i)) uncollected.push(i);
|
|
148
|
+
if (uncollected.length && Math.random() < 0.7) {
|
|
149
|
+
return uncollected[Math.floor(Math.random() * uncollected.length)];
|
|
150
|
+
}
|
|
151
|
+
return Math.floor(Math.random() * APP_COUNT);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function spawnHex(opts) {
|
|
155
|
+
opts = opts || {};
|
|
156
|
+
const appId = opts.appId !== undefined ? opts.appId : pickAppId();
|
|
157
|
+
const size = rand(54, 96);
|
|
158
|
+
const x = rand(8, 92);
|
|
159
|
+
const dur = getFallDuration();
|
|
160
|
+
const delay = opts.delay || 0;
|
|
161
|
+
const isAlt = Math.random() < 0.35;
|
|
162
|
+
|
|
163
|
+
const hex = document.createElement('span');
|
|
164
|
+
hex.className = 'h' + (isAlt ? ' alt' : '');
|
|
165
|
+
hex.style.setProperty('--rain-x', x.toFixed(1) + '%');
|
|
166
|
+
hex.style.setProperty('--rain-size', size.toFixed(1) + 'px');
|
|
167
|
+
hex.style.setProperty('--rain-dur', dur.toFixed(1) + 's');
|
|
168
|
+
if (delay) hex.style.setProperty('--rain-delay', delay.toFixed(1) + 's');
|
|
169
|
+
hex.style.setProperty('--rain-end', (container.clientHeight + size + 32).toFixed(1) + 'px');
|
|
170
|
+
hex.dataset.app = String(appId);
|
|
171
|
+
hex.dataset.size = size.toFixed(1);
|
|
172
|
+
hex.title = APP_NAMES[appId] + ', click to collect';
|
|
173
|
+
hex.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true">' + ICONS[appId] + '</svg>';
|
|
174
|
+
|
|
175
|
+
if (reduceMotion) {
|
|
176
|
+
// Static placement, rain doesn't move but hexes are still clickable.
|
|
177
|
+
hex.style.top = rand(8, 88).toFixed(1) + '%';
|
|
178
|
+
hex.style.transform = 'translate(-50%, -50%)';
|
|
179
|
+
hex.style.opacity = '1';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Once the fall keyframe completes, GC the element. (Click-out
|
|
183
|
+
// animations dispatch their own `finished` cleanup below.)
|
|
184
|
+
hex.addEventListener('animationend', (e) => {
|
|
185
|
+
if (e.animationName === 'hex-rain-fall' && hex.parentNode) hex.remove();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
container.appendChild(hex);
|
|
189
|
+
return hex;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function scheduleNextSpawn() {
|
|
193
|
+
spawnTimer = setTimeout(() => {
|
|
194
|
+
spawnHex();
|
|
195
|
+
scheduleNextSpawn();
|
|
196
|
+
}, getSpawnInterval());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Pre-populate so the rain looks alive immediately (negative delay
|
|
200
|
+
// = hexes start mid-flight).
|
|
201
|
+
const initialPool = reduceMotion ? 9 : 14;
|
|
202
|
+
for (let i = 0; i < initialPool; i++) {
|
|
203
|
+
const dur = rand(38, 60);
|
|
204
|
+
spawnHex({ delay: -rand(0, dur) });
|
|
205
|
+
}
|
|
206
|
+
if (!reduceMotion) scheduleNextSpawn();
|
|
207
|
+
|
|
208
|
+
// ===== Click handling, toggles the clicked hex's app on/off.
|
|
209
|
+
// Uses event delegation so spawned hexes don't need re-wiring. =====
|
|
210
|
+
container.addEventListener('click', (e) => {
|
|
211
|
+
const hex = e.target.closest('.h');
|
|
212
|
+
if (!hex || hex.classList.contains('clicking')) return;
|
|
213
|
+
const appId = parseInt(hex.dataset.app, 10);
|
|
214
|
+
if (Number.isNaN(appId)) return;
|
|
215
|
+
|
|
216
|
+
if (game.over) return; // post-end clicks ignored; rain still drips
|
|
217
|
+
if (!game.started) startGame();
|
|
218
|
+
|
|
219
|
+
const wasCollected = game.collected.has(appId);
|
|
220
|
+
const slot = hudIcons.children[appId];
|
|
221
|
+
if (wasCollected) {
|
|
222
|
+
game.collected.delete(appId);
|
|
223
|
+
slot.classList.remove('collected', 'just');
|
|
224
|
+
flashCounter('down');
|
|
225
|
+
} else {
|
|
226
|
+
game.collected.add(appId);
|
|
227
|
+
slot.classList.add('collected');
|
|
228
|
+
slot.classList.remove('just'); void slot.offsetWidth; slot.classList.add('just');
|
|
229
|
+
flashCounter('up');
|
|
230
|
+
}
|
|
231
|
+
animateHexOut(hex, !wasCollected);
|
|
232
|
+
updateHud();
|
|
233
|
+
|
|
234
|
+
if (game.collected.size >= APP_COUNT) endGame(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Freeze the falling hex at its current Y (read from computed
|
|
238
|
+
// transform), then run a WAAPI scale + fade for collect (orange
|
|
239
|
+
// pop) or deselect (red shake).
|
|
240
|
+
function animateHexOut(hex, isCollect) {
|
|
241
|
+
hex.classList.add('clicking');
|
|
242
|
+
let y = 0;
|
|
243
|
+
try {
|
|
244
|
+
const m = new DOMMatrixReadOnly(getComputedStyle(hex).transform);
|
|
245
|
+
y = m.m42 || 0;
|
|
246
|
+
} catch (err) { /* keep y=0 */ }
|
|
247
|
+
hex.style.animation = 'none';
|
|
248
|
+
hex.style.transform = 'translate(-50%, ' + y + 'px)';
|
|
249
|
+
void hex.offsetWidth;
|
|
250
|
+
|
|
251
|
+
const dur = 380;
|
|
252
|
+
const collectKeyframes = [
|
|
253
|
+
{ transform: 'translate(-50%, ' + y + 'px) scale(1)', filter: 'none', opacity: 1 },
|
|
254
|
+
{ transform: 'translate(-50%, ' + y + 'px) scale(1.5)', filter: 'brightness(1.6) drop-shadow(0 0 14px rgba(255,107,0,0.95))', opacity: 1, offset: 0.5 },
|
|
255
|
+
{ transform: 'translate(-50%, ' + y + 'px) scale(1.8)', opacity: 0 },
|
|
256
|
+
];
|
|
257
|
+
const deselectKeyframes = [
|
|
258
|
+
{ transform: 'translate(-50%, ' + y + 'px) scale(1) rotate(0deg)', filter: 'none' },
|
|
259
|
+
{ transform: 'translate(-50%, ' + y + 'px) scale(0.85) rotate(-9deg)', filter: 'hue-rotate(-100deg) brightness(1.5)', offset: 0.3 },
|
|
260
|
+
{ transform: 'translate(-50%, ' + y + 'px) scale(0.85) rotate(9deg)', filter: 'hue-rotate(-100deg) brightness(1.5)', offset: 0.6 },
|
|
261
|
+
{ transform: 'translate(-50%, ' + y + 'px) scale(0.7) rotate(0deg)', opacity: 0 },
|
|
262
|
+
];
|
|
263
|
+
const anim = hex.animate(
|
|
264
|
+
isCollect ? collectKeyframes : deselectKeyframes,
|
|
265
|
+
{ duration: dur, easing: 'ease-out', fill: 'forwards' }
|
|
266
|
+
);
|
|
267
|
+
anim.finished.then(() => hex.remove()).catch(() => {});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function init() {
|
|
272
|
+
document.querySelectorAll('.hex-rain').forEach(hydrateContainer);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* SPA-friendly: expose so React/MDX components can re-trigger after
|
|
276
|
+
route changes without double-hydrating already-hydrated nodes. */
|
|
277
|
+
window.HexRain = {hydrate: init};
|
|
278
|
+
|
|
279
|
+
if (document.readyState === 'loading') {
|
|
280
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
281
|
+
} else {
|
|
282
|
+
init();
|
|
283
|
+
}
|
|
284
|
+
})();
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/* Kade Cyclist minigame styles. Pairs with kade-cyclist.js. The stage
|
|
2
|
+
overlays the .kade strip; while the game is active the kade flips to
|
|
3
|
+
overflow: visible so the HUD and game-over panel can extend above
|
|
4
|
+
into the skyline area. Hazards are absolutely positioned within the
|
|
5
|
+
track and animated leftward via the kc-drift-l keyframe (duration is
|
|
6
|
+
set inline by the script so the difficulty ramp can shorten it).
|
|
7
|
+
Tokens used: --c-cobalt-900, --c-cobalt-700, --c-orange-knvb,
|
|
8
|
+
--conduction-typography-font-family-body. */
|
|
9
|
+
|
|
10
|
+
/* Active state: let the HUD and game-over banner rise above the kade. */
|
|
11
|
+
.canal-footer .kade:has(.kc-stage) { overflow: visible; }
|
|
12
|
+
|
|
13
|
+
.kc-stage {
|
|
14
|
+
position: absolute;
|
|
15
|
+
inset: 0;
|
|
16
|
+
z-index: 5;
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
font-family: var(--conduction-typography-font-family-body, 'Figtree', system-ui, sans-serif);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* ============ Track + lanes ============ */
|
|
22
|
+
|
|
23
|
+
.kc-track {
|
|
24
|
+
position: absolute;
|
|
25
|
+
inset: 0;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
pointer-events: none;
|
|
28
|
+
}
|
|
29
|
+
.kc-lane {
|
|
30
|
+
position: absolute;
|
|
31
|
+
left: 0; right: 0;
|
|
32
|
+
height: 50%;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
/* Subtle dashed divider hint between lanes once the game is on */
|
|
35
|
+
}
|
|
36
|
+
.kc-lane-top { top: 0; }
|
|
37
|
+
.kc-lane-bottom { bottom: 0; }
|
|
38
|
+
/* Lane divider — kept always visible during gameplay (more prominent
|
|
39
|
+
than the original ambient kade) so the player can read which lane
|
|
40
|
+
each hazard is in at a glance. */
|
|
41
|
+
.kc-track::before {
|
|
42
|
+
content: "";
|
|
43
|
+
position: absolute;
|
|
44
|
+
left: 0; right: 0;
|
|
45
|
+
top: 50%;
|
|
46
|
+
height: 2px;
|
|
47
|
+
margin-top: -1px;
|
|
48
|
+
background: repeating-linear-gradient(
|
|
49
|
+
to right,
|
|
50
|
+
rgba(10, 23, 47, 0.45) 0 8px,
|
|
51
|
+
transparent 8px 14px
|
|
52
|
+
);
|
|
53
|
+
pointer-events: none;
|
|
54
|
+
z-index: 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ============ Player ============ */
|
|
58
|
+
|
|
59
|
+
.kc-player {
|
|
60
|
+
position: absolute;
|
|
61
|
+
/* The runtime sets --kc-player-x to the centre x of the blue
|
|
62
|
+
Conduction house in the skyline, so the cyclist visually sits
|
|
63
|
+
under the brand-anchor house. Fallback covers the case where the
|
|
64
|
+
skyline hasn't been built yet. */
|
|
65
|
+
left: var(--kc-player-x, 14%);
|
|
66
|
+
width: 22px;
|
|
67
|
+
height: 16px;
|
|
68
|
+
z-index: 6;
|
|
69
|
+
transition: top 220ms cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
70
|
+
bottom 220ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
71
|
+
filter: drop-shadow(0 1px 0 rgba(247, 127, 14, 0.4));
|
|
72
|
+
}
|
|
73
|
+
.kc-player[data-lane="top"] { top: 0px; bottom: auto; }
|
|
74
|
+
.kc-player[data-lane="bottom"] { bottom: 0px; top: auto; }
|
|
75
|
+
.kc-player-svg { display: block; }
|
|
76
|
+
|
|
77
|
+
.kc-player.crashed {
|
|
78
|
+
animation: kc-crash 480ms ease both;
|
|
79
|
+
}
|
|
80
|
+
@keyframes kc-crash {
|
|
81
|
+
0% { transform: rotate(0deg) translateY(0); }
|
|
82
|
+
20% { transform: rotate(-18deg) translateY(-3px); }
|
|
83
|
+
50% { transform: rotate(28deg) translateY(2px); }
|
|
84
|
+
80% { transform: rotate(-8deg) translateY(0); }
|
|
85
|
+
100% { transform: rotate(20deg) translateY(0); }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ============ Hazards ============ */
|
|
89
|
+
|
|
90
|
+
.kc-hazard {
|
|
91
|
+
position: absolute;
|
|
92
|
+
right: -64px;
|
|
93
|
+
height: 16px;
|
|
94
|
+
z-index: 5;
|
|
95
|
+
animation-name: kc-drift-l;
|
|
96
|
+
animation-timing-function: linear;
|
|
97
|
+
animation-fill-mode: forwards;
|
|
98
|
+
animation-iteration-count: 1;
|
|
99
|
+
}
|
|
100
|
+
.kc-hazard[data-lane="top"] { top: 0px; }
|
|
101
|
+
.kc-hazard[data-lane="bottom"] { bottom: 0px; }
|
|
102
|
+
|
|
103
|
+
@keyframes kc-drift-l {
|
|
104
|
+
from { transform: translateX(0); }
|
|
105
|
+
to { transform: translateX(calc(-100vw - 200px)); }
|
|
106
|
+
}
|
|
107
|
+
.kc-hazard-svg { display: block; }
|
|
108
|
+
|
|
109
|
+
/* ============ HUD ============ */
|
|
110
|
+
|
|
111
|
+
.kc-hud {
|
|
112
|
+
position: absolute;
|
|
113
|
+
top: -28px;
|
|
114
|
+
right: 8px;
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
gap: 8px;
|
|
118
|
+
pointer-events: auto;
|
|
119
|
+
z-index: 10;
|
|
120
|
+
background: rgba(10, 23, 47, 0.92);
|
|
121
|
+
-webkit-backdrop-filter: blur(4px);
|
|
122
|
+
backdrop-filter: blur(4px);
|
|
123
|
+
padding: 4px 8px;
|
|
124
|
+
border-radius: 999px;
|
|
125
|
+
color: white;
|
|
126
|
+
font-size: 12px;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
}
|
|
129
|
+
.kc-score-block {
|
|
130
|
+
display: flex; align-items: baseline; gap: 4px;
|
|
131
|
+
padding: 0 4px;
|
|
132
|
+
}
|
|
133
|
+
.kc-score-num {
|
|
134
|
+
color: var(--c-orange-knvb, #F77F0E);
|
|
135
|
+
font-size: 14px;
|
|
136
|
+
font-weight: 700;
|
|
137
|
+
font-variant-numeric: tabular-nums;
|
|
138
|
+
}
|
|
139
|
+
.kc-score-label {
|
|
140
|
+
font-size: 9px;
|
|
141
|
+
opacity: 0.65;
|
|
142
|
+
text-transform: uppercase;
|
|
143
|
+
letter-spacing: 0.06em;
|
|
144
|
+
font-weight: 500;
|
|
145
|
+
}
|
|
146
|
+
.kc-controls {
|
|
147
|
+
font-size: 10px;
|
|
148
|
+
opacity: 0.7;
|
|
149
|
+
border-left: 1px solid rgba(255, 255, 255, 0.18);
|
|
150
|
+
padding-left: 8px;
|
|
151
|
+
}
|
|
152
|
+
.kc-controls kbd {
|
|
153
|
+
background: rgba(255, 255, 255, 0.16);
|
|
154
|
+
padding: 1px 4px;
|
|
155
|
+
border-radius: 3px;
|
|
156
|
+
font-family: 'JetBrains Mono', 'SF Mono', Menlo, monospace;
|
|
157
|
+
font-size: 10px;
|
|
158
|
+
font-weight: 600;
|
|
159
|
+
}
|
|
160
|
+
.kc-close {
|
|
161
|
+
width: 20px; height: 20px;
|
|
162
|
+
border-radius: 50%;
|
|
163
|
+
background: rgba(255, 255, 255, 0.16);
|
|
164
|
+
border: 0;
|
|
165
|
+
color: white;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
display: grid; place-items: center;
|
|
168
|
+
font-size: 14px;
|
|
169
|
+
line-height: 1;
|
|
170
|
+
font-family: inherit;
|
|
171
|
+
transition: background 150ms ease;
|
|
172
|
+
}
|
|
173
|
+
.kc-close:hover,
|
|
174
|
+
.kc-close:focus-visible {
|
|
175
|
+
background: var(--c-orange-knvb, #F77F0E);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ============ Game over panel ============ */
|
|
179
|
+
|
|
180
|
+
.kc-over {
|
|
181
|
+
position: absolute;
|
|
182
|
+
bottom: 100%;
|
|
183
|
+
left: 50%;
|
|
184
|
+
transform: translateX(-50%);
|
|
185
|
+
margin-bottom: 6px;
|
|
186
|
+
background: var(--c-cobalt-900, #0A172F);
|
|
187
|
+
color: white;
|
|
188
|
+
padding: 14px 24px;
|
|
189
|
+
border-radius: 10px;
|
|
190
|
+
text-align: center;
|
|
191
|
+
pointer-events: auto;
|
|
192
|
+
z-index: 11;
|
|
193
|
+
min-width: 200px;
|
|
194
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
|
195
|
+
animation: kc-pop 360ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
|
196
|
+
}
|
|
197
|
+
@keyframes kc-pop {
|
|
198
|
+
from { opacity: 0; transform: translateX(-50%) translateY(8px) scale(0.85); }
|
|
199
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
|
|
200
|
+
}
|
|
201
|
+
.kc-over-title {
|
|
202
|
+
font-size: 24px;
|
|
203
|
+
font-weight: 700;
|
|
204
|
+
color: var(--c-orange-knvb, #F77F0E);
|
|
205
|
+
margin: 0 0 4px;
|
|
206
|
+
letter-spacing: -0.02em;
|
|
207
|
+
}
|
|
208
|
+
.kc-over-stat {
|
|
209
|
+
font-size: 13px;
|
|
210
|
+
margin: 0 0 14px;
|
|
211
|
+
opacity: 0.85;
|
|
212
|
+
font-weight: 500;
|
|
213
|
+
}
|
|
214
|
+
.kc-over-stat span {
|
|
215
|
+
color: var(--c-orange-knvb, #F77F0E);
|
|
216
|
+
font-weight: 700;
|
|
217
|
+
font-variant-numeric: tabular-nums;
|
|
218
|
+
margin-right: 2px;
|
|
219
|
+
}
|
|
220
|
+
.kc-over button {
|
|
221
|
+
border: 0;
|
|
222
|
+
background: var(--c-orange-knvb, #F77F0E);
|
|
223
|
+
color: white;
|
|
224
|
+
padding: 8px 18px;
|
|
225
|
+
border-radius: 999px;
|
|
226
|
+
font-weight: 600;
|
|
227
|
+
font-size: 13px;
|
|
228
|
+
cursor: pointer;
|
|
229
|
+
font-family: inherit;
|
|
230
|
+
transition: transform 150ms ease, filter 150ms ease;
|
|
231
|
+
}
|
|
232
|
+
.kc-over button:hover,
|
|
233
|
+
.kc-over button:focus-visible {
|
|
234
|
+
transform: scale(1.05);
|
|
235
|
+
filter: brightness(1.08);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* ============ Hover hint on bikes (subtle, only when ambient) ============ */
|
|
239
|
+
|
|
240
|
+
.canal-footer:not(:has(.kc-stage)) .ki-bike-1,
|
|
241
|
+
.canal-footer:not(:has(.kc-stage)) .ki-bike-2 {
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
pointer-events: auto;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* ============ Reduced motion ============ */
|
|
247
|
+
|
|
248
|
+
@media (prefers-reduced-motion: reduce) {
|
|
249
|
+
.kc-player,
|
|
250
|
+
.kc-over,
|
|
251
|
+
.kc-player.crashed {
|
|
252
|
+
transition: none !important;
|
|
253
|
+
animation: none !important;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* ============ Mobile ============ */
|
|
258
|
+
|
|
259
|
+
@media (max-width: 720px) {
|
|
260
|
+
.kc-hud { top: -26px; right: 4px; padding: 3px 6px; }
|
|
261
|
+
.kc-controls { display: none; }
|
|
262
|
+
.kc-over { padding: 10px 16px; min-width: 160px; }
|
|
263
|
+
.kc-over-title { font-size: 18px; }
|
|
264
|
+
}
|