@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,317 @@
|
|
|
1
|
+
/* Clients Flow: continuous-spawn marquee runtime for the Clients hex
|
|
2
|
+
wall. Replaces the old CSS-animated track-with-duplicate approach,
|
|
3
|
+
which had a one-pixel seam at the wrap boundary. Each row is a
|
|
4
|
+
simple relative container; the runtime spawns absolutely-positioned
|
|
5
|
+
<a class="hex"> children on the right, drifts them left at a per-row
|
|
6
|
+
speed via translateX, and removes them once they leave the left
|
|
7
|
+
edge. Logos are picked at random from the supplied client pool,
|
|
8
|
+
avoiding immediate repeats so the same shield doesn't keep flashing
|
|
9
|
+
in one row.
|
|
10
|
+
|
|
11
|
+
Hooks:
|
|
12
|
+
- data-memory-marquee on the marquee element (also serves as the
|
|
13
|
+
Logo Memory game trigger surface)
|
|
14
|
+
- data-clients='[{"src":"...","name":"..."}, ...]' on the marquee
|
|
15
|
+
- data-hex-class on the marquee (optional) — class name for spawned
|
|
16
|
+
hex anchors. CSS Modules hashes the React build's class to
|
|
17
|
+
something like hex_QXzL; pass that through. Falls back to "hex"
|
|
18
|
+
which the preview HTML uses literally.
|
|
19
|
+
- data-row-stagger on a row (optional) — px offset added to the
|
|
20
|
+
row's first hex x. Used by row 2 to slot half-hex into the
|
|
21
|
+
honeycomb stagger between row 1 and row 3.
|
|
22
|
+
- data-row-speed on a row (optional) — drift speed in px/sec.
|
|
23
|
+
Defaults to 50; row 2 defaults to 58 and row 3 to 46 for slight
|
|
24
|
+
visual desync.
|
|
25
|
+
|
|
26
|
+
Public API:
|
|
27
|
+
window.ConductionClientsFlow.hydrate() — find + initialise marquees
|
|
28
|
+
window.ConductionClientsFlow.pause() — freeze drift (used by Logo
|
|
29
|
+
Memory while a game is in
|
|
30
|
+
progress)
|
|
31
|
+
window.ConductionClientsFlow.resume() — un-freeze
|
|
32
|
+
window.ConductionClientsFlow.reset() — clear all hex children
|
|
33
|
+
and re-fill from scratch.
|
|
34
|
+
Logo Memory calls this on
|
|
35
|
+
game tear-down so the
|
|
36
|
+
marquee comes back fresh.
|
|
37
|
+
*/
|
|
38
|
+
(function () {
|
|
39
|
+
const DEFAULT_SPEED = 50;
|
|
40
|
+
/* All rows use the same default speed so honeycomb alignment is
|
|
41
|
+
preserved (rows 1 and 3 stay column-aligned, row 2 stays at the
|
|
42
|
+
half-hex offset). Per-row data-row-speed still lets a host page
|
|
43
|
+
override this if that's the look they want. The "more dynamic"
|
|
44
|
+
feel comes from random-logo selection on each spawn, not from
|
|
45
|
+
per-row drift desync. */
|
|
46
|
+
const DEFAULT_ROW_SPEEDS = [50, 50, 50];
|
|
47
|
+
const RECENT_REPEAT_AVOID = 5;
|
|
48
|
+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
49
|
+
|
|
50
|
+
/* All running marquees. Each entry holds the per-row state for that
|
|
51
|
+
marquee plus the rAF id so global pause/resume can address them. */
|
|
52
|
+
const REGISTRY = [];
|
|
53
|
+
let rafId = null;
|
|
54
|
+
|
|
55
|
+
function readPx(value, fallback) {
|
|
56
|
+
const n = parseFloat(value);
|
|
57
|
+
return isFinite(n) && n >= 0 ? n : fallback;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pickLogo(state, clients) {
|
|
61
|
+
let pick;
|
|
62
|
+
let attempts = 0;
|
|
63
|
+
do {
|
|
64
|
+
pick = clients[(Math.random() * clients.length) | 0];
|
|
65
|
+
attempts++;
|
|
66
|
+
} while (state.recent.includes(pick.src) && attempts < 10);
|
|
67
|
+
state.recent.push(pick.src);
|
|
68
|
+
if (state.recent.length > RECENT_REPEAT_AVOID) state.recent.shift();
|
|
69
|
+
return pick;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function spawnHex(state, baseX, scrollPx) {
|
|
73
|
+
/* baseX is the hex's "world" x at scrollPx=0. Current visible
|
|
74
|
+
position = baseX - scrollPx. Storing baseX lets every tick
|
|
75
|
+
recompute current x from the shared scroll accumulator without
|
|
76
|
+
per-hex per-row drift accumulation. */
|
|
77
|
+
const logo = pickLogo(state, state.clients);
|
|
78
|
+
const x = baseX - (scrollPx || 0);
|
|
79
|
+
const a = document.createElement('a');
|
|
80
|
+
a.className = state.hexClass;
|
|
81
|
+
a.href = '#';
|
|
82
|
+
a.setAttribute('aria-label', logo.name);
|
|
83
|
+
a.style.position = 'absolute';
|
|
84
|
+
a.style.top = '0';
|
|
85
|
+
a.style.left = '0';
|
|
86
|
+
a.style.transform = 'translateX(' + x + 'px)';
|
|
87
|
+
a.style.willChange = 'transform';
|
|
88
|
+
const img = document.createElement('img');
|
|
89
|
+
img.src = logo.src;
|
|
90
|
+
img.alt = logo.name;
|
|
91
|
+
img.title = logo.name;
|
|
92
|
+
img.loading = 'lazy';
|
|
93
|
+
if (state.hexLogoClass) img.className = state.hexLogoClass;
|
|
94
|
+
a.appendChild(img);
|
|
95
|
+
a.addEventListener('click', function (e) { e.preventDefault(); });
|
|
96
|
+
state.row.appendChild(a);
|
|
97
|
+
state.hexes.push({ el: a, baseX: baseX, x: x });
|
|
98
|
+
return a;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function initialFill(state) {
|
|
102
|
+
/* Pre-populate the row with hexes from its stagger offset out
|
|
103
|
+
past the right edge, so users never see an empty row. baseX
|
|
104
|
+
starts at stagger; advance by pitch and spawn while still in
|
|
105
|
+
the right-edge spawn zone. */
|
|
106
|
+
const containerWidth = state.row.getBoundingClientRect().width;
|
|
107
|
+
let baseX = state.stagger;
|
|
108
|
+
while (baseX < containerWidth + state.hexW) {
|
|
109
|
+
spawnHex(state, baseX, 0);
|
|
110
|
+
baseX += state.pitch;
|
|
111
|
+
}
|
|
112
|
+
state.cursorBaseX = baseX;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function tickMarquee(marqueeState, scrollPx) {
|
|
116
|
+
/* `scrollPx` is the cumulative drift since the marquee started, in
|
|
117
|
+
pixels, shared across all rows. Each hex stores its spawn-time
|
|
118
|
+
baseline (h.baseX = the x at scroll = 0); current position is
|
|
119
|
+
baseX - scrollPx. Going via this single shared accumulator
|
|
120
|
+
guarantees rows stay perfectly in lockstep — there is no per-
|
|
121
|
+
row dt to diverge on. */
|
|
122
|
+
marqueeState.rows.forEach(function (state) {
|
|
123
|
+
if (state.paused) return;
|
|
124
|
+
const containerWidth = state.row.getBoundingClientRect().width;
|
|
125
|
+
/* Update every hex's transform from its baseline. */
|
|
126
|
+
for (let i = 0; i < state.hexes.length; i++) {
|
|
127
|
+
const h = state.hexes[i];
|
|
128
|
+
h.x = h.baseX - scrollPx;
|
|
129
|
+
h.el.style.transform = 'translateX(' + h.x + 'px)';
|
|
130
|
+
}
|
|
131
|
+
/* Cull anything that fully left the viewport on the left. */
|
|
132
|
+
for (let i = state.hexes.length - 1; i >= 0; i--) {
|
|
133
|
+
if (state.hexes[i].x + state.hexW < -16) {
|
|
134
|
+
state.hexes[i].el.remove();
|
|
135
|
+
state.hexes.splice(i, 1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/* Cursor is also baseline-relative: cursor world position =
|
|
139
|
+
cursorBaseX - scrollPx. Spawn while it falls inside the spawn
|
|
140
|
+
zone (the right-edge buffer). */
|
|
141
|
+
let safety = 0;
|
|
142
|
+
while ((state.cursorBaseX - scrollPx) < containerWidth + state.hexW && safety < 50) {
|
|
143
|
+
const newBaseX = state.cursorBaseX;
|
|
144
|
+
spawnHex(state, newBaseX, scrollPx);
|
|
145
|
+
state.cursorBaseX += state.pitch;
|
|
146
|
+
safety++;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Single shared scroll accumulator for all marquees + rows. Stays
|
|
152
|
+
constant while paused, advances by speed*dt while running. Each
|
|
153
|
+
hex's visible position is baseX - scrollPx, so all hexes (any
|
|
154
|
+
row, any marquee) move in lockstep. */
|
|
155
|
+
let scrollStart = null; // ts when the current run began
|
|
156
|
+
let scrollAccum = 0; // total scroll px captured at last pause
|
|
157
|
+
let running = true;
|
|
158
|
+
|
|
159
|
+
function currentScroll(timestamp) {
|
|
160
|
+
const speed = REGISTRY[0]?.rows[0]?.speed ?? DEFAULT_SPEED;
|
|
161
|
+
if (!running) return scrollAccum;
|
|
162
|
+
if (scrollStart === null) scrollStart = timestamp;
|
|
163
|
+
return scrollAccum + (timestamp - scrollStart) * speed / 1000;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function tick(timestamp) {
|
|
167
|
+
const scrollPx = currentScroll(timestamp);
|
|
168
|
+
REGISTRY.forEach(function (m) { tickMarquee(m, scrollPx); });
|
|
169
|
+
rafId = requestAnimationFrame(tick);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function startLoop() {
|
|
173
|
+
if (rafId !== null) return;
|
|
174
|
+
lastTimestamp = null;
|
|
175
|
+
rafId = requestAnimationFrame(tick);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function hydrateMarquee(marquee) {
|
|
179
|
+
if (marquee.dataset.flowHydrated === '1') return;
|
|
180
|
+
marquee.dataset.flowHydrated = '1';
|
|
181
|
+
|
|
182
|
+
let clients;
|
|
183
|
+
/* Two ways to pass the client pool: a JSON string in
|
|
184
|
+
data-clients (compact, used by the React build) or an inner
|
|
185
|
+
<script type="application/json" data-clients-source> (less
|
|
186
|
+
quote-noise, used by the preview HTML). */
|
|
187
|
+
try { clients = JSON.parse(marquee.dataset.clients || '[]'); }
|
|
188
|
+
catch (e) { clients = []; }
|
|
189
|
+
if (!clients.length) {
|
|
190
|
+
const inline = marquee.querySelector('script[type="application/json"][data-clients-source]');
|
|
191
|
+
if (inline) {
|
|
192
|
+
try { clients = JSON.parse(inline.textContent); }
|
|
193
|
+
catch (e) { clients = []; }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!clients || !clients.length) return;
|
|
197
|
+
|
|
198
|
+
const hexClass = marquee.dataset.hexClass || 'hex';
|
|
199
|
+
const hexLogoClass = marquee.dataset.hexLogoClass || 'hexLogo';
|
|
200
|
+
const hexW = readPx(getComputedStyle(marquee).getPropertyValue('--hex-w'), 120);
|
|
201
|
+
const gap = readPx(getComputedStyle(marquee).getPropertyValue('--gap'), 8);
|
|
202
|
+
const pitch = hexW + gap;
|
|
203
|
+
|
|
204
|
+
const rowEls = Array.from(marquee.querySelectorAll('[data-flow-row]'));
|
|
205
|
+
if (!rowEls.length) return;
|
|
206
|
+
|
|
207
|
+
const rows = rowEls.map(function (row, idx) {
|
|
208
|
+
const stagger = readPx(row.dataset.rowStagger, idx === 1 ? pitch / 2 : 0);
|
|
209
|
+
const speedAttr = parseFloat(row.dataset.rowSpeed);
|
|
210
|
+
const speed = isFinite(speedAttr) && speedAttr > 0
|
|
211
|
+
? speedAttr
|
|
212
|
+
: (DEFAULT_ROW_SPEEDS[idx] || DEFAULT_SPEED);
|
|
213
|
+
return {
|
|
214
|
+
row: row,
|
|
215
|
+
stagger: stagger,
|
|
216
|
+
speed: reduceMotion ? 0 : speed,
|
|
217
|
+
hexW: hexW,
|
|
218
|
+
pitch: pitch,
|
|
219
|
+
clients: clients,
|
|
220
|
+
hexClass: hexClass,
|
|
221
|
+
hexLogoClass: hexLogoClass,
|
|
222
|
+
hexes: [],
|
|
223
|
+
cursorBaseX: stagger,
|
|
224
|
+
recent: [],
|
|
225
|
+
paused: false,
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const marqueeState = { marquee: marquee, rows: rows };
|
|
230
|
+
rows.forEach(initialFill);
|
|
231
|
+
REGISTRY.push(marqueeState);
|
|
232
|
+
if (!reduceMotion) startLoop();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function hydrate() {
|
|
236
|
+
document.querySelectorAll('[data-memory-marquee]').forEach(hydrateMarquee);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function pause() {
|
|
240
|
+
if (!running) return;
|
|
241
|
+
/* Capture scroll into the accumulator and FULLY halt the rAF.
|
|
242
|
+
Setting paused flags isn't enough — Logo Memory snapshots and
|
|
243
|
+
lifts hex positions, and any subsequent tick that reads/writes
|
|
244
|
+
a transform on a now-lifted hex would corrupt its placement. */
|
|
245
|
+
scrollAccum = currentScroll(performance.now());
|
|
246
|
+
scrollStart = null;
|
|
247
|
+
running = false;
|
|
248
|
+
if (rafId !== null) {
|
|
249
|
+
cancelAnimationFrame(rafId);
|
|
250
|
+
rafId = null;
|
|
251
|
+
}
|
|
252
|
+
REGISTRY.forEach(function (m) {
|
|
253
|
+
m.rows.forEach(function (r) { r.paused = true; });
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function resume() {
|
|
258
|
+
if (running) return;
|
|
259
|
+
scrollStart = null; // first tick after resume re-anchors
|
|
260
|
+
running = true;
|
|
261
|
+
REGISTRY.forEach(function (m) {
|
|
262
|
+
m.rows.forEach(function (r) { r.paused = false; });
|
|
263
|
+
});
|
|
264
|
+
if (!reduceMotion) startLoop();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function reset() {
|
|
268
|
+
/* Halt rAF before mutating state so a stray tick can't write to
|
|
269
|
+
elements we're about to remove. */
|
|
270
|
+
if (rafId !== null) {
|
|
271
|
+
cancelAnimationFrame(rafId);
|
|
272
|
+
rafId = null;
|
|
273
|
+
}
|
|
274
|
+
scrollAccum = 0;
|
|
275
|
+
scrollStart = null;
|
|
276
|
+
running = true;
|
|
277
|
+
REGISTRY.forEach(function (m) {
|
|
278
|
+
m.rows.forEach(function (r) {
|
|
279
|
+
r.hexes.forEach(function (h) { h.el.remove(); });
|
|
280
|
+
r.hexes.length = 0;
|
|
281
|
+
r.recent.length = 0;
|
|
282
|
+
r.cursorBaseX = r.stagger;
|
|
283
|
+
r.paused = false;
|
|
284
|
+
initialFill(r);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
if (!reduceMotion) startLoop();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
window.ConductionClientsFlow = window.ConductionClientsFlow || {};
|
|
291
|
+
window.ConductionClientsFlow.hydrate = hydrate;
|
|
292
|
+
window.ConductionClientsFlow.pause = pause;
|
|
293
|
+
window.ConductionClientsFlow.resume = resume;
|
|
294
|
+
window.ConductionClientsFlow.reset = reset;
|
|
295
|
+
window.ConductionClientsFlow._inspect = function () {
|
|
296
|
+
return REGISTRY.map(function (m) {
|
|
297
|
+
return {
|
|
298
|
+
rows: m.rows.map(function (r) {
|
|
299
|
+
return {
|
|
300
|
+
stagger: r.stagger,
|
|
301
|
+
speed: r.speed,
|
|
302
|
+
cursorBaseX: r.cursorBaseX,
|
|
303
|
+
hexCount: r.hexes.length,
|
|
304
|
+
baseXs: r.hexes.map(function (h) { return h.baseX; }),
|
|
305
|
+
currentXs: r.hexes.map(function (h) { return Math.round(h.x * 100) / 100; }),
|
|
306
|
+
};
|
|
307
|
+
}),
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
if (document.readyState === 'loading') {
|
|
313
|
+
document.addEventListener('DOMContentLoaded', hydrate);
|
|
314
|
+
} else {
|
|
315
|
+
hydrate();
|
|
316
|
+
}
|
|
317
|
+
})();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
Conduction background — reusable parallax honeycomb depth field
|
|
3
|
+
------------------------------------------------------------
|
|
4
|
+
Drop <div class="conduction-bg" aria-hidden="true"></div> as
|
|
5
|
+
the first child of any position:relative + overflow:hidden
|
|
6
|
+
container. The hydrator (conduction-bg.js) generates
|
|
7
|
+
depth-tiered hexes that parallax on scroll and slowly
|
|
8
|
+
breathe in opacity.
|
|
9
|
+
|
|
10
|
+
Themes (set by class on .conduction-bg):
|
|
11
|
+
(default) white hexes — for cobalt / cobalt-800 surfaces
|
|
12
|
+
.conduction-bg.on-light cobalt hexes — for white / cobalt-50 surfaces
|
|
13
|
+
|
|
14
|
+
Per huisstijl: hexes are always pointy-top, never rotated.
|
|
15
|
+
Requires tokens.css to provide --hex-pointy-top, --c-blue-cobalt,
|
|
16
|
+
and --c-orange-knvb.
|
|
17
|
+
============================================================ */
|
|
18
|
+
.conduction-bg {
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset: 0;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
--cbg-hex: #ffffff;
|
|
24
|
+
--cbg-accent: var(--c-orange-knvb);
|
|
25
|
+
}
|
|
26
|
+
.conduction-bg.on-light {
|
|
27
|
+
--cbg-hex: var(--c-blue-cobalt);
|
|
28
|
+
--cbg-accent: var(--c-orange-knvb);
|
|
29
|
+
}
|
|
30
|
+
.conduction-bg .hex {
|
|
31
|
+
position: absolute;
|
|
32
|
+
clip-path: var(--hex-pointy-top);
|
|
33
|
+
background: var(--cbg-hex);
|
|
34
|
+
will-change: transform, opacity;
|
|
35
|
+
transform: translate(-50%, -50%);
|
|
36
|
+
animation: conduction-hex-breathe var(--cbg-dur, 14s) ease-in-out var(--cbg-delay, 0s) infinite;
|
|
37
|
+
}
|
|
38
|
+
.conduction-bg .hex.accent {
|
|
39
|
+
background: var(--cbg-accent);
|
|
40
|
+
}
|
|
41
|
+
@keyframes conduction-hex-breathe {
|
|
42
|
+
0%, 100% { opacity: var(--cbg-opa-min, 0.04); }
|
|
43
|
+
50% { opacity: var(--cbg-opa-max, 0.18); }
|
|
44
|
+
}
|
|
45
|
+
@media (prefers-reduced-motion: reduce) {
|
|
46
|
+
.conduction-bg .hex {
|
|
47
|
+
animation: none;
|
|
48
|
+
opacity: var(--cbg-opa-max, 0.12);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* Conduction background — parallax honeycomb hydrator.
|
|
2
|
+
|
|
3
|
+
Walks every .conduction-bg in the document, injects depth-tiered hex
|
|
4
|
+
spans, and wires them up to scroll-based parallax. Smaller hexes have
|
|
5
|
+
smaller depth → less translate per scroll-pixel → read as further away.
|
|
6
|
+
|
|
7
|
+
Per huisstijl: hexes are pointy-top only, never rotated.
|
|
8
|
+
|
|
9
|
+
Hosts opt in by adding <div class="conduction-bg"></div> inside any
|
|
10
|
+
position:relative + overflow:hidden container, plus this script tag
|
|
11
|
+
and conduction-bg.css. The script auto-runs on DOMContentLoaded and
|
|
12
|
+
is idempotent — calling init() twice on the same element is a no-op.
|
|
13
|
+
|
|
14
|
+
Optional dataset hooks on .conduction-bg:
|
|
15
|
+
data-count="N" — override default hex count (default 18)
|
|
16
|
+
*/
|
|
17
|
+
(function () {
|
|
18
|
+
const TIERS = [
|
|
19
|
+
{ d: 0.10, sMin: 32, sMax: 56, oMin: 0.03, oMax: 0.10, w: 5 },
|
|
20
|
+
{ d: 0.25, sMin: 56, sMax: 96, oMin: 0.04, oMax: 0.13, w: 4 },
|
|
21
|
+
{ d: 0.45, sMin: 96, sMax: 150, oMin: 0.05, oMax: 0.16, w: 3 },
|
|
22
|
+
{ d: 0.65, sMin: 150, sMax: 220, oMin: 0.07, oMax: 0.18, w: 2 },
|
|
23
|
+
{ d: 0.85, sMin: 220, sMax: 300, oMin: 0.08, oMax: 0.20, w: 1 },
|
|
24
|
+
];
|
|
25
|
+
const PARALLAX_FACTOR = 0.4;
|
|
26
|
+
const TIER_TOTAL = TIERS.reduce((s, t) => s + t.w, 0);
|
|
27
|
+
|
|
28
|
+
const rand = (a, b) => Math.random() * (b - a) + a;
|
|
29
|
+
const pickTier = () => {
|
|
30
|
+
let r = Math.random() * TIER_TOTAL;
|
|
31
|
+
for (const t of TIERS) { if ((r -= t.w) <= 0) return t; }
|
|
32
|
+
return TIERS[0];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function hydrate(bg) {
|
|
36
|
+
if (bg.dataset.cbgHydrated === '1') return;
|
|
37
|
+
bg.dataset.cbgHydrated = '1';
|
|
38
|
+
|
|
39
|
+
const host = bg.parentElement;
|
|
40
|
+
if (!host) return;
|
|
41
|
+
const onLight = bg.classList.contains('on-light');
|
|
42
|
+
/* Cobalt-on-white reads stronger per opacity-unit than white-on-cobalt,
|
|
43
|
+
so scale back the on-light range to keep the field a soft veil. */
|
|
44
|
+
const opaScale = onLight ? 0.55 : 1.0;
|
|
45
|
+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
46
|
+
|
|
47
|
+
const hexCount = parseInt(bg.dataset.count, 10) || 18;
|
|
48
|
+
const accentIndex = Math.floor(hexCount / 2);
|
|
49
|
+
|
|
50
|
+
const hexes = [];
|
|
51
|
+
for (let i = 0; i < hexCount; i++) {
|
|
52
|
+
const tier = pickTier();
|
|
53
|
+
const size = rand(tier.sMin, tier.sMax);
|
|
54
|
+
const isAccent = (i === accentIndex);
|
|
55
|
+
const hex = document.createElement('span');
|
|
56
|
+
hex.className = isAccent ? 'hex accent' : 'hex';
|
|
57
|
+
hex.style.width = size + 'px';
|
|
58
|
+
hex.style.height = (size * 1.1547) + 'px';
|
|
59
|
+
hex.style.left = rand(-5, 105) + '%';
|
|
60
|
+
hex.style.top = rand(-25, 125) + '%';
|
|
61
|
+
hex.style.setProperty('--cbg-opa-min', (tier.oMin * opaScale).toFixed(3));
|
|
62
|
+
hex.style.setProperty(
|
|
63
|
+
'--cbg-opa-max',
|
|
64
|
+
((isAccent ? Math.min(tier.oMax + 0.05, 0.28) : tier.oMax) * opaScale).toFixed(3)
|
|
65
|
+
);
|
|
66
|
+
hex.style.setProperty('--cbg-dur', rand(11, 18).toFixed(1) + 's');
|
|
67
|
+
hex.style.setProperty('--cbg-delay', rand(-18, 0).toFixed(1) + 's');
|
|
68
|
+
hex.dataset.depth = tier.d;
|
|
69
|
+
bg.appendChild(hex);
|
|
70
|
+
hexes.push(hex);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (reduceMotion) return;
|
|
74
|
+
|
|
75
|
+
let visible = false;
|
|
76
|
+
let ticking = false;
|
|
77
|
+
|
|
78
|
+
const update = () => {
|
|
79
|
+
const rect = host.getBoundingClientRect();
|
|
80
|
+
const offset = (rect.top + rect.height / 2) - window.innerHeight / 2;
|
|
81
|
+
for (const h of hexes) {
|
|
82
|
+
const d = parseFloat(h.dataset.depth);
|
|
83
|
+
const ty = -offset * d * PARALLAX_FACTOR;
|
|
84
|
+
h.style.transform = `translate(-50%, -50%) translateY(${ty.toFixed(1)}px)`;
|
|
85
|
+
}
|
|
86
|
+
ticking = false;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const onScroll = () => {
|
|
90
|
+
if (!visible || ticking) return;
|
|
91
|
+
ticking = true;
|
|
92
|
+
requestAnimationFrame(update);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const io = new IntersectionObserver(([entry]) => {
|
|
96
|
+
visible = entry.isIntersecting;
|
|
97
|
+
if (visible) update();
|
|
98
|
+
}, { rootMargin: '300px 0px' });
|
|
99
|
+
io.observe(host);
|
|
100
|
+
|
|
101
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
102
|
+
window.addEventListener('resize', onScroll, { passive: true });
|
|
103
|
+
update();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function init() {
|
|
107
|
+
/* hydrate() has its own dataset.cbgHydrated guard so calling it
|
|
108
|
+
repeatedly across SPA route changes is a no-op for already-
|
|
109
|
+
mounted nodes. */
|
|
110
|
+
document.querySelectorAll('.conduction-bg').forEach(hydrate);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* SPA-friendly: expose so React/MDX components can re-trigger after
|
|
114
|
+
route changes without double-hydrating already-hydrated nodes. */
|
|
115
|
+
window.ConductionBg = {hydrate: init};
|
|
116
|
+
|
|
117
|
+
if (document.readyState === 'loading') {
|
|
118
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
119
|
+
} else {
|
|
120
|
+
init();
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
|
|
2
|
+
/* ===== Hex rain, slow vertical fall of icon hexes =====
|
|
3
|
+
The IIFE at the end of the page hydrates this with icon-bearing
|
|
4
|
+
hexes that fall finite cycles (one pass each) and get garbage-
|
|
5
|
+
collected on `animationend` so the spawn loop can ramp without
|
|
6
|
+
stacking density. Each hex carries an app id 0-11 in dataset.app
|
|
7
|
+
and clicking it toggles that app's collection state in the game.
|
|
8
|
+
Always pointy-top per huisstijl. */
|
|
9
|
+
.hex-rain {
|
|
10
|
+
position: relative;
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
min-height: 520px;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
.hex-rain .h {
|
|
17
|
+
position: absolute;
|
|
18
|
+
top: 0;
|
|
19
|
+
left: var(--rain-x, 50%);
|
|
20
|
+
width: var(--rain-size, 80px);
|
|
21
|
+
height: calc(var(--rain-size, 80px) * 1.1547);
|
|
22
|
+
clip-path: var(--hex-pointy-top);
|
|
23
|
+
background: var(--c-blue-cobalt);
|
|
24
|
+
color: white;
|
|
25
|
+
display: flex; align-items: center; justify-content: center;
|
|
26
|
+
will-change: transform, opacity;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
animation: hex-rain-fall var(--rain-dur, 28s) linear var(--rain-delay, 0s) 1 forwards;
|
|
29
|
+
}
|
|
30
|
+
.hex-rain .h.alt { background: var(--c-cobalt-700); }
|
|
31
|
+
.hex-rain .h.accent { background: var(--c-orange-knvb); }
|
|
32
|
+
.hex-rain .h.clicking { pointer-events: none; }
|
|
33
|
+
.hex-rain .h svg {
|
|
34
|
+
width: 44%; height: 44%;
|
|
35
|
+
stroke: currentColor;
|
|
36
|
+
stroke-width: 1.6;
|
|
37
|
+
fill: none;
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
}
|
|
40
|
+
@keyframes hex-rain-fall {
|
|
41
|
+
0% { transform: translate(-50%, calc(-1 * var(--rain-size, 80px) - 24px)); opacity: 0; }
|
|
42
|
+
8% { opacity: 1; }
|
|
43
|
+
92% { opacity: 1; }
|
|
44
|
+
100% { transform: translate(-50%, var(--rain-end, 600px)); opacity: 0; }
|
|
45
|
+
}
|
|
46
|
+
@media (prefers-reduced-motion: reduce) {
|
|
47
|
+
.hex-rain .h { animation: none; transform: translate(-50%, 50%); opacity: 1; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Game HUD that fades in on first hex click. Counter (apps / 12),
|
|
51
|
+
timer (60s) and a row of 12 mini app-hex icons that light orange
|
|
52
|
+
as the player collects each app. Cobalt-on-white card per the
|
|
53
|
+
GameModal styling so the HUD reads cleanly over a falling-hex
|
|
54
|
+
background instead of fighting with white-on-cobalt shadows. */
|
|
55
|
+
.hex-rain .rain-hud {
|
|
56
|
+
position: absolute;
|
|
57
|
+
top: 14px; right: 14px;
|
|
58
|
+
z-index: 2;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
61
|
+
color: var(--c-cobalt-900);
|
|
62
|
+
background: rgba(255, 255, 255, 0.94);
|
|
63
|
+
backdrop-filter: blur(4px);
|
|
64
|
+
border-radius: var(--radius-lg);
|
|
65
|
+
padding: 12px 14px;
|
|
66
|
+
box-shadow: 0 4px 16px -4px rgba(11, 32, 73, 0.18);
|
|
67
|
+
opacity: 0;
|
|
68
|
+
transform: translateY(-8px);
|
|
69
|
+
transition: opacity 240ms ease, transform 240ms ease;
|
|
70
|
+
text-align: right;
|
|
71
|
+
}
|
|
72
|
+
.hex-rain .rain-hud.active { opacity: 1; transform: translateY(0); }
|
|
73
|
+
.hex-rain .rain-hud .hud-top {
|
|
74
|
+
display: flex; gap: 22px; justify-content: flex-end;
|
|
75
|
+
}
|
|
76
|
+
.hex-rain .rain-hud .hud-block { text-align: right; }
|
|
77
|
+
.hex-rain .rain-hud .hud-num {
|
|
78
|
+
display: block;
|
|
79
|
+
font-size: 28px; font-weight: 700;
|
|
80
|
+
letter-spacing: -0.02em;
|
|
81
|
+
line-height: 1;
|
|
82
|
+
font-variant-numeric: tabular-nums;
|
|
83
|
+
color: var(--c-cobalt-900);
|
|
84
|
+
}
|
|
85
|
+
.hex-rain .rain-hud .hud-label {
|
|
86
|
+
display: block;
|
|
87
|
+
font-size: 9px;
|
|
88
|
+
letter-spacing: 0.16em;
|
|
89
|
+
text-transform: uppercase;
|
|
90
|
+
color: var(--c-cobalt-400);
|
|
91
|
+
margin-top: 4px;
|
|
92
|
+
}
|
|
93
|
+
.hex-rain .rain-hud .hud-block.hud-tick .hud-num { animation: rain-tick 420ms ease; }
|
|
94
|
+
.hex-rain .rain-hud .hud-block.hud-untick .hud-num { animation: rain-untick 420ms ease; }
|
|
95
|
+
@keyframes rain-tick { 0% { transform: scale(1); color: var(--c-cobalt-900); } 35% { transform: scale(1.4); color: var(--c-orange-knvb); } 100% { transform: scale(1); color: var(--c-cobalt-900); } }
|
|
96
|
+
@keyframes rain-untick { 0% { transform: scale(1); color: var(--c-cobalt-900); } 35% { transform: scale(0.85); color: #d64633; } 100% { transform: scale(1); color: var(--c-cobalt-900); } }
|
|
97
|
+
.hex-rain .rain-hud .hud-timer.urgent .hud-num {
|
|
98
|
+
color: #d64633;
|
|
99
|
+
animation: rain-pulse 800ms ease infinite;
|
|
100
|
+
}
|
|
101
|
+
@keyframes rain-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
|
|
102
|
+
.hex-rain .rain-hud .hud-icons {
|
|
103
|
+
display: flex; gap: 3px; margin-top: 12px; justify-content: flex-end;
|
|
104
|
+
}
|
|
105
|
+
.hex-rain .rain-hud .hud-icon {
|
|
106
|
+
width: 22px; height: 26px;
|
|
107
|
+
clip-path: var(--hex-pointy-top);
|
|
108
|
+
background: var(--c-cobalt-100);
|
|
109
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
110
|
+
color: var(--c-cobalt-400);
|
|
111
|
+
transition: background 220ms, color 220ms;
|
|
112
|
+
}
|
|
113
|
+
.hex-rain .rain-hud .hud-icon.collected {
|
|
114
|
+
background: var(--c-orange-knvb);
|
|
115
|
+
color: var(--c-cobalt-900);
|
|
116
|
+
}
|
|
117
|
+
.hex-rain .rain-hud .hud-icon.collected.just { animation: rain-icon-pop 380ms ease; }
|
|
118
|
+
@keyframes rain-icon-pop {
|
|
119
|
+
0% { transform: scale(0.6); }
|
|
120
|
+
60% { transform: scale(1.2); }
|
|
121
|
+
100% { transform: scale(1); }
|
|
122
|
+
}
|
|
123
|
+
.hex-rain .rain-hud .hud-icon svg {
|
|
124
|
+
width: 60%; height: 60%;
|
|
125
|
+
stroke: currentColor; stroke-width: 1.7; fill: none;
|
|
126
|
+
stroke-linecap: round; stroke-linejoin: round;
|
|
127
|
+
}
|
|
128
|
+
|