@energy8platform/platform-core 0.30.5 → 0.30.7
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/dist/index.cjs.js +337 -28
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +337 -28
- package/dist/index.esm.js.map +1 -1
- package/dist/loading.cjs.js +337 -28
- package/dist/loading.cjs.js.map +1 -1
- package/dist/loading.d.ts +1 -0
- package/dist/loading.esm.js +337 -28
- package/dist/loading.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/loading/CSSPreloader.ts +102 -18
- package/src/loading/splash.ts +116 -0
- package/src/loading/variants/index.ts +2 -0
- package/src/loading/variants/slottech.ts +141 -0
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { LoadingScreenConfig } from '../types';
|
|
2
2
|
import { VARIANTS, DEFAULT_VARIANT_NAME } from './variants';
|
|
3
3
|
import type { PreloaderVariantHandle } from './variants';
|
|
4
|
+
import {
|
|
5
|
+
BRAND_FLOOR_MS,
|
|
6
|
+
SPLASH_CSS,
|
|
7
|
+
SPLASH_DURATION_MS,
|
|
8
|
+
buildSplashHTML,
|
|
9
|
+
} from './splash';
|
|
4
10
|
|
|
5
11
|
const PRELOADER_ID = '__ge-css-preloader__';
|
|
6
12
|
const REMOVE_FADE_TIMEOUT_MS = 600;
|
|
@@ -15,6 +21,12 @@ interface PreloaderState {
|
|
|
15
21
|
styleEl: HTMLStyleElement;
|
|
16
22
|
/** Live binding to the selected variant's DOM; `null` for custom-HTML (inert lifecycle). */
|
|
17
23
|
handle: PreloaderVariantHandle | null;
|
|
24
|
+
/** The powered-by layer, until it has played out and been detached. */
|
|
25
|
+
splashEl: HTMLDivElement | null;
|
|
26
|
+
/** Pending detach of `splashEl` — cleared on removal so it can't fire late. */
|
|
27
|
+
splashTimer: ReturnType<typeof setTimeout> | null;
|
|
28
|
+
/** Mount time, the origin the splash + brand-floor gate is measured from. */
|
|
29
|
+
startedAt: number;
|
|
18
30
|
showPercentage: boolean;
|
|
19
31
|
tapToStart: boolean;
|
|
20
32
|
tapToStartText: string;
|
|
@@ -32,6 +44,17 @@ function clampProgress(p: number): number {
|
|
|
32
44
|
return Math.max(0, Math.min(1, p));
|
|
33
45
|
}
|
|
34
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Milliseconds still owed before the game may take over: the powered-by splash
|
|
49
|
+
* plus the floor that keeps the game's own brand readable behind it. Zero once
|
|
50
|
+
* that window has passed, so a slow boot pays nothing.
|
|
51
|
+
*/
|
|
52
|
+
function brandGateDelay(): number {
|
|
53
|
+
if (!state) return 0;
|
|
54
|
+
const elapsed = Date.now() - state.startedAt;
|
|
55
|
+
return Math.max(0, SPLASH_DURATION_MS + BRAND_FLOOR_MS - elapsed);
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
export function createCSSPreloader(
|
|
36
59
|
container: HTMLElement,
|
|
37
60
|
config?: LoadingScreenConfig,
|
|
@@ -60,6 +83,15 @@ export function createCSSPreloader(
|
|
|
60
83
|
overlay.id = PRELOADER_ID;
|
|
61
84
|
overlay.innerHTML = customHTML || variant.buildContentHTML(config);
|
|
62
85
|
|
|
86
|
+
// The platform's powered-by pre-roll, layered over the variant's content
|
|
87
|
+
// inside the SAME overlay — one element to tear down, and no frame where the
|
|
88
|
+
// page shows through between the two. Appended last so it wins the stacking
|
|
89
|
+
// order. The variant content is still mounted underneath from the first
|
|
90
|
+
// frame, so `setCSSPreloaderProgress` has a live target throughout the
|
|
91
|
+
// splash and nothing has to be buffered and replayed on handover.
|
|
92
|
+
overlay.insertAdjacentHTML('beforeend', buildSplashHTML());
|
|
93
|
+
const splashEl = overlay.lastElementChild as HTMLDivElement;
|
|
94
|
+
|
|
63
95
|
const styleEl = document.createElement('style');
|
|
64
96
|
// Shared overlay infrastructure (positioning / background / fade) plus the
|
|
65
97
|
// variant's own content styling and animations.
|
|
@@ -82,6 +114,7 @@ export function createCSSPreloader(
|
|
|
82
114
|
pointer-events: none;
|
|
83
115
|
}
|
|
84
116
|
${variant.css}
|
|
117
|
+
${SPLASH_CSS}
|
|
85
118
|
`;
|
|
86
119
|
|
|
87
120
|
// The absolute overlay needs a positioned ancestor to size against. Override ONLY a container
|
|
@@ -103,12 +136,26 @@ ${variant.css}
|
|
|
103
136
|
// to bind to and the handle stays null (lifecycle API becomes inert).
|
|
104
137
|
const handle = customHTML ? null : variant.mount(overlay, config);
|
|
105
138
|
|
|
139
|
+
// Detach the splash once its CSS timeline has played out. Keeping a spent,
|
|
140
|
+
// fully-transparent layer in the tree would otherwise sit over the content
|
|
141
|
+
// for the rest of the boot.
|
|
142
|
+
const splashTimer = setTimeout(() => {
|
|
143
|
+
splashEl.remove();
|
|
144
|
+
if (state) {
|
|
145
|
+
state.splashEl = null;
|
|
146
|
+
state.splashTimer = null;
|
|
147
|
+
}
|
|
148
|
+
}, SPLASH_DURATION_MS);
|
|
149
|
+
|
|
106
150
|
state = {
|
|
107
151
|
container,
|
|
108
152
|
prevPosition,
|
|
109
153
|
overlay,
|
|
110
154
|
styleEl,
|
|
111
155
|
handle,
|
|
156
|
+
splashEl,
|
|
157
|
+
splashTimer,
|
|
158
|
+
startedAt: Date.now(),
|
|
112
159
|
showPercentage: config?.showPercentage === true,
|
|
113
160
|
tapToStart: config?.tapToStart !== false,
|
|
114
161
|
tapToStartText: config?.tapToStartText ?? 'TAP TO START',
|
|
@@ -135,25 +182,56 @@ export function waitCSSPreloaderTap(): Promise<void> {
|
|
|
135
182
|
);
|
|
136
183
|
}
|
|
137
184
|
if (state.removed) return Promise.resolve();
|
|
138
|
-
if (!state.tapToStart) return Promise.resolve();
|
|
139
185
|
if (state.tapPromise) return state.tapPromise;
|
|
140
186
|
|
|
141
|
-
state.
|
|
142
|
-
|
|
187
|
+
const requireTap = state.tapToStart;
|
|
188
|
+
|
|
189
|
+
// Only the tap path takes over the status text and freezes progress; with
|
|
190
|
+
// tapToStart:false the bar keeps filling behind the gate as it always did.
|
|
191
|
+
if (requireTap) {
|
|
192
|
+
state.handle?.showTapText(state.tapToStartText);
|
|
193
|
+
state.overlay.style.cursor = 'pointer';
|
|
194
|
+
state.tapState = 'waiting';
|
|
195
|
+
}
|
|
143
196
|
|
|
144
|
-
state.tapState = 'waiting';
|
|
145
197
|
state.tapPromise = new Promise<void>((resolve) => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
state
|
|
198
|
+
let settled = false;
|
|
199
|
+
// Shared exit for every path — the gates below, and removeCSSPreloader
|
|
200
|
+
// short-circuiting a pending wait during teardown.
|
|
201
|
+
const finish = () => {
|
|
202
|
+
if (settled) return;
|
|
203
|
+
settled = true;
|
|
204
|
+
if (state) {
|
|
205
|
+
if (state.tapHandler) {
|
|
206
|
+
state.overlay.removeEventListener('pointerdown', state.tapHandler);
|
|
207
|
+
state.tapHandler = null;
|
|
208
|
+
}
|
|
209
|
+
if (requireTap) state.tapState = 'resolved';
|
|
210
|
+
state.tapResolve = null;
|
|
211
|
+
}
|
|
153
212
|
resolve();
|
|
154
213
|
};
|
|
155
|
-
state!.
|
|
156
|
-
|
|
214
|
+
state!.tapResolve = finish;
|
|
215
|
+
|
|
216
|
+
// Gate 1 — the powered-by splash plus the brand floor. This is what stands
|
|
217
|
+
// between the splash and the game when tapToStart is false; without it a
|
|
218
|
+
// fast boot would cut to gameplay while the splash was still animating.
|
|
219
|
+
const gates: Promise<unknown>[] = [
|
|
220
|
+
new Promise<void>((r) => setTimeout(r, brandGateDelay())),
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
// Gate 2 — the player's tap, when one is required.
|
|
224
|
+
if (requireTap) {
|
|
225
|
+
gates.push(
|
|
226
|
+
new Promise<void>((r) => {
|
|
227
|
+
const handler = (_e: Event) => r();
|
|
228
|
+
state!.tapHandler = handler;
|
|
229
|
+
state!.overlay.addEventListener('pointerdown', handler, { once: true });
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
void Promise.all(gates).then(finish);
|
|
157
235
|
});
|
|
158
236
|
|
|
159
237
|
return state.tapPromise;
|
|
@@ -162,16 +240,22 @@ export function waitCSSPreloaderTap(): Promise<void> {
|
|
|
162
240
|
export function removeCSSPreloader(_container: HTMLElement): Promise<void> {
|
|
163
241
|
if (!state || state.removed) return Promise.resolve();
|
|
164
242
|
|
|
243
|
+
// Drop the splash's pending detach — the whole overlay is going away, and a
|
|
244
|
+
// late callback would touch a torn-down state.
|
|
245
|
+
if (state.splashTimer !== null) {
|
|
246
|
+
clearTimeout(state.splashTimer);
|
|
247
|
+
state.splashTimer = null;
|
|
248
|
+
}
|
|
249
|
+
state.splashEl = null;
|
|
250
|
+
|
|
165
251
|
// Detach the pending pointer listener (if any) and resolve a pending tap.
|
|
252
|
+
// Teardown beats the brand gate on purpose: this is also the boot-error path
|
|
253
|
+
// in GameApplication, which must not sit for seconds behind a brand floor.
|
|
166
254
|
if (state.tapHandler) {
|
|
167
255
|
state.overlay.removeEventListener('pointerdown', state.tapHandler);
|
|
168
256
|
state.tapHandler = null;
|
|
169
257
|
}
|
|
170
|
-
if (state.
|
|
171
|
-
state.tapState = 'resolved';
|
|
172
|
-
state.tapResolve();
|
|
173
|
-
state.tapResolve = null;
|
|
174
|
-
}
|
|
258
|
+
if (state.tapResolve) state.tapResolve();
|
|
175
259
|
|
|
176
260
|
state.removed = true;
|
|
177
261
|
const { overlay, styleEl, container, prevPosition } = state;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "POWERED BY ENERGY8 ENGINE" pre-roll shown at the very start of every
|
|
3
|
+
* boot, above whatever preloader variant the game selected.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately NOT configurable: it is the platform's attribution and
|
|
6
|
+
* plays for every game, on every boot. `CSSPreloader` mounts it as a layer
|
|
7
|
+
* inside the same overlay it already owns, so there is still exactly one
|
|
8
|
+
* element to tear down and no gap where the page shows through.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Total splash length: logo fades in, holds, fades out, then the black backdrop lifts. */
|
|
12
|
+
export const SPLASH_DURATION_MS = 2000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How long the game's own brand must remain readable once the splash has
|
|
16
|
+
* lifted. Without this a fast boot would swap straight from the splash into
|
|
17
|
+
* the game and the game's logo would never be seen.
|
|
18
|
+
*/
|
|
19
|
+
export const BRAND_FLOOR_MS = 1000;
|
|
20
|
+
|
|
21
|
+
/** Class on the splash layer — also the hook tests assert against. */
|
|
22
|
+
export const SPLASH_CLASS = 'ge-splash';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The official artwork as vector outlines. Three groups, in the source's own
|
|
26
|
+
* order: the ENERGY8 wordmark, the "POWERED BY" line above it, and "ENGINE"
|
|
27
|
+
* below. Two deviations from the export:
|
|
28
|
+
*
|
|
29
|
+
* - Every shape is forced to #fff. The source paints `.st0 { fill: #02020a }`
|
|
30
|
+
* and leaves the two text groups with no fill at all (so they default to
|
|
31
|
+
* black) — on a black splash the whole logo would be invisible. Colours are
|
|
32
|
+
* set as attributes rather than a `<style>` block on purpose: inlining the
|
|
33
|
+
* export's `<style>` would leak a rule for the very generic `.st0` selector
|
|
34
|
+
* into the host page.
|
|
35
|
+
* - Ten zero-area `<path d="M…h0Z"/>` stubs are dropped (Illustrator artifacts).
|
|
36
|
+
*/
|
|
37
|
+
const SPLASH_LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 792.22 250.81" class="ge-splash-logo" role="img">
|
|
38
|
+
<title>Powered by Energy8 Engine</title>
|
|
39
|
+
<g fill="#fff" stroke="#fff" stroke-miterlimit="10">
|
|
40
|
+
<polygon points="126.9 47.45 33.24 47.45 .62 199.67 94.28 199.72 101.83 164.51 43.28 164.54 48.29 141.12 83.41 141.15 90.96 105.92 55.8 106.02 60.85 82.55 119.38 82.61 126.9 47.45"/>
|
|
41
|
+
<polygon points="337.67 47.45 243.99 47.45 211.39 199.65 305.06 199.63 312.59 164.42 254.06 164.43 259.05 141.1 294.18 141.08 301.73 105.87 266.58 105.98 271.61 82.48 330.14 82.59 337.67 47.45"/>
|
|
42
|
+
<path d="M431.33,47.45h-81.96c-10.92,50.92-21.7,101.3-32.62,152.21h35.13l12.53-58.53h11.71l-.83,58.54,35.14-.04.83-58.52,14.21-11.71,15.06-70.26-9.21-11.69h0ZM395.37,106l-23.41-.03c1.67-7.78,5.86-27.35,7.53-35.13l23.43-.06-7.55,35.22h0Z"/>
|
|
43
|
+
<polygon points="433.85 199.67 432.18 152.81 452.26 59.09 466.46 47.45 548.43 47.45 557.67 58.98 550.11 94.29 514.96 94.32 517.48 82.54 482.35 82.57 464.79 164.51 499.92 164.52 504.93 141.12 493.23 141.1 498.23 117.71 545.09 117.67 530.05 187.92 515.8 199.73 433.85 199.67"/>
|
|
44
|
+
<path d="M791.83,59.16l-9.23-11.71h-70.25l-14.21,11.7-12.52,58.52,15.05,6.34-17.56,5.4-12.54,58.53,9.2,11.71h70.25l14.22-11.73,12.53-58.51-17.56-5.45,20.07-6.25,12.54-58.56h0ZM731.61,176.28l-23.41-.04c1.67-7.78,5.86-27.34,7.53-35.11h23.42l-7.54,35.15ZM746.67,105.99l-23.41-.02c1.67-7.77,5.86-27.33,7.52-35.1l23.42-.02-7.53,35.14Z"/>
|
|
45
|
+
<polygon points="194.71 222.91 167.33 223.06 156.18 129.4 141.1 199.94 105.93 199.9 138.61 47.45 173.74 47.45 182.11 117.71 197.16 47.45 232.29 47.45 194.71 222.91"/>
|
|
46
|
+
<polygon points="591.92 117.71 571.85 47.45 606.98 47.45 620.38 94.19 653.81 47.45 688.94 47.45 622.04 141.1 609.51 199.64 569.37 223.06 591.92 117.71"/>
|
|
47
|
+
</g>
|
|
48
|
+
<g fill="#fff">
|
|
49
|
+
<path d="M154.59,6.63c3.65-.3,8.87-.56,16.66-.56,7.19,0,12.27.79,15.59,2.14,3.22,1.27,5.54,3.46,5.54,6.13,0,2.46-1.54,4.7-4.59,6.13-4.04,1.95-9.94,2.85-17.29,2.85-1.75,0-3.29-.07-4.41-.17v9.2h-11.5V6.63ZM166.09,18.44c1.06.13,2.42.19,4.39.19,6.26,0,10.4-1.5,10.4-4.21,0-2.44-3.54-3.74-9.53-3.74-2.62,0-4.38.1-5.26.2v7.57Z"/>
|
|
50
|
+
<path d="M251.53,19.13c0,8.12-10.78,13.68-26.82,13.68s-25.68-5.71-25.68-13.23c0-8.01,10.82-13.75,26.48-13.75s26.02,5.67,26.02,13.29ZM211.37,19.41c0,4.83,5.36,8.62,13.95,8.62s13.88-3.72,13.88-8.72c0-4.73-5.14-8.7-13.89-8.7s-13.94,3.91-13.94,8.8Z"/>
|
|
51
|
+
<path d="M268.79,32.35l-12.71-26.05h12.55l4.18,10.84c1.17,3.14,2.39,6.65,3.24,9.1h.15c.82-2.7,2.04-5.91,3.35-9.19l4.44-10.75h13.15l4.15,11.23c1.1,3.08,1.93,5.82,2.67,8.53h.15c.82-2.74,2-5.84,3.23-9l4.5-10.76h11.94l-14.01,26.05h-12.69l-4.61-12.11c-1.04-2.66-1.82-4.96-2.43-7.83h-.15c-.82,2.86-1.6,5.19-2.83,7.85l-5.16,12.1h-13.1Z"/>
|
|
52
|
+
<path d="M363.08,21.48h-20.37v5.91h22.73v4.97h-34.35V6.3h33.22v4.97h-21.61v5.24h20.37v4.97Z"/>
|
|
53
|
+
<path d="M374.75,6.65c3.75-.32,9.54-.58,16.2-.58,7.97,0,13.21.65,16.83,2.11,2.92,1.17,4.76,3.03,4.76,5.48,0,3.47-5.07,5.71-9.04,6.37v.14c3.43.73,5.42,2.49,6.76,4.75,1.71,2.97,3.25,6.42,4.24,7.45h-11.84c-.8-.76-2.07-2.68-3.68-6.02-1.55-3.26-3.77-4.12-8.81-4.16h-3.93v10.18h-11.5V6.65ZM386.25,17.8h5.22c5.96,0,9.57-1.39,9.57-3.65,0-2.44-3.55-3.54-8.9-3.54-3.26,0-5.03.11-5.89.21v6.98Z"/>
|
|
54
|
+
<path d="M454.32,21.48h-20.37v5.91h22.73v4.97h-34.35V6.3h33.22v4.97h-21.61v5.24h20.37v4.97Z"/>
|
|
55
|
+
<path d="M465.99,6.65c4.66-.37,10.58-.58,16.73-.58,10.36,0,17.04.96,22.27,2.96,5.4,2.02,8.95,5.11,8.95,9.57,0,4.9-3.69,8.25-8.56,10.39-5.77,2.48-14.71,3.66-25.2,3.66-6.69,0-11.17-.19-14.19-.42V6.65ZM477.6,27.78c1.12.1,2.96.11,4.59.11,11.88.06,19.36-3.07,19.36-8.98.03-5.43-7.06-8.14-17.86-8.14-3.01,0-4.97.14-6.09.26v16.76Z"/>
|
|
56
|
+
<path d="M540.25,6.65c2.93-.29,9.45-.58,15.49-.58,7.51,0,11.56.37,15.23,1.34,3.71.9,6.57,2.69,6.57,5.27,0,2.28-2.62,4.42-8.38,5.49v.09c5.58.75,10.14,2.95,10.14,6.44,0,2.46-2.32,4.38-5.61,5.67-3.83,1.48-10.07,2.28-20.37,2.28-6.09,0-10.46-.22-13.05-.42V6.65ZM551.75,16.51h4.12c6.69,0,9.91-1.21,9.91-3.09,0-2-3.21-2.9-8.6-2.9-2.89,0-4.42.09-5.43.19v5.8ZM551.75,28.02c1.22.1,2.74.11,4.97.11,5.43,0,10.28-.95,10.28-3.73,0-2.6-4.75-3.54-10.88-3.54h-4.37v7.17Z"/>
|
|
57
|
+
<path d="M598.63,32.35v-10.92l-17.08-15.13h13.56l5.55,6.29c1.7,1.93,2.83,3.15,4.19,4.81h.15c1.26-1.54,2.57-2.94,4.17-4.78l5.64-6.32h13.39l-17.95,14.94v11.12h-11.62Z"/>
|
|
58
|
+
</g>
|
|
59
|
+
<g fill="#fff">
|
|
60
|
+
<path d="M257.81,230.46l-25.46.08.02,5.72,28.41-.09v4.82s-42.91.13-42.91.13l-.08-25.25,41.52-.13.02,4.81-27,.08.02,5.08,25.46-.08v4.82Z"/>
|
|
61
|
+
<path d="M272.41,240.96l-.08-25.25,17.91-.05,13.87,9c4.29,2.87,8.09,5.99,11.21,9.06l.25-.02c-1.01-3.57-1.11-6.62-1.12-10.17l-.02-7.96,13.49-.04.08,25.25-15.73.05-14.47-9.72c-4.19-2.91-8.65-6.15-12.16-9.33l-.34.02c.47,3.61.57,7.08.58,10.88l.02,8.23-13.49.04Z"/>
|
|
62
|
+
<path d="M399.35,239.23c-4.86.69-13.79,1.64-23.09,1.67-12.61.04-21.62-1.2-27.82-3.6-5.76-2.2-8.94-5.33-8.95-8.83-.02-8.13,15.56-13.28,38.01-13.35,9.02-.03,16,.65,19.42,1.3l-3.17,4.7c-3.95-.67-8.69-1.21-16.7-1.19-12.73.04-22.3,2.87-22.29,8,.01,4.93,8.78,8.24,22.08,8.2,3.82-.01,6.95-.21,8.26-.45l-.02-5.05-10.82.03v-4.6s25.04-.08,25.04-.08l.04,13.24Z"/>
|
|
63
|
+
<path d="M427.02,215.23l.08,25.25-14.52.04-.08-25.25,14.52-.04Z"/>
|
|
64
|
+
<path d="M441.45,240.44l-.08-25.25,17.91-.06,13.87,9c4.29,2.87,8.09,5.99,11.21,9.06l.25-.02c-1.01-3.57-1.11-6.62-1.12-10.17l-.02-7.96,13.49-.04.08,25.25-15.73.05-14.47-9.72c-4.2-2.91-8.65-6.15-12.16-9.33l-.35.02c.47,3.61.57,7.08.58,10.88l.03,8.23-13.49.04Z"/>
|
|
65
|
+
<path d="M551.33,229.56l-25.46.08.02,5.72,28.41-.09v4.82s-42.91.13-42.91.13l-.08-25.25,41.52-.13v4.81s-26.99.08-26.99.08l.02,5.08,25.46-.08v4.82Z"/>
|
|
66
|
+
</g>
|
|
67
|
+
</svg>`;
|
|
68
|
+
|
|
69
|
+
/** Markup for the splash layer — a black backdrop over the overlay's content. */
|
|
70
|
+
export function buildSplashHTML(): string {
|
|
71
|
+
return `<div class="${SPLASH_CLASS}">${SPLASH_LOGO_SVG}</div>`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The whole timeline is CSS. JS only removes the layer once it has played, so
|
|
76
|
+
* a stalled main thread can't strand the splash half-faded on screen.
|
|
77
|
+
*
|
|
78
|
+
* Percentages map onto SPLASH_DURATION_MS: logo in by 500ms, held to 1400ms,
|
|
79
|
+
* gone by 1700ms; the backdrop then lifts over the last 300ms, revealing the
|
|
80
|
+
* game's own background and brand from black.
|
|
81
|
+
*/
|
|
82
|
+
export const SPLASH_CSS = `
|
|
83
|
+
.${SPLASH_CLASS} {
|
|
84
|
+
position: absolute;
|
|
85
|
+
top: 0; left: 0;
|
|
86
|
+
width: 100%; height: 100%;
|
|
87
|
+
background: #000;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
z-index: 1;
|
|
92
|
+
/* Never swallow the tap — the overlay's own pointerdown gate sits below. */
|
|
93
|
+
pointer-events: none;
|
|
94
|
+
animation: ge-splash-backdrop ${SPLASH_DURATION_MS}ms linear forwards;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@keyframes ge-splash-backdrop {
|
|
98
|
+
0%, 85% { opacity: 1; }
|
|
99
|
+
100% { opacity: 0; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ge-splash-logo {
|
|
103
|
+
width: 62%;
|
|
104
|
+
max-width: 520px;
|
|
105
|
+
height: auto;
|
|
106
|
+
animation: ge-splash-logo ${SPLASH_DURATION_MS}ms ease-in-out forwards;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@keyframes ge-splash-logo {
|
|
110
|
+
0% { opacity: 0; }
|
|
111
|
+
25% { opacity: 1; }
|
|
112
|
+
70% { opacity: 1; }
|
|
113
|
+
85% { opacity: 0; }
|
|
114
|
+
100% { opacity: 0; }
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { energy8Variant } from './energy8';
|
|
2
|
+
import { slottechVariant } from './slottech';
|
|
2
3
|
import { voidmoonVariant } from './voidmoon';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -8,6 +9,7 @@ import { voidmoonVariant } from './voidmoon';
|
|
|
8
9
|
*/
|
|
9
10
|
export const VARIANTS = {
|
|
10
11
|
energy8: energy8Variant,
|
|
12
|
+
slottech: slottechVariant,
|
|
11
13
|
voidmoon: voidmoonVariant,
|
|
12
14
|
} as const;
|
|
13
15
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { PreloaderVariant, PreloaderVariantHandle } from './types';
|
|
2
|
+
|
|
3
|
+
/** Element ids the lifecycle handle binds to. */
|
|
4
|
+
const RECT_ID = 'ge-st-loader-rect';
|
|
5
|
+
const TEXT_ID = 'ge-st-loader-text';
|
|
6
|
+
|
|
7
|
+
/** Max width (SVG units) of the slottech loader bar fill — spans the artwork's full width. */
|
|
8
|
+
const LOADER_BAR_MAX_WIDTH = 841.89;
|
|
9
|
+
|
|
10
|
+
/** Flat wordmark fill, straight from the official artwork (no gradient). */
|
|
11
|
+
const LOGO_FILL = '#f5f5f5';
|
|
12
|
+
/** Ambient glow tint, keyed off the fill. */
|
|
13
|
+
const GLOW = 'rgba(245, 245, 245, 0.22)';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* "SlotTech" wordmark — the official logo embedded verbatim as vector outlines
|
|
17
|
+
* (the "ST" monogram above, "SLOTTECH" lettering below). The artwork ends at
|
|
18
|
+
* y=426.32; the loader bar and status text are added beneath it in the extended
|
|
19
|
+
* viewBox.
|
|
20
|
+
*
|
|
21
|
+
* The one deviation from the export: the "O" of SLOTTECH ships as two separate
|
|
22
|
+
* filled paths (outer + counter), which would paint a solid slab. They are
|
|
23
|
+
* merged into a single `fill-rule="evenodd"` path so the counter stays a hole.
|
|
24
|
+
*/
|
|
25
|
+
const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.89 520" class="ge-st-logo-svg" role="img">
|
|
26
|
+
<title>SlotTech</title>
|
|
27
|
+
<g fill="${LOGO_FILL}">
|
|
28
|
+
<path d="M147.7,289.83l37.26-55.73c1.75-2.61,4.67-4.18,7.81-4.17,19.91.04,120.3-.2,161.37-.13,15.28.03,27.67-12.36,27.65-27.64h0c-.02-15.26-12.4-27.6-27.66-27.57-.9,0-82.45.49-83.44.49-48.78.09-88.18-39.8-87.46-88.58h0C183.93,38.8,222.81.49,270.52.49l421.66-.49-40.64,56.9c-3.88,4.36-9.44,6.86-15.28,6.87-49.38.03-293.63-.02-365.45.13-12.9.03-23.36,10.42-23.49,23.31h0c-.13,13,10.29,23.64,23.28,23.78,25.44.28,80.28.99,88.33,1.19,48.36,1.24,86.86,40.91,86.6,89.28h0c-.26,48.88-39.96,88.37-88.84,88.37h-209Z"/>
|
|
29
|
+
<path d="M431.26,289.83h76.99l35.07-165.93c1.19-6.22-.72-12.63-5.11-17.19h0c-3.55-3.69-8.4-5.84-13.52-5.98l-102.5-.23c-3.34-.04-5.26,3.79-3.23,6.44,62.27,45.72,62.12,111.86,22.04,167.08-.68.94-1.49,1.79-2.38,2.52l-9.97,8.24c-1.5,2.12.01,5.04,2.61,5.04Z"/>
|
|
30
|
+
<path d="M66.01,376.51v-11.84l-47.67-.13C8.22,364.52,0,372.72,0,382.84h0c0,10.08,8.15,18.26,18.23,18.29l33.07-.16c3.7.01,6.69,3.02,6.69,6.72h0c0,3.71-3.01,6.72-6.72,6.72l-50.07.25v11.5l50.17.16c10.26,0,18.55-8.37,18.45-18.63h0c-.1-10.06-8.23-18.19-18.3-18.28l-34.94.03c-3.38-.03-6.11-2.76-6.16-6.14h0c-.05-3.46,2.74-6.3,6.2-6.31l49.36-.48Z"/>
|
|
31
|
+
<polygon points="107.82 426.32 176.27 426.32 176.27 414.31 120.71 414.31 120.71 364.55 107.82 364.55 107.82 426.32"/>
|
|
32
|
+
<path fill-rule="evenodd" d="M288.1,381.64v27.6c0,9.44-7.66,17.08-17.1,17.08h-40.89c-9.44,0-17.08-7.64-17.08-17.08v-27.6c0-9.44,7.64-17.1,17.08-17.1h40.89c9.44,0,17.1,7.66,17.1,17.1ZM274.68,382.63v24.86c0,3.58-2.89,6.48-6.46,6.48h-35.5c-3.56,0-6.46-2.9-6.46-6.48v-24.86c0-3.57,2.9-6.46,6.46-6.46h35.5c3.57,0,6.46,2.89,6.46,6.46Z"/>
|
|
33
|
+
<polygon points="368.74 426.32 368.91 376.17 400.44 376.17 400.44 364.55 324.84 364.55 324.84 376.17 356.19 376.17 356.19 426.32 368.74 426.32"/>
|
|
34
|
+
<polygon points="482.21 425.95 482.39 375.8 513.91 375.8 513.91 364.18 438.32 364.18 438.32 375.8 469.67 375.8 469.67 425.95 482.21 425.95"/>
|
|
35
|
+
<rect x="554.23" y="364.55" width="71.41" height="11.97"/>
|
|
36
|
+
<rect x="554.23" y="389.26" width="71.41" height="11.97"/>
|
|
37
|
+
<rect x="554.23" y="413.98" width="71.41" height="11.97"/>
|
|
38
|
+
<path d="M733.29,376.51v-11.97h-49.5c-9.79,0-17.73,7.94-17.73,17.73v25.95c0,9.79,7.94,17.73,17.73,17.73h49.5v-11.97h-48.67c-3.52,0-6.37-2.85-6.37-6.37v-24.72c0-3.52,2.85-6.37,6.37-6.37h48.67Z"/>
|
|
39
|
+
<polygon points="773.52 425.95 785.8 425.95 785.98 401.16 829.26 400.99 829.26 425.95 841.89 425.95 841.89 364.55 829.35 364.55 829.35 389.49 786.15 389.49 786.15 364.55 773.52 364.55 773.52 425.95"/>
|
|
40
|
+
</g>
|
|
41
|
+
|
|
42
|
+
<rect x="0" y="460" width="841.89" height="12" rx="6" fill="rgba(255,255,255,0.12)"/>
|
|
43
|
+
<clipPath id="ge-st-loader-clip">
|
|
44
|
+
<rect id="${RECT_ID}" x="0" y="460" width="0" height="12" rx="6" class="ge-st-clip-rect"/>
|
|
45
|
+
</clipPath>
|
|
46
|
+
<rect x="0" y="460" width="841.89" height="12" rx="6" fill="${LOGO_FILL}" clip-path="url(#ge-st-loader-clip)"/>
|
|
47
|
+
|
|
48
|
+
<text id="${TEXT_ID}" x="420.95" y="510" text-anchor="middle" class="ge-st-text">Loading...</text>
|
|
49
|
+
</svg>`;
|
|
50
|
+
|
|
51
|
+
export const slottechVariant: PreloaderVariant = {
|
|
52
|
+
buildContentHTML() {
|
|
53
|
+
return `
|
|
54
|
+
<div class="ge-st-content">
|
|
55
|
+
${LOGO_SVG}
|
|
56
|
+
</div>
|
|
57
|
+
`;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
css: `
|
|
61
|
+
.ge-st-content {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
align-items: center;
|
|
65
|
+
width: 78%;
|
|
66
|
+
max-width: 620px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.ge-st-logo-svg {
|
|
70
|
+
width: 100%;
|
|
71
|
+
height: auto;
|
|
72
|
+
filter: drop-shadow(0 0 26px ${GLOW});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Shimmer the loader bar while waiting */
|
|
76
|
+
.ge-st-clip-rect {
|
|
77
|
+
animation: ge-st-fill 2s ease-in-out infinite;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@keyframes ge-st-fill {
|
|
81
|
+
0% { width: 0; }
|
|
82
|
+
50% { width: 841.89; }
|
|
83
|
+
100% { width: 0; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Stop shimmer once JS-driven progress takes over. */
|
|
87
|
+
.ge-st-clip-rect.driven {
|
|
88
|
+
animation: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ge-st-text {
|
|
92
|
+
fill: rgba(255, 255, 255, 0.6);
|
|
93
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
94
|
+
font-size: 22px;
|
|
95
|
+
font-weight: 600;
|
|
96
|
+
letter-spacing: 3px;
|
|
97
|
+
animation: ge-st-pulse 1.5s ease-in-out infinite;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes ge-st-pulse {
|
|
101
|
+
0%, 100% { opacity: 0.4; }
|
|
102
|
+
50% { opacity: 1; }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Tap-to-start CTA pulse. Compound selector outweighs the ambient
|
|
106
|
+
.ge-st-text rule, swapping the animation cleanly. */
|
|
107
|
+
.ge-st-text.ge-st-tap-pulse {
|
|
108
|
+
animation: ge-st-tap 1.2s ease-in-out infinite;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@keyframes ge-st-tap {
|
|
112
|
+
0%, 100% { opacity: 0.5; }
|
|
113
|
+
50% { opacity: 1; }
|
|
114
|
+
}
|
|
115
|
+
`,
|
|
116
|
+
|
|
117
|
+
mount(overlay): PreloaderVariantHandle | null {
|
|
118
|
+
const rectEl = overlay.querySelector(`#${RECT_ID}`) as SVGRectElement | null;
|
|
119
|
+
const textEl = overlay.querySelector(`#${TEXT_ID}`) as SVGTextElement | null;
|
|
120
|
+
if (!rectEl || !textEl) return null;
|
|
121
|
+
|
|
122
|
+
let driven = false;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
setProgress(p, showPercentage) {
|
|
126
|
+
if (!driven) {
|
|
127
|
+
rectEl.classList.add('driven');
|
|
128
|
+
driven = true;
|
|
129
|
+
}
|
|
130
|
+
rectEl.setAttribute('width', String(p * LOADER_BAR_MAX_WIDTH));
|
|
131
|
+
if (showPercentage) {
|
|
132
|
+
textEl.textContent = `${Math.round(p * 100)}%`;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
showTapText(text) {
|
|
136
|
+
textEl.textContent = text;
|
|
137
|
+
textEl.classList.add('ge-st-tap-pulse');
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
};
|