@energy8platform/platform-core 0.25.4 → 0.26.1
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/game-spec.d.ts +3 -0
- package/dist/index.cjs.js +1772 -187
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +77 -9
- package/dist/index.esm.js +1772 -187
- package/dist/index.esm.js.map +1 -1
- package/dist/loading.cjs.js +237 -90
- package/dist/loading.cjs.js.map +1 -1
- package/dist/loading.d.ts +52 -2
- package/dist/loading.esm.js +235 -90
- package/dist/loading.esm.js.map +1 -1
- package/dist/shell.cjs.js +1539 -97
- package/dist/shell.cjs.js.map +1 -1
- package/dist/shell.d.ts +43 -11
- package/dist/shell.esm.js +1538 -98
- package/dist/shell.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/game-spec/types.ts +3 -0
- package/src/loading/CSSPreloader.ts +21 -115
- package/src/loading/index.ts +6 -0
- package/src/loading/variants/energy8.ts +105 -0
- package/src/loading/variants/index.ts +19 -0
- package/src/loading/variants/types.ts +36 -0
- package/src/loading/variants/voidmoon.ts +134 -0
- package/src/shell/GameShell.ts +87 -41
- package/src/shell/components/BuyBonus.ts +160 -14
- package/src/shell/components/GameInfo.ts +104 -5
- package/src/shell/components/Settings.ts +9 -10
- package/src/shell/components/pickers.ts +66 -10
- package/src/shell/components/primitives.ts +4 -3
- package/src/shell/i18n.ts +23 -0
- package/src/shell/index.ts +2 -1
- package/src/shell/keyboard.ts +229 -0
- package/src/shell/locales.ts +864 -0
- package/src/shell/shell.css.ts +37 -14
- package/src/shell/types.ts +8 -0
- package/src/shell/version.ts +1 -1
- package/src/types.ts +8 -0
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import type { LoadingScreenConfig } from '../types';
|
|
2
|
-
import {
|
|
2
|
+
import { VARIANTS, DEFAULT_VARIANT_NAME } from './variants';
|
|
3
|
+
import type { PreloaderVariantHandle } from './variants';
|
|
3
4
|
|
|
4
5
|
const PRELOADER_ID = '__ge-css-preloader__';
|
|
5
|
-
const RECT_ID = 'ge-pl-loader-rect';
|
|
6
|
-
const TEXT_ID = 'ge-pl-loader-text';
|
|
7
6
|
const REMOVE_FADE_TIMEOUT_MS = 600;
|
|
8
7
|
|
|
9
|
-
const LOGO_SVG = buildLogoSVG({
|
|
10
|
-
idPrefix: 'pl',
|
|
11
|
-
svgClass: 'ge-logo-svg',
|
|
12
|
-
clipRectClass: 'ge-clip-rect',
|
|
13
|
-
clipRectId: RECT_ID,
|
|
14
|
-
textClass: 'ge-preloader-svg-text',
|
|
15
|
-
textId: TEXT_ID,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
8
|
interface PreloaderState {
|
|
19
9
|
container: HTMLElement;
|
|
20
10
|
/** The container's inline `position` before we overrode it — restored on removal so we don't
|
|
@@ -23,12 +13,11 @@ interface PreloaderState {
|
|
|
23
13
|
prevPosition: string;
|
|
24
14
|
overlay: HTMLDivElement;
|
|
25
15
|
styleEl: HTMLStyleElement;
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
/** Live binding to the selected variant's DOM; `null` for custom-HTML (inert lifecycle). */
|
|
17
|
+
handle: PreloaderVariantHandle | null;
|
|
28
18
|
showPercentage: boolean;
|
|
29
19
|
tapToStart: boolean;
|
|
30
20
|
tapToStartText: string;
|
|
31
|
-
driven: boolean;
|
|
32
21
|
tapState: 'idle' | 'waiting' | 'resolved';
|
|
33
22
|
tapPromise: Promise<void> | null;
|
|
34
23
|
tapResolve: (() => void) | null;
|
|
@@ -61,15 +50,19 @@ export function createCSSPreloader(
|
|
|
61
50
|
|
|
62
51
|
const customHTML = config?.cssPreloaderHTML ?? '';
|
|
63
52
|
|
|
53
|
+
// Pick the visual identity. Unknown names fall back to the default so a bad
|
|
54
|
+
// config value degrades to a working preloader rather than a blank overlay.
|
|
55
|
+
const variant =
|
|
56
|
+
VARIANTS[config?.preloaderVariant ?? DEFAULT_VARIANT_NAME] ??
|
|
57
|
+
VARIANTS[DEFAULT_VARIANT_NAME];
|
|
58
|
+
|
|
64
59
|
const overlay = document.createElement('div');
|
|
65
60
|
overlay.id = PRELOADER_ID;
|
|
66
|
-
overlay.innerHTML = customHTML ||
|
|
67
|
-
<div class="ge-preloader-content">
|
|
68
|
-
${LOGO_SVG}
|
|
69
|
-
</div>
|
|
70
|
-
`;
|
|
61
|
+
overlay.innerHTML = customHTML || variant.buildContentHTML(config);
|
|
71
62
|
|
|
72
63
|
const styleEl = document.createElement('style');
|
|
64
|
+
// Shared overlay infrastructure (positioning / background / fade) plus the
|
|
65
|
+
// variant's own content styling and animations.
|
|
73
66
|
styleEl.textContent = `
|
|
74
67
|
#${PRELOADER_ID} {
|
|
75
68
|
position: absolute;
|
|
@@ -88,57 +81,7 @@ export function createCSSPreloader(
|
|
|
88
81
|
opacity: 0;
|
|
89
82
|
pointer-events: none;
|
|
90
83
|
}
|
|
91
|
-
|
|
92
|
-
.ge-preloader-content {
|
|
93
|
-
display: flex;
|
|
94
|
-
flex-direction: column;
|
|
95
|
-
align-items: center;
|
|
96
|
-
width: 80%;
|
|
97
|
-
max-width: 700px;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.ge-logo-svg {
|
|
101
|
-
width: 100%;
|
|
102
|
-
height: auto;
|
|
103
|
-
filter: drop-shadow(0 0 30px rgba(121, 57, 194, 0.4));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* Animate the loader clip-rect to shimmer while waiting */
|
|
107
|
-
.ge-clip-rect {
|
|
108
|
-
animation: ge-loader-fill 2s ease-in-out infinite;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
@keyframes ge-loader-fill {
|
|
112
|
-
0% { width: 0; }
|
|
113
|
-
50% { width: 174; }
|
|
114
|
-
100% { width: 0; }
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* Animate the SVG text opacity */
|
|
118
|
-
.ge-preloader-svg-text {
|
|
119
|
-
animation: ge-pulse 1.5s ease-in-out infinite;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@keyframes ge-pulse {
|
|
123
|
-
0%, 100% { opacity: 0.4; }
|
|
124
|
-
50% { opacity: 1; }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/* Stop shimmer once JS-driven progress takes over. */
|
|
128
|
-
.ge-clip-rect.driven {
|
|
129
|
-
animation: none;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/* Tap-to-start CTA pulse. Compound selector outweighs the ambient
|
|
133
|
-
.ge-preloader-svg-text rule, swapping the animation cleanly. */
|
|
134
|
-
.ge-preloader-svg-text.ge-svg-pulse {
|
|
135
|
-
animation: ge-tap-pulse 1.2s ease-in-out infinite;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@keyframes ge-tap-pulse {
|
|
139
|
-
0%, 100% { opacity: 0.5; }
|
|
140
|
-
50% { opacity: 1; }
|
|
141
|
-
}
|
|
84
|
+
${variant.css}
|
|
142
85
|
`;
|
|
143
86
|
|
|
144
87
|
// The absolute overlay needs a positioned ancestor. Only override a STATIC container, and
|
|
@@ -149,42 +92,19 @@ export function createCSSPreloader(
|
|
|
149
92
|
container.appendChild(styleEl);
|
|
150
93
|
container.appendChild(overlay);
|
|
151
94
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// Custom HTML mode — no logo SVG, lifecycle API becomes mostly inert.
|
|
156
|
-
// We still record state so removeCSSPreloader works.
|
|
157
|
-
state = {
|
|
158
|
-
container,
|
|
159
|
-
prevPosition,
|
|
160
|
-
overlay,
|
|
161
|
-
styleEl,
|
|
162
|
-
rectEl: null as unknown as SVGRectElement,
|
|
163
|
-
textEl: null as unknown as SVGTextElement,
|
|
164
|
-
showPercentage: false,
|
|
165
|
-
tapToStart: config?.tapToStart !== false,
|
|
166
|
-
tapToStartText: config?.tapToStartText ?? 'TAP TO START',
|
|
167
|
-
driven: false,
|
|
168
|
-
tapState: 'idle',
|
|
169
|
-
tapPromise: null,
|
|
170
|
-
tapResolve: null,
|
|
171
|
-
tapHandler: null,
|
|
172
|
-
removed: false,
|
|
173
|
-
};
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
95
|
+
// Custom HTML bypasses the variant's content, so there is no progress target
|
|
96
|
+
// to bind to and the handle stays null (lifecycle API becomes inert).
|
|
97
|
+
const handle = customHTML ? null : variant.mount(overlay, config);
|
|
176
98
|
|
|
177
99
|
state = {
|
|
178
100
|
container,
|
|
179
101
|
prevPosition,
|
|
180
102
|
overlay,
|
|
181
103
|
styleEl,
|
|
182
|
-
|
|
183
|
-
textEl,
|
|
104
|
+
handle,
|
|
184
105
|
showPercentage: config?.showPercentage === true,
|
|
185
106
|
tapToStart: config?.tapToStart !== false,
|
|
186
107
|
tapToStartText: config?.tapToStartText ?? 'TAP TO START',
|
|
187
|
-
driven: false,
|
|
188
108
|
tapState: 'idle',
|
|
189
109
|
tapPromise: null,
|
|
190
110
|
tapResolve: null,
|
|
@@ -196,20 +116,9 @@ export function createCSSPreloader(
|
|
|
196
116
|
export function setCSSPreloaderProgress(progress: number): void {
|
|
197
117
|
if (!state || state.removed) return;
|
|
198
118
|
if (state.tapState === 'waiting' || state.tapState === 'resolved') return;
|
|
199
|
-
if (!state.
|
|
200
|
-
|
|
201
|
-
const p = clampProgress(progress);
|
|
202
|
-
|
|
203
|
-
if (!state.driven) {
|
|
204
|
-
state.rectEl.classList.add('driven');
|
|
205
|
-
state.driven = true;
|
|
206
|
-
}
|
|
119
|
+
if (!state.handle) return;
|
|
207
120
|
|
|
208
|
-
state.
|
|
209
|
-
|
|
210
|
-
if (state.showPercentage && state.textEl) {
|
|
211
|
-
state.textEl.textContent = `${Math.round(p * 100)}%`;
|
|
212
|
-
}
|
|
121
|
+
state.handle.setProgress(clampProgress(progress), state.showPercentage);
|
|
213
122
|
}
|
|
214
123
|
|
|
215
124
|
export function waitCSSPreloaderTap(): Promise<void> {
|
|
@@ -222,10 +131,7 @@ export function waitCSSPreloaderTap(): Promise<void> {
|
|
|
222
131
|
if (!state.tapToStart) return Promise.resolve();
|
|
223
132
|
if (state.tapPromise) return state.tapPromise;
|
|
224
133
|
|
|
225
|
-
|
|
226
|
-
state.textEl.textContent = state.tapToStartText;
|
|
227
|
-
state.textEl.classList.add('ge-svg-pulse');
|
|
228
|
-
}
|
|
134
|
+
state.handle?.showTapText(state.tapToStartText);
|
|
229
135
|
state.overlay.style.cursor = 'pointer';
|
|
230
136
|
|
|
231
137
|
state.tapState = 'waiting';
|
package/src/loading/index.ts
CHANGED
|
@@ -5,4 +5,10 @@ export {
|
|
|
5
5
|
removeCSSPreloader,
|
|
6
6
|
} from './CSSPreloader';
|
|
7
7
|
export { buildLogoSVG, LOADER_BAR_MAX_WIDTH } from './logo';
|
|
8
|
+
export { VARIANTS, DEFAULT_VARIANT_NAME } from './variants';
|
|
9
|
+
export type {
|
|
10
|
+
PreloaderVariant,
|
|
11
|
+
PreloaderVariantHandle,
|
|
12
|
+
PreloaderVariantName,
|
|
13
|
+
} from './variants';
|
|
8
14
|
export type { LoadingScreenConfig, AssetManifest, AssetBundle, AssetEntry } from '../types';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { buildLogoSVG, LOADER_BAR_MAX_WIDTH } from '../logo';
|
|
2
|
+
import type { PreloaderVariant, PreloaderVariantHandle } from './types';
|
|
3
|
+
|
|
4
|
+
/** Element ids the lifecycle handle binds to (also asserted by tests). */
|
|
5
|
+
const RECT_ID = 'ge-pl-loader-rect';
|
|
6
|
+
const TEXT_ID = 'ge-pl-loader-text';
|
|
7
|
+
|
|
8
|
+
const LOGO_SVG = buildLogoSVG({
|
|
9
|
+
idPrefix: 'pl',
|
|
10
|
+
svgClass: 'ge-logo-svg',
|
|
11
|
+
clipRectClass: 'ge-clip-rect',
|
|
12
|
+
clipRectId: RECT_ID,
|
|
13
|
+
textClass: 'ge-preloader-svg-text',
|
|
14
|
+
textId: TEXT_ID,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/** The default Energy8-branded preloader: animated wordmark + shimmering loader bar. */
|
|
18
|
+
export const energy8Variant: PreloaderVariant = {
|
|
19
|
+
buildContentHTML() {
|
|
20
|
+
return `
|
|
21
|
+
<div class="ge-preloader-content">
|
|
22
|
+
${LOGO_SVG}
|
|
23
|
+
</div>
|
|
24
|
+
`;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
css: `
|
|
28
|
+
.ge-preloader-content {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
align-items: center;
|
|
32
|
+
width: 80%;
|
|
33
|
+
max-width: 700px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ge-logo-svg {
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: auto;
|
|
39
|
+
filter: drop-shadow(0 0 30px rgba(121, 57, 194, 0.4));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Animate the loader clip-rect to shimmer while waiting */
|
|
43
|
+
.ge-clip-rect {
|
|
44
|
+
animation: ge-loader-fill 2s ease-in-out infinite;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@keyframes ge-loader-fill {
|
|
48
|
+
0% { width: 0; }
|
|
49
|
+
50% { width: 174; }
|
|
50
|
+
100% { width: 0; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Animate the SVG text opacity */
|
|
54
|
+
.ge-preloader-svg-text {
|
|
55
|
+
animation: ge-pulse 1.5s ease-in-out infinite;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@keyframes ge-pulse {
|
|
59
|
+
0%, 100% { opacity: 0.4; }
|
|
60
|
+
50% { opacity: 1; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Stop shimmer once JS-driven progress takes over. */
|
|
64
|
+
.ge-clip-rect.driven {
|
|
65
|
+
animation: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Tap-to-start CTA pulse. Compound selector outweighs the ambient
|
|
69
|
+
.ge-preloader-svg-text rule, swapping the animation cleanly. */
|
|
70
|
+
.ge-preloader-svg-text.ge-svg-pulse {
|
|
71
|
+
animation: ge-tap-pulse 1.2s ease-in-out infinite;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes ge-tap-pulse {
|
|
75
|
+
0%, 100% { opacity: 0.5; }
|
|
76
|
+
50% { opacity: 1; }
|
|
77
|
+
}
|
|
78
|
+
`,
|
|
79
|
+
|
|
80
|
+
mount(overlay): PreloaderVariantHandle | null {
|
|
81
|
+
const rectEl = overlay.querySelector(`#${RECT_ID}`) as SVGRectElement | null;
|
|
82
|
+
const textEl = overlay.querySelector(`#${TEXT_ID}`) as SVGTextElement | null;
|
|
83
|
+
// Custom HTML mode (or missing logo) — no progress target; lifecycle inert.
|
|
84
|
+
if (!rectEl || !textEl) return null;
|
|
85
|
+
|
|
86
|
+
let driven = false;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
setProgress(p, showPercentage) {
|
|
90
|
+
if (!driven) {
|
|
91
|
+
rectEl.classList.add('driven');
|
|
92
|
+
driven = true;
|
|
93
|
+
}
|
|
94
|
+
rectEl.setAttribute('width', String(p * LOADER_BAR_MAX_WIDTH));
|
|
95
|
+
if (showPercentage) {
|
|
96
|
+
textEl.textContent = `${Math.round(p * 100)}%`;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
showTapText(text) {
|
|
100
|
+
textEl.textContent = text;
|
|
101
|
+
textEl.classList.add('ge-svg-pulse');
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { energy8Variant } from './energy8';
|
|
2
|
+
import { voidmoonVariant } from './voidmoon';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Registry of selectable preloader variants. Add a new variant by writing a
|
|
6
|
+
* file in this folder and adding one entry here — `PreloaderVariantName` and
|
|
7
|
+
* `LoadingScreenConfig.preloaderVariant` widen automatically.
|
|
8
|
+
*/
|
|
9
|
+
export const VARIANTS = {
|
|
10
|
+
energy8: energy8Variant,
|
|
11
|
+
voidmoon: voidmoonVariant,
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
/** Default variant used when `preloaderVariant` is omitted or unknown. */
|
|
15
|
+
export const DEFAULT_VARIANT_NAME = 'energy8';
|
|
16
|
+
|
|
17
|
+
export type PreloaderVariantName = keyof typeof VARIANTS;
|
|
18
|
+
|
|
19
|
+
export type { PreloaderVariant, PreloaderVariantHandle } from './types';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { LoadingScreenConfig } from '../../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A live binding between the CSS preloader's lifecycle API
|
|
5
|
+
* (`setCSSPreloaderProgress` / `waitCSSPreloaderTap`) and the variant's own
|
|
6
|
+
* DOM. Returned by {@link PreloaderVariant.mount}; `null` when the variant has
|
|
7
|
+
* no progress target (e.g. a custom-HTML override), which makes the lifecycle
|
|
8
|
+
* inert.
|
|
9
|
+
*/
|
|
10
|
+
export interface PreloaderVariantHandle {
|
|
11
|
+
/** Drive the progress indicator. `p` is already clamped to [0, 1]. */
|
|
12
|
+
setProgress(p: number, showPercentage: boolean): void;
|
|
13
|
+
/** Swap the waiting indicator to the tap-to-start cue. */
|
|
14
|
+
showTapText(text: string): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A selectable visual identity for the CSS preloader. Encapsulates the markup,
|
|
19
|
+
* its scoped CSS (animations + logo styling), and how progress/tap are driven —
|
|
20
|
+
* everything `CSSPreloader.ts` does NOT own (it keeps overlay/background/fade
|
|
21
|
+
* infrastructure and the shared tap-listener machinery).
|
|
22
|
+
*/
|
|
23
|
+
export interface PreloaderVariant {
|
|
24
|
+
/** Inner HTML for the overlay (including the variant's own content wrapper). */
|
|
25
|
+
buildContentHTML(config?: LoadingScreenConfig): string;
|
|
26
|
+
/** Variant-specific CSS appended after the shared base styles. */
|
|
27
|
+
css: string;
|
|
28
|
+
/**
|
|
29
|
+
* Bind to the freshly-mounted overlay and return a handle, or `null` if the
|
|
30
|
+
* variant's progress target is absent (lifecycle then becomes inert).
|
|
31
|
+
*/
|
|
32
|
+
mount(
|
|
33
|
+
overlay: HTMLElement,
|
|
34
|
+
config?: LoadingScreenConfig,
|
|
35
|
+
): PreloaderVariantHandle | null;
|
|
36
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { PreloaderVariant, PreloaderVariantHandle } from './types';
|
|
2
|
+
|
|
3
|
+
/** Element ids the lifecycle handle binds to. */
|
|
4
|
+
const RECT_ID = 'ge-vm-loader-rect';
|
|
5
|
+
const TEXT_ID = 'ge-vm-loader-text';
|
|
6
|
+
|
|
7
|
+
/** Max width (SVG units) of the voidmoon loader bar fill. Spans the first 'o' → end of the crescent. */
|
|
8
|
+
const LOADER_BAR_MAX_WIDTH = 751;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* "voidmoon" wordmark — the official logo, embedded verbatim as SVG outlines:
|
|
12
|
+
* thin white letters with the final "o" of "moon" rendered as a purple crescent
|
|
13
|
+
* (#9D63FE). The glyphs live in a flipped group (`translate(0,941) scale(1,-1)`)
|
|
14
|
+
* exactly as exported; the loader bar + status text are added beneath it in the
|
|
15
|
+
* outer (un-flipped) viewBox space.
|
|
16
|
+
*/
|
|
17
|
+
const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="301 339 1075 335" class="ge-vm-logo-svg" style="overflow:visible" role="img">
|
|
18
|
+
<title>voidmoon</title>
|
|
19
|
+
<g transform="translate(0,941) scale(1,-1)">
|
|
20
|
+
<g fill="#ffffff" fill-rule="evenodd">
|
|
21
|
+
<path d="M627 562 c-4 -2 -7 -6 -7 -12 0 -13 14 -18 23 -10 3 3 3 5 4 9 0 7 -2 11 -7 13 -5 2 -9 2 -13 0z"/>
|
|
22
|
+
<path d="M780 531 l0 -31 -6 5 c-14 14 -35 17 -56 10 -24 -8 -40 -28 -42 -53 0 -11 1 -19 6 -30 8 -16 22 -27 40 -32 8 -2 23 -2 31 0 18 4 34 17 42 33 2 5 4 11 5 14 1 3 1 24 1 61 l0 55 -10 0 -11 0 0 -32z m-24 -38 c20 -10 28 -31 20 -50 -3 -6 -13 -16 -19 -19 -21 -10 -45 -2 -56 18 -2 4 -2 7 -3 14 -1 14 4 24 15 33 8 6 15 8 27 8 9 -1 10 -1 16 -4z"/>
|
|
23
|
+
<path d="M520 518 c-26 -6 -45 -25 -50 -51 -1 -9 -1 -11 0 -19 3 -12 7 -21 15 -30 8 -8 16 -13 28 -17 6 -2 9 -2 19 -2 10 0 13 0 20 2 21 8 36 24 41 46 1 8 1 13 0 22 -5 23 -21 40 -44 47 -6 2 -23 3 -29 2z m29 -25 c9 -4 16 -11 20 -19 2 -5 3 -6 3 -15 0 -10 -1 -11 -3 -16 -8 -15 -23 -24 -40 -23 -9 1 -15 3 -22 8 -22 15 -21 48 2 63 7 4 14 6 24 6 8 -1 10 -1 16 -4z"/>
|
|
24
|
+
<path d="M869 518 c-21 -4 -35 -18 -40 -38 -1 -6 -1 -14 -1 -43 l1 -35 10 0 11 0 0 37 c0 33 1 38 2 41 3 7 7 11 13 14 5 2 8 3 13 3 11 0 20 -5 25 -16 l3 -5 0 -37 1 -37 10 0 10 0 0 37 c0 35 1 37 3 42 5 10 14 16 26 16 11 0 21 -7 25 -17 2 -5 2 -7 2 -41 0 -19 0 -36 1 -37 0 -1 3 -1 11 -1 l10 1 0 35 c0 23 0 38 -1 42 -2 9 -8 21 -14 27 -20 17 -51 17 -68 -1 l-5 -5 -5 5 c-9 9 -19 13 -31 14 -5 0 -10 0 -12 -1z"/>
|
|
25
|
+
<path d="M1077 517 c-9 -1 -20 -7 -27 -12 -7 -6 -14 -16 -18 -25 -4 -11 -5 -25 -3 -35 5 -21 21 -37 42 -44 38 -12 78 14 81 53 1 15 -4 31 -14 43 -14 17 -38 25 -61 20z m25 -22 c19 -5 31 -24 28 -42 -4 -26 -34 -41 -59 -29 -20 10 -27 32 -17 52 8 16 29 25 48 19z"/>
|
|
26
|
+
<path d="M1282 516 c-18 -5 -34 -21 -38 -39 -1 -4 -1 -18 -1 -40 l1 -35 10 -1 10 0 0 32 c0 20 0 35 1 38 2 11 8 18 18 23 7 3 18 3 26 0 6 -3 12 -9 15 -16 2 -4 3 -6 3 -40 l1 -36 10 0 11 0 0 33 c0 38 0 43 -6 54 -7 14 -19 24 -34 28 -7 1 -20 1 -27 -1z"/>
|
|
27
|
+
<path d="M329 515 c0 -1 2 -5 4 -9 2 -5 13 -28 24 -53 12 -24 21 -45 22 -46 2 -4 10 -7 15 -7 4 0 11 3 13 6 3 2 50 105 50 108 0 1 -3 1 -11 1 l-11 0 -7 -14 c-3 -8 -12 -28 -20 -45 -7 -17 -13 -31 -14 -31 -1 -1 -3 5 -17 35 -19 43 -23 53 -24 54 -1 1 -5 1 -13 1 -6 0 -11 0 -11 0z"/>
|
|
28
|
+
<path d="M623 514 c0 -1 0 -26 0 -57 l1 -55 10 0 10 0 0 56 0 57 -10 0 c-7 0 -10 0 -11 -1z"/>
|
|
29
|
+
</g>
|
|
30
|
+
<g fill="#9D63FE" fill-rule="evenodd">
|
|
31
|
+
<path d="M1150 515 c-3 0 -6 -1 -6 -1 0 -1 2 -2 5 -2 10 -4 26 -17 31 -27 11 -21 8 -44 -7 -62 -5 -7 -17 -16 -24 -18 -3 -1 -5 -2 -4 -2 0 -2 16 -3 24 -3 35 3 59 40 49 74 -2 8 -7 18 -13 24 -5 6 -15 13 -23 16 -8 3 -24 4 -32 1z"/>
|
|
32
|
+
</g>
|
|
33
|
+
</g>
|
|
34
|
+
|
|
35
|
+
<rect x="469" y="600" width="751" height="9" rx="4.5" fill="rgba(255,255,255,0.12)"/>
|
|
36
|
+
<clipPath id="vm-loader-clip">
|
|
37
|
+
<rect id="${RECT_ID}" x="469" y="600" width="0" height="9" rx="4.5" class="ge-vm-clip-rect"/>
|
|
38
|
+
</clipPath>
|
|
39
|
+
<rect x="469" y="600" width="751" height="9" rx="4.5" fill="#9D63FE" clip-path="url(#vm-loader-clip)"/>
|
|
40
|
+
|
|
41
|
+
<text id="${TEXT_ID}" x="844.5" y="650" text-anchor="middle" class="ge-vm-text">Loading...</text>
|
|
42
|
+
</svg>`;
|
|
43
|
+
|
|
44
|
+
export const voidmoonVariant: PreloaderVariant = {
|
|
45
|
+
buildContentHTML() {
|
|
46
|
+
return `
|
|
47
|
+
<div class="ge-vm-content">
|
|
48
|
+
${LOGO_SVG}
|
|
49
|
+
</div>
|
|
50
|
+
`;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
css: `
|
|
54
|
+
.ge-vm-content {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
align-items: center;
|
|
58
|
+
width: 82%;
|
|
59
|
+
max-width: 680px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.ge-vm-logo-svg {
|
|
63
|
+
width: 100%;
|
|
64
|
+
height: auto;
|
|
65
|
+
filter: drop-shadow(0 0 26px rgba(157, 99, 254, 0.3));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Shimmer the loader bar while waiting */
|
|
69
|
+
.ge-vm-clip-rect {
|
|
70
|
+
animation: ge-vm-fill 2s ease-in-out infinite;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@keyframes ge-vm-fill {
|
|
74
|
+
0% { width: 0; }
|
|
75
|
+
50% { width: 751; }
|
|
76
|
+
100% { width: 0; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Stop shimmer once JS-driven progress takes over. */
|
|
80
|
+
.ge-vm-clip-rect.driven {
|
|
81
|
+
animation: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ge-vm-text {
|
|
85
|
+
fill: rgba(255, 255, 255, 0.6);
|
|
86
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
87
|
+
font-size: 20px;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
letter-spacing: 3px;
|
|
90
|
+
animation: ge-vm-pulse 1.5s ease-in-out infinite;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@keyframes ge-vm-pulse {
|
|
94
|
+
0%, 100% { opacity: 0.4; }
|
|
95
|
+
50% { opacity: 1; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Tap-to-start CTA pulse. Compound selector outweighs the ambient
|
|
99
|
+
.ge-vm-text rule, swapping the animation cleanly. */
|
|
100
|
+
.ge-vm-text.ge-vm-tap-pulse {
|
|
101
|
+
animation: ge-vm-tap 1.2s ease-in-out infinite;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@keyframes ge-vm-tap {
|
|
105
|
+
0%, 100% { opacity: 0.5; }
|
|
106
|
+
50% { opacity: 1; }
|
|
107
|
+
}
|
|
108
|
+
`,
|
|
109
|
+
|
|
110
|
+
mount(overlay): PreloaderVariantHandle | null {
|
|
111
|
+
const rectEl = overlay.querySelector(`#${RECT_ID}`) as SVGRectElement | null;
|
|
112
|
+
const textEl = overlay.querySelector(`#${TEXT_ID}`) as SVGTextElement | null;
|
|
113
|
+
if (!rectEl || !textEl) return null;
|
|
114
|
+
|
|
115
|
+
let driven = false;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
setProgress(p, showPercentage) {
|
|
119
|
+
if (!driven) {
|
|
120
|
+
rectEl.classList.add('driven');
|
|
121
|
+
driven = true;
|
|
122
|
+
}
|
|
123
|
+
rectEl.setAttribute('width', String(p * LOADER_BAR_MAX_WIDTH));
|
|
124
|
+
if (showPercentage) {
|
|
125
|
+
textEl.textContent = `${Math.round(p * 100)}%`;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
showTapText(text) {
|
|
129
|
+
textEl.textContent = text;
|
|
130
|
+
textEl.classList.add('ge-vm-tap-pulse');
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
};
|