@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,540 @@
|
|
|
1
|
+
/* Logo Memory: in-place transformation of the Clients hex marquee.
|
|
2
|
+
No overlay, no clones-with-new-DOM-tiles — the actual <a class="hex">
|
|
3
|
+
anchors that are already on screen become the game tiles.
|
|
4
|
+
|
|
5
|
+
Phases on click:
|
|
6
|
+
1. Freeze the sideways scroll, restore color (drop the grayscale).
|
|
7
|
+
2. Lift every visible hex out of its track into absolute positioning
|
|
8
|
+
anchored to its current snapshot coords. Pick the 4 hexes nearest
|
|
9
|
+
the horizontal centre per row as keepers (12 total). Animate the
|
|
10
|
+
rest off-screen left or right with stagger + fade.
|
|
11
|
+
3. Reposition the 12 keepers into a clean centred 4×3 honeycomb
|
|
12
|
+
cluster.
|
|
13
|
+
4. Clone each keeper, slide the clone down 3 rows so we have a
|
|
14
|
+
4×6 = 24-tile cluster.
|
|
15
|
+
5. Blink-shuffle: 12 random client logos picked, each duplicated
|
|
16
|
+
into pair assignments, each tile fades out, swaps to its
|
|
17
|
+
assigned logo, fades back in. Stagger across all 24.
|
|
18
|
+
6. Flip every tile to a cobalt back face with the Conduction C-in-
|
|
19
|
+
hex avatar. Stagger.
|
|
20
|
+
|
|
21
|
+
From there it's classic memory: click two, match holds at orange-glow
|
|
22
|
+
ring, mismatch flips back. Win fires `connext:gameend` on window so
|
|
23
|
+
the GameModal picks it up.
|
|
24
|
+
|
|
25
|
+
The tear-down restores the original marquee innerHTML so the scroll
|
|
26
|
+
animation resumes cleanly.
|
|
27
|
+
*/
|
|
28
|
+
(function () {
|
|
29
|
+
const PAIRS = 12;
|
|
30
|
+
const KEEP_PER_ROW = 4;
|
|
31
|
+
const FLIP_MS = 600;
|
|
32
|
+
const MISMATCH_MS = 900;
|
|
33
|
+
const STAGGER_MS = 35;
|
|
34
|
+
const BLINK_OUT_MS = 180;
|
|
35
|
+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
36
|
+
|
|
37
|
+
const CONDUCTION_BACK_SVG =
|
|
38
|
+
'<svg class="lm-back-svg" viewBox="-86.6 -100 173.2 200" aria-hidden="true">' +
|
|
39
|
+
'<polygon fill="#21468B" points="0,-100 86.6,-50 86.6,50 0,100 -86.6,50 -86.6,-50"/>' +
|
|
40
|
+
'<polygon fill="#FFFFFF" points="0,-74.5 64.5,-37.3 64.5,37.3 0,74.5 -64.5,37.3 -64.5,-37.3"/>' +
|
|
41
|
+
'<polygon fill="#21468B" points="-0.2,-25.2 20.1,-13.5 43.7,-27.1 -0.2,-52.4 -45.6,-26.2 -45.6,26.2 -0.2,52.4 43.7,27.1 20.1,13.5 -0.2,25.2 -22,12.6 -22,-12.6"/>' +
|
|
42
|
+
'</svg>';
|
|
43
|
+
|
|
44
|
+
function shuffle(arr) {
|
|
45
|
+
const a = arr.slice();
|
|
46
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
47
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
48
|
+
const t = a[i]; a[i] = a[j]; a[j] = t;
|
|
49
|
+
}
|
|
50
|
+
return a;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function wait(ms) { return new Promise(function (r) { setTimeout(r, ms); }); }
|
|
54
|
+
|
|
55
|
+
function collectLogos(marquee) {
|
|
56
|
+
const seen = new Map();
|
|
57
|
+
marquee.querySelectorAll('a').forEach(function (a) {
|
|
58
|
+
const img = a.querySelector('img');
|
|
59
|
+
if (!img) return;
|
|
60
|
+
const src = img.getAttribute('src');
|
|
61
|
+
if (!src || seen.has(src)) return;
|
|
62
|
+
const name = a.getAttribute('aria-label') || img.getAttribute('alt') || '';
|
|
63
|
+
seen.set(src, name);
|
|
64
|
+
});
|
|
65
|
+
return Array.from(seen, function (e) { return { src: e[0], name: e[1] }; });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hydrateContainer(container) {
|
|
69
|
+
if (container.dataset.memoryHydrated === '1') return;
|
|
70
|
+
container.dataset.memoryHydrated = '1';
|
|
71
|
+
|
|
72
|
+
const marquee = container.querySelector('[data-memory-marquee]') ||
|
|
73
|
+
container.querySelector('.marquee');
|
|
74
|
+
if (!marquee) return;
|
|
75
|
+
|
|
76
|
+
/* On replay (modal "Play again" button) we want a fresh round. The
|
|
77
|
+
runtime listens for connext:gamereplay and re-arms the marquee. */
|
|
78
|
+
window.addEventListener('connext:gamereplay', function (e) {
|
|
79
|
+
if (e.detail && e.detail.id === 'logo-memory') {
|
|
80
|
+
if (container.dataset.memoryActive === '1') {
|
|
81
|
+
tearDownActive(container, marquee);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
/* On Close — restore the marquee. The cluster is no longer needed
|
|
86
|
+
once the user is done with the round. */
|
|
87
|
+
window.addEventListener('connext:gameclose', function (e) {
|
|
88
|
+
if (e.detail && e.detail.id === 'logo-memory') {
|
|
89
|
+
if (container.dataset.memoryActive === '1') {
|
|
90
|
+
tearDownActive(container, marquee);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
marquee.addEventListener('click', function (e) {
|
|
96
|
+
if (container.dataset.memoryActive === '1') return;
|
|
97
|
+
const a = e.target.closest('a');
|
|
98
|
+
if (!a || !marquee.contains(a)) return;
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
const logos = collectLogos(marquee);
|
|
101
|
+
if (logos.length < PAIRS) return;
|
|
102
|
+
startGame(container, marquee, logos);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Stash for active-game state so tearDown can clean up keyboard
|
|
107
|
+
listeners and revert the marquee. */
|
|
108
|
+
const ACTIVE = new WeakMap();
|
|
109
|
+
|
|
110
|
+
function tearDownActive(container, marquee) {
|
|
111
|
+
const state = ACTIVE.get(container);
|
|
112
|
+
if (!state) return;
|
|
113
|
+
document.removeEventListener('keydown', state.onKey);
|
|
114
|
+
/* The marquee is now driven by clients-flow.js which manages its
|
|
115
|
+
own hex pool. Calling reset() removes every existing hex (the
|
|
116
|
+
ones the game lifted out of rows AND any leftovers) and re-fills
|
|
117
|
+
the rows from scratch. Falls back to an innerHTML restore for
|
|
118
|
+
the transitional case where the flow runtime hasn't loaded. */
|
|
119
|
+
if (window.ConductionClientsFlow && typeof window.ConductionClientsFlow.reset === 'function') {
|
|
120
|
+
/* Strip everything we appended during the game (lifted hex
|
|
121
|
+
anchors, the HUD pill, the win panel, etc) — anything that
|
|
122
|
+
isn't a row container. The runtime's reset() will repopulate
|
|
123
|
+
the rows from scratch. */
|
|
124
|
+
Array.from(marquee.children).forEach(function (child) {
|
|
125
|
+
if (!child.hasAttribute('data-flow-row')) child.remove();
|
|
126
|
+
});
|
|
127
|
+
window.ConductionClientsFlow.reset();
|
|
128
|
+
} else if (state.originalHTML) {
|
|
129
|
+
marquee.innerHTML = state.originalHTML;
|
|
130
|
+
}
|
|
131
|
+
marquee.classList.remove('lm-active', 'lm-overflow');
|
|
132
|
+
delete container.dataset.memoryActive;
|
|
133
|
+
ACTIVE.delete(container);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function startGame(container, marquee, logos) {
|
|
137
|
+
container.dataset.memoryActive = '1';
|
|
138
|
+
const originalHTML = marquee.innerHTML;
|
|
139
|
+
|
|
140
|
+
/* Freeze the continuous-spawn runtime so hexes stop drifting and
|
|
141
|
+
stay in their snapshot positions for phase 2 to lift cleanly. */
|
|
142
|
+
if (window.ConductionClientsFlow && typeof window.ConductionClientsFlow.pause === 'function') {
|
|
143
|
+
window.ConductionClientsFlow.pause();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* === Phase 1: Freeze + colorize === */
|
|
147
|
+
marquee.classList.add('lm-active');
|
|
148
|
+
/* Briefly let the CSS color transition + animation pause take effect
|
|
149
|
+
so the snapshot rects are taken from a still frame. */
|
|
150
|
+
await wait(reduceMotion ? 30 : 280);
|
|
151
|
+
|
|
152
|
+
/* === Phase 2: Snapshot, lift to absolute, pick keepers === */
|
|
153
|
+
/* The row class is hashed by CSS modules in the React build (e.g.
|
|
154
|
+
row_cD73), so .row doesn't match there. Match any descendant
|
|
155
|
+
whose class string contains "row" — works for both the literal
|
|
156
|
+
"row row1" of the preview HTML and the CSS-modules-hashed
|
|
157
|
+
"row_cD73 row2_w4Mq" of the React build. */
|
|
158
|
+
const rows = Array.from(marquee.querySelectorAll('[class*="row"]'));
|
|
159
|
+
const marqueeRect = marquee.getBoundingClientRect();
|
|
160
|
+
|
|
161
|
+
const rowsHexes = rows.map(function (row, rowIdx) {
|
|
162
|
+
const hexes = Array.from(row.querySelectorAll('a'));
|
|
163
|
+
hexes.forEach(function (hex) {
|
|
164
|
+
const r = hex.getBoundingClientRect();
|
|
165
|
+
hex._lmX = r.left - marqueeRect.left;
|
|
166
|
+
hex._lmY = r.top - marqueeRect.top;
|
|
167
|
+
hex._lmW = r.width;
|
|
168
|
+
hex._lmH = r.height;
|
|
169
|
+
hex._lmRowIdx = rowIdx;
|
|
170
|
+
});
|
|
171
|
+
/* Don't filter by viewport bounds — we always need 4 keepers per
|
|
172
|
+
row, and a hex slightly off-screen will get repositioned
|
|
173
|
+
centred anyway. Just exclude zero-size hexes (in case any are
|
|
174
|
+
display:none for some other reason). The blink-shuffle re-
|
|
175
|
+
assigns logos so it doesn't matter which DOM duplicate gets
|
|
176
|
+
picked. */
|
|
177
|
+
return hexes.filter(function (h) { return h._lmW > 0; });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const stageWidth = marqueeRect.width;
|
|
181
|
+
const stageCenterX = stageWidth / 2;
|
|
182
|
+
|
|
183
|
+
/* Pick the keepers per row, but guarantee a total of PAIRS keepers
|
|
184
|
+
so we always end up with 24 tiles. If a row's first 4 picks
|
|
185
|
+
overlap with another row's (rare in practice), we fall back to
|
|
186
|
+
picking the next-nearest hex from the densest row. */
|
|
187
|
+
const allKept = [];
|
|
188
|
+
const allDropping = [];
|
|
189
|
+
|
|
190
|
+
rowsHexes.forEach(function (rowHexes) {
|
|
191
|
+
const sorted = rowHexes.slice().sort(function (a, b) {
|
|
192
|
+
const da = Math.abs((a._lmX + a._lmW / 2) - stageCenterX);
|
|
193
|
+
const db = Math.abs((b._lmX + b._lmW / 2) - stageCenterX);
|
|
194
|
+
return da - db;
|
|
195
|
+
});
|
|
196
|
+
const pickCount = Math.min(KEEP_PER_ROW, sorted.length);
|
|
197
|
+
for (let k = 0; k < pickCount; k++) {
|
|
198
|
+
sorted[k]._lmKept = true;
|
|
199
|
+
allKept.push(sorted[k]);
|
|
200
|
+
}
|
|
201
|
+
for (let k = pickCount; k < sorted.length; k++) {
|
|
202
|
+
sorted[k]._lmKept = false;
|
|
203
|
+
allDropping.push(sorted[k]);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
/* Top up keepers from droppers' nearest-to-centre if any row was
|
|
207
|
+
short. Pulls from the front of allDropping (which is row-sorted),
|
|
208
|
+
preferring the nearest-to-centre droppers. */
|
|
209
|
+
while (allKept.length < PAIRS && allDropping.length > 0) {
|
|
210
|
+
const sortedDrops = allDropping.slice().sort(function (a, b) {
|
|
211
|
+
const da = Math.abs((a._lmX + a._lmW / 2) - stageCenterX);
|
|
212
|
+
const db = Math.abs((b._lmX + b._lmW / 2) - stageCenterX);
|
|
213
|
+
return da - db;
|
|
214
|
+
});
|
|
215
|
+
const promote = sortedDrops[0];
|
|
216
|
+
promote._lmKept = true;
|
|
217
|
+
allKept.push(promote);
|
|
218
|
+
const idx = allDropping.indexOf(promote);
|
|
219
|
+
if (idx >= 0) allDropping.splice(idx, 1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Lift every visible hex to position:absolute at its snapshot coords
|
|
223
|
+
so it's no longer affected by track flex layout. The flow runtime
|
|
224
|
+
drives drift via inline transform: translateX(...) — clear that
|
|
225
|
+
so the game's own left/top take over cleanly. Otherwise the hex
|
|
226
|
+
would render at left + transform, leaking the pre-pause drift. */
|
|
227
|
+
const allLifted = allKept.concat(allDropping);
|
|
228
|
+
allLifted.forEach(function (hex) {
|
|
229
|
+
hex.style.transform = '';
|
|
230
|
+
hex.style.position = 'absolute';
|
|
231
|
+
hex.style.left = hex._lmX + 'px';
|
|
232
|
+
hex.style.top = hex._lmY + 'px';
|
|
233
|
+
hex.style.width = hex._lmW + 'px';
|
|
234
|
+
hex.style.height = hex._lmH + 'px';
|
|
235
|
+
hex.style.margin = '0';
|
|
236
|
+
hex.style.transition = 'transform 700ms cubic-bezier(0.65, 0, 0.35, 1), opacity 600ms ease, left 700ms cubic-bezier(0.65, 0, 0.35, 1), top 700ms cubic-bezier(0.65, 0, 0.35, 1)';
|
|
237
|
+
hex.style.zIndex = '5';
|
|
238
|
+
marquee.appendChild(hex);
|
|
239
|
+
});
|
|
240
|
+
/* Hide the leftover (aria-hidden duplicates and any non-visible
|
|
241
|
+
originals) hexes so the rows collapse without ghost slots. */
|
|
242
|
+
marquee.querySelectorAll('a').forEach(function (h) {
|
|
243
|
+
if (allLifted.indexOf(h) === -1) h.style.display = 'none';
|
|
244
|
+
});
|
|
245
|
+
/* Collapse rows so there's no empty band where the tracks used to
|
|
246
|
+
sit. Marquee gets its own min-height set below for the cluster.
|
|
247
|
+
Reuse the rows array we captured at snapshot — `marquee.children`
|
|
248
|
+
now includes the lifted anchors as well so re-querying is risky. */
|
|
249
|
+
rows.forEach(function (r) {
|
|
250
|
+
r.style.height = '0';
|
|
251
|
+
r.style.margin = '0';
|
|
252
|
+
r.style.overflow = 'visible';
|
|
253
|
+
});
|
|
254
|
+
/* Allow the cluster to expand below the marquee's original 3-row
|
|
255
|
+
height — overflow:visible during play. */
|
|
256
|
+
marquee.classList.add('lm-overflow');
|
|
257
|
+
|
|
258
|
+
/* === Phase 3: Drop edges off-screen === */
|
|
259
|
+
allDropping.forEach(function (hex, i) {
|
|
260
|
+
const cx = hex._lmX + hex._lmW / 2;
|
|
261
|
+
const goLeft = cx < stageCenterX;
|
|
262
|
+
const tx = goLeft ? -(stageWidth / 2 + 120) : (stageWidth / 2 + 120);
|
|
263
|
+
setTimeout(function () {
|
|
264
|
+
hex.style.transform = 'translateX(' + tx + 'px) rotate(' + (goLeft ? -8 : 8) + 'deg)';
|
|
265
|
+
hex.style.opacity = '0';
|
|
266
|
+
}, i * 22);
|
|
267
|
+
});
|
|
268
|
+
await wait(reduceMotion ? 30 : 700 + allDropping.length * 22);
|
|
269
|
+
/* Drop fully done — remove dropped tiles. */
|
|
270
|
+
allDropping.forEach(function (h) { h.remove(); });
|
|
271
|
+
|
|
272
|
+
/* Track the originalHTML in ACTIVE before any phase that might
|
|
273
|
+
mutate the marquee, so the early-abort path can fall back to
|
|
274
|
+
tearDownActive without needing setupGame to have run. */
|
|
275
|
+
ACTIVE.set(container, { onKey: function () {}, originalHTML: originalHTML });
|
|
276
|
+
|
|
277
|
+
/* === Phase 4: Reposition keepers into clean centred 4×3 grid === */
|
|
278
|
+
if (allKept.length === 0) {
|
|
279
|
+
tearDownActive(container, marquee);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const hexW = allKept[0]._lmW;
|
|
283
|
+
const hexH = allKept[0]._lmH;
|
|
284
|
+
/* Read the actual --gap from the marquee so the cluster math
|
|
285
|
+
matches whatever the CSS sets (8px desktop, 6px mobile, etc).
|
|
286
|
+
Fallback to 8 if the variable isn't resolvable. */
|
|
287
|
+
const cssGap = parseFloat(getComputedStyle(marquee).getPropertyValue('--gap'));
|
|
288
|
+
const gap = isFinite(cssGap) && cssGap >= 0 ? cssGap : 8;
|
|
289
|
+
const cellW = hexW + gap;
|
|
290
|
+
/* Isotropic row spacing: vertical pitch matches the horizontal
|
|
291
|
+
pitch on the diagonal axis, same formula as the marquee CSS. */
|
|
292
|
+
const rowSpacingY = (hexW + gap) * 0.866;
|
|
293
|
+
|
|
294
|
+
/* Group keepers by their original row to preserve the 3-row visual,
|
|
295
|
+
then space them evenly within each row centred on stageCenterX. */
|
|
296
|
+
const keptByRow = {};
|
|
297
|
+
allKept.forEach(function (h) {
|
|
298
|
+
const r = h._lmRowIdx;
|
|
299
|
+
(keptByRow[r] = keptByRow[r] || []).push(h);
|
|
300
|
+
});
|
|
301
|
+
const sortedRowIds = Object.keys(keptByRow).map(Number).sort(function (a, b) { return a - b; });
|
|
302
|
+
/* Top y of the cluster — start where the topmost keeper currently
|
|
303
|
+
sits so the relayout doesn't jump vertically. */
|
|
304
|
+
const minTopY = Math.min.apply(null, allKept.map(function (h) { return h._lmY; }));
|
|
305
|
+
sortedRowIds.forEach(function (rIdx, i) {
|
|
306
|
+
const rowHexes = keptByRow[rIdx].slice().sort(function (a, b) { return a._lmX - b._lmX; });
|
|
307
|
+
const stagger = (i % 2 === 1) ? cellW / 2 : 0;
|
|
308
|
+
const totalRowWidth = (rowHexes.length - 1) * cellW + hexW;
|
|
309
|
+
const startX = stageCenterX - totalRowWidth / 2 + (stagger - cellW / 4);
|
|
310
|
+
const newY = minTopY + i * rowSpacingY;
|
|
311
|
+
rowHexes.forEach(function (hex, j) {
|
|
312
|
+
const newX = startX + j * cellW;
|
|
313
|
+
hex.style.left = newX + 'px';
|
|
314
|
+
hex.style.top = newY + 'px';
|
|
315
|
+
hex._lmFinalX = newX;
|
|
316
|
+
hex._lmFinalY = newY;
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await wait(reduceMotion ? 30 : 720);
|
|
321
|
+
|
|
322
|
+
/* === Phase 5: Duplicate keepers downward === */
|
|
323
|
+
const downOffset = 3 * rowSpacingY;
|
|
324
|
+
const halfCell = cellW / 2;
|
|
325
|
+
/* Each clone needs its lateral position shifted so the bottom 3
|
|
326
|
+
rows continue the honeycomb stagger pattern. The top half has
|
|
327
|
+
stagger [0, ½, 0] for layout rows 0/1/2; the bottom half is
|
|
328
|
+
rows 3/4/5 which have stagger [½, 0, ½]. The shift is the
|
|
329
|
+
difference. Pre-compute per clone for use during both the
|
|
330
|
+
slide-in animation and the final position lock. */
|
|
331
|
+
function lateralShiftFor(orig) {
|
|
332
|
+
const origLayoutRow = sortedRowIds.indexOf(orig._lmRowIdx);
|
|
333
|
+
const origStaggered = (origLayoutRow % 2 === 1);
|
|
334
|
+
const cloneStaggered = ((origLayoutRow + 3) % 2 === 1);
|
|
335
|
+
return (cloneStaggered ? halfCell : 0) - (origStaggered ? halfCell : 0);
|
|
336
|
+
}
|
|
337
|
+
const clones = allKept.map(function (orig) {
|
|
338
|
+
const clone = orig.cloneNode(true);
|
|
339
|
+
clone.classList.add('lm-clone');
|
|
340
|
+
clone.style.position = 'absolute';
|
|
341
|
+
clone.style.left = orig._lmFinalX + 'px';
|
|
342
|
+
clone.style.top = orig._lmFinalY + 'px';
|
|
343
|
+
clone.style.width = orig._lmW + 'px';
|
|
344
|
+
clone.style.height = orig._lmH + 'px';
|
|
345
|
+
clone.style.margin = '0';
|
|
346
|
+
clone.style.transition = orig.style.transition;
|
|
347
|
+
clone.style.opacity = '0';
|
|
348
|
+
clone.style.transform = 'scale(0.6)';
|
|
349
|
+
clone.style.zIndex = '5';
|
|
350
|
+
marquee.appendChild(clone);
|
|
351
|
+
return clone;
|
|
352
|
+
});
|
|
353
|
+
/* One frame for the clones to be in DOM before the transition. */
|
|
354
|
+
await wait(50);
|
|
355
|
+
clones.forEach(function (clone, i) {
|
|
356
|
+
const orig = allKept[i];
|
|
357
|
+
const dx = lateralShiftFor(orig);
|
|
358
|
+
setTimeout(function () {
|
|
359
|
+
clone.style.opacity = '1';
|
|
360
|
+
clone.style.transform = 'translate(' + dx + 'px, ' + downOffset + 'px) scale(1)';
|
|
361
|
+
}, i * 30);
|
|
362
|
+
});
|
|
363
|
+
await wait(reduceMotion ? 30 : 700 + clones.length * 30);
|
|
364
|
+
/* After slide-down, lock clones at their final left/top and clear
|
|
365
|
+
transform so flip rotateY is purely Y-rotation. */
|
|
366
|
+
clones.forEach(function (clone, i) {
|
|
367
|
+
const orig = allKept[i];
|
|
368
|
+
const dx = lateralShiftFor(orig);
|
|
369
|
+
clone.style.transition = 'opacity 200ms ease';
|
|
370
|
+
clone.style.transform = '';
|
|
371
|
+
clone.style.left = (orig._lmFinalX + dx) + 'px';
|
|
372
|
+
clone.style.top = (orig._lmFinalY + downOffset) + 'px';
|
|
373
|
+
clone._lmFinalX = orig._lmFinalX + dx;
|
|
374
|
+
clone._lmFinalY = orig._lmFinalY + downOffset;
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
/* === Phase 6: Blink-shuffle the logos === */
|
|
378
|
+
const allTiles = allKept.concat(clones);
|
|
379
|
+
const picked = shuffle(logos).slice(0, PAIRS);
|
|
380
|
+
const pairAssignments = shuffle(picked.flatMap(function (l, i) {
|
|
381
|
+
return [
|
|
382
|
+
{ src: l.src, name: l.name, pairId: i },
|
|
383
|
+
{ src: l.src, name: l.name, pairId: i },
|
|
384
|
+
];
|
|
385
|
+
}));
|
|
386
|
+
/* Every tile fades, swaps img src to its assigned logo, fades back. */
|
|
387
|
+
const shuffleStagger = reduceMotion ? 0 : STAGGER_MS;
|
|
388
|
+
allTiles.forEach(function (tile, i) {
|
|
389
|
+
setTimeout(function () {
|
|
390
|
+
tile.style.opacity = '0';
|
|
391
|
+
setTimeout(function () {
|
|
392
|
+
const a = pairAssignments[i];
|
|
393
|
+
const img = tile.querySelector('img');
|
|
394
|
+
if (img) {
|
|
395
|
+
img.src = a.src;
|
|
396
|
+
img.alt = a.name;
|
|
397
|
+
}
|
|
398
|
+
tile.dataset.lmPair = String(a.pairId);
|
|
399
|
+
tile.setAttribute('aria-label', 'Memory tile: ' + a.name);
|
|
400
|
+
tile.style.opacity = '1';
|
|
401
|
+
}, BLINK_OUT_MS);
|
|
402
|
+
}, i * shuffleStagger);
|
|
403
|
+
});
|
|
404
|
+
await wait(reduceMotion ? 30 : allTiles.length * shuffleStagger + BLINK_OUT_MS + 240);
|
|
405
|
+
|
|
406
|
+
/* === Phase 7: Flip every tile to the cobalt back face === */
|
|
407
|
+
allTiles.forEach(function (tile) { enhanceTileForFlip(tile); });
|
|
408
|
+
/* Force reflow before flip transition so the new transform-style
|
|
409
|
+
takes effect. */
|
|
410
|
+
void marquee.offsetHeight;
|
|
411
|
+
const flipStagger = reduceMotion ? 0 : 30;
|
|
412
|
+
allTiles.forEach(function (tile, i) {
|
|
413
|
+
setTimeout(function () { tile.classList.add('lm-back-up'); }, i * flipStagger);
|
|
414
|
+
});
|
|
415
|
+
await wait(reduceMotion ? 30 : allTiles.length * flipStagger + 600);
|
|
416
|
+
|
|
417
|
+
/* === Phase 8: Game ready === */
|
|
418
|
+
setupGame(container, marquee, allTiles, originalHTML);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function enhanceTileForFlip(tile) {
|
|
422
|
+
if (tile.classList.contains('lm-tile')) return;
|
|
423
|
+
tile.classList.add('lm-tile');
|
|
424
|
+
const img = tile.querySelector('img');
|
|
425
|
+
const flipper = document.createElement('div');
|
|
426
|
+
flipper.className = 'lm-flipper';
|
|
427
|
+
const front = document.createElement('div');
|
|
428
|
+
front.className = 'lm-face lm-front';
|
|
429
|
+
if (img) front.appendChild(img);
|
|
430
|
+
const back = document.createElement('div');
|
|
431
|
+
back.className = 'lm-face lm-back';
|
|
432
|
+
back.innerHTML = CONDUCTION_BACK_SVG;
|
|
433
|
+
flipper.appendChild(front);
|
|
434
|
+
flipper.appendChild(back);
|
|
435
|
+
tile.appendChild(flipper);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function setupGame(container, marquee, tiles, originalHTML) {
|
|
439
|
+
/* HUD pill positioned at the top of the marquee (over the cluster). */
|
|
440
|
+
const hud = document.createElement('div');
|
|
441
|
+
hud.className = 'lm-hud';
|
|
442
|
+
hud.innerHTML =
|
|
443
|
+
'<div class="lm-counter"><span data-matched>0</span> / ' + PAIRS + '</div>' +
|
|
444
|
+
'<div class="lm-moves"><span data-moves>0</span> zetten</div>' +
|
|
445
|
+
'<button class="lm-close" type="button" aria-label="Sluit spel">×</button>';
|
|
446
|
+
marquee.appendChild(hud);
|
|
447
|
+
|
|
448
|
+
const matchedSpan = hud.querySelector('[data-matched]');
|
|
449
|
+
const movesSpan = hud.querySelector('[data-moves]');
|
|
450
|
+
const closeBtn = hud.querySelector('.lm-close');
|
|
451
|
+
|
|
452
|
+
let firstTile = null;
|
|
453
|
+
let busy = false;
|
|
454
|
+
let moves = 0;
|
|
455
|
+
let matched = 0;
|
|
456
|
+
let phase = 'play';
|
|
457
|
+
const startTime = performance.now();
|
|
458
|
+
|
|
459
|
+
function onTileClick(tile) {
|
|
460
|
+
if (busy || phase !== 'play') return;
|
|
461
|
+
if (tile.classList.contains('lm-matched')) return;
|
|
462
|
+
if (!tile.classList.contains('lm-back-up')) return;
|
|
463
|
+
tile.classList.remove('lm-back-up');
|
|
464
|
+
if (!firstTile) {
|
|
465
|
+
firstTile = tile;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const second = tile;
|
|
469
|
+
moves++;
|
|
470
|
+
movesSpan.textContent = String(moves);
|
|
471
|
+
const t1 = firstTile;
|
|
472
|
+
firstTile = null;
|
|
473
|
+
if (t1.dataset.lmPair === second.dataset.lmPair) {
|
|
474
|
+
busy = true;
|
|
475
|
+
setTimeout(function () {
|
|
476
|
+
t1.classList.add('lm-matched');
|
|
477
|
+
second.classList.add('lm-matched');
|
|
478
|
+
matched++;
|
|
479
|
+
matchedSpan.textContent = String(matched);
|
|
480
|
+
busy = false;
|
|
481
|
+
if (matched === PAIRS) onWin();
|
|
482
|
+
}, FLIP_MS);
|
|
483
|
+
} else {
|
|
484
|
+
busy = true;
|
|
485
|
+
setTimeout(function () {
|
|
486
|
+
t1.classList.add('lm-back-up');
|
|
487
|
+
second.classList.add('lm-back-up');
|
|
488
|
+
busy = false;
|
|
489
|
+
}, MISMATCH_MS);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
tiles.forEach(function (tile) {
|
|
494
|
+
tile.addEventListener('click', function (e) {
|
|
495
|
+
e.preventDefault();
|
|
496
|
+
onTileClick(tile);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
function onWin() {
|
|
501
|
+
phase = 'won';
|
|
502
|
+
const elapsedSec = Math.round((performance.now() - startTime) / 1000);
|
|
503
|
+
window.dispatchEvent(new CustomEvent('connext:gameend', {
|
|
504
|
+
detail: {
|
|
505
|
+
id: 'logo-memory',
|
|
506
|
+
won: true,
|
|
507
|
+
score: matched,
|
|
508
|
+
summary: matched + ' pairs · ' + moves + ' zetten · ' + elapsedSec + 's',
|
|
509
|
+
title: 'Klaar.',
|
|
510
|
+
subtitle: 'You matched all 12 pairs across the clients marquee.',
|
|
511
|
+
}
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function tearDown() {
|
|
516
|
+
tearDownActive(container, marquee);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function onKey(e) {
|
|
520
|
+
if (e.key === 'Escape') tearDown();
|
|
521
|
+
}
|
|
522
|
+
document.addEventListener('keydown', onKey);
|
|
523
|
+
closeBtn.addEventListener('click', tearDown);
|
|
524
|
+
|
|
525
|
+
ACTIVE.set(container, { onKey: onKey, originalHTML: originalHTML });
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function hydrate() {
|
|
529
|
+
document.querySelectorAll('[data-logo-memory]').forEach(hydrateContainer);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
window.LogoMemory = window.LogoMemory || {};
|
|
533
|
+
window.LogoMemory.hydrate = hydrate;
|
|
534
|
+
|
|
535
|
+
if (document.readyState === 'loading') {
|
|
536
|
+
document.addEventListener('DOMContentLoaded', hydrate);
|
|
537
|
+
} else {
|
|
538
|
+
hydrate();
|
|
539
|
+
}
|
|
540
|
+
})();
|