@energy8platform/shell 0.2.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/dist/html.cjs.js +3894 -0
- package/dist/html.cjs.js.map +1 -0
- package/dist/html.d.ts +616 -0
- package/dist/html.esm.js +3879 -0
- package/dist/html.esm.js.map +1 -0
- package/dist/index.cjs.js +1593 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +554 -0
- package/dist/index.esm.js +1582 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/pixi.cjs.js +5740 -0
- package/dist/pixi.cjs.js.map +1 -0
- package/dist/pixi.d.ts +682 -0
- package/dist/pixi.esm.js +5726 -0
- package/dist/pixi.esm.js.map +1 -0
- package/package.json +79 -0
- package/src/core/EventEmitter.ts +55 -0
- package/src/core/ShellController.ts +234 -0
- package/src/core/colors.ts +32 -0
- package/src/core/fonts-digits.ts +12 -0
- package/src/core/fonts.ts +13 -0
- package/src/core/format.ts +39 -0
- package/src/core/i18n.ts +96 -0
- package/src/core/index.ts +35 -0
- package/src/core/keyboard.ts +229 -0
- package/src/core/locales.ts +864 -0
- package/src/core/motion.ts +10 -0
- package/src/core/renderer.ts +121 -0
- package/src/core/state.ts +31 -0
- package/src/core/theme.ts +81 -0
- package/src/core/types.ts +269 -0
- package/src/core/version.ts +3 -0
- package/src/ui/html/HtmlRenderer.ts +292 -0
- package/src/ui/html/components/BottomBar.ts +240 -0
- package/src/ui/html/components/BuyBonus.ts +326 -0
- package/src/ui/html/components/GameInfo.ts +384 -0
- package/src/ui/html/components/Modal.ts +36 -0
- package/src/ui/html/components/ReplayModal.ts +58 -0
- package/src/ui/html/components/Settings.ts +59 -0
- package/src/ui/html/components/pickers.ts +146 -0
- package/src/ui/html/icons-preview.svg +224 -0
- package/src/ui/html/icons.ts +31 -0
- package/src/ui/html/index.ts +36 -0
- package/src/ui/html/motion-dom.ts +29 -0
- package/src/ui/html/primitives.ts +85 -0
- package/src/ui/html/shell.css.ts +528 -0
- package/src/ui/html/theme-css.ts +21 -0
- package/src/ui/pixi/PixiRenderer.ts +350 -0
- package/src/ui/pixi/components/BottomBar.ts +477 -0
- package/src/ui/pixi/components/BuyBonus.ts +763 -0
- package/src/ui/pixi/components/GameInfo.ts +559 -0
- package/src/ui/pixi/components/Modal.ts +40 -0
- package/src/ui/pixi/components/ReplayModal.ts +80 -0
- package/src/ui/pixi/components/Settings.ts +117 -0
- package/src/ui/pixi/components/pickers.ts +170 -0
- package/src/ui/pixi/context.ts +52 -0
- package/src/ui/pixi/icons.ts +45 -0
- package/src/ui/pixi/index.ts +56 -0
- package/src/ui/pixi/motion-pixi.ts +58 -0
- package/src/ui/pixi/pixi-icon.ts +119 -0
- package/src/ui/pixi/primitives/card.ts +227 -0
- package/src/ui/pixi/primitives/controls.ts +243 -0
- package/src/ui/pixi/primitives/flex.ts +335 -0
- package/src/ui/pixi/primitives/overlay.ts +181 -0
- package/src/ui/pixi/primitives/scroll.ts +117 -0
- package/src/ui/pixi/primitives/widgets.ts +680 -0
- package/src/ui/pixi/text.ts +131 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { ShellHost } from '@/core/renderer';
|
|
2
|
+
import { createCardModal } from '../primitives';
|
|
3
|
+
|
|
4
|
+
interface Choice { id: string; label: string }
|
|
5
|
+
|
|
6
|
+
interface SheetOpts {
|
|
7
|
+
ge: string;
|
|
8
|
+
title: string;
|
|
9
|
+
choices: Choice[];
|
|
10
|
+
selected: string;
|
|
11
|
+
/** Chips per row. A single number is fixed across layouts; `{ wide, mobile }` reflows
|
|
12
|
+
* with the shell's mobile breakpoint (driven by CSS custom props, so an open modal
|
|
13
|
+
* re-columns live on resize/rotate without being rebuilt). */
|
|
14
|
+
columns: number | { wide: number; mobile: number };
|
|
15
|
+
confirmLabel: string;
|
|
16
|
+
onConfirm: (id: string) => void;
|
|
17
|
+
/** Called to dismiss the picker (should invoke host.actions.closeOverlay()). */
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Result of buildSheet — the root DOM element plus a keyboard handler. */
|
|
22
|
+
interface Sheet {
|
|
23
|
+
root: HTMLElement;
|
|
24
|
+
/** Route keydown events here. Returns true if the event was consumed. */
|
|
25
|
+
onKey: (e: KeyboardEvent) => boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A centred picker (chips grid + accent Confirm) on the shared card modal. */
|
|
29
|
+
function buildSheet(opts: SheetOpts): Sheet {
|
|
30
|
+
const ui = createCardModal({ ge: opts.ge, title: opts.title, onClose: () => opts.onClose() });
|
|
31
|
+
|
|
32
|
+
const grid = document.createElement('div'); grid.className = 'ge-sheet-grid';
|
|
33
|
+
const cols = typeof opts.columns === 'number' ? { wide: opts.columns, mobile: opts.columns } : opts.columns;
|
|
34
|
+
grid.style.setProperty('--cols', String(cols.wide));
|
|
35
|
+
grid.style.setProperty('--cols-m', String(cols.mobile));
|
|
36
|
+
let selected = opts.selected;
|
|
37
|
+
let focusIndex = opts.choices.findIndex((c) => c.id === selected);
|
|
38
|
+
if (focusIndex < 0) focusIndex = 0;
|
|
39
|
+
const chips: HTMLButtonElement[] = [];
|
|
40
|
+
|
|
41
|
+
/** Update chip visuals to reflect the current selected/focused index. */
|
|
42
|
+
function setHighlight(newIndex: number): void {
|
|
43
|
+
focusIndex = newIndex;
|
|
44
|
+
selected = opts.choices[focusIndex].id;
|
|
45
|
+
for (let i = 0; i < chips.length; i++) {
|
|
46
|
+
chips[i].classList.toggle('ge-on', i === focusIndex);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < opts.choices.length; i++) {
|
|
51
|
+
const c = opts.choices[i];
|
|
52
|
+
const chip = document.createElement('button');
|
|
53
|
+
chip.className = 'ge-chip' + (i === focusIndex ? ' ge-on' : '');
|
|
54
|
+
chip.dataset.id = c.id; chip.textContent = c.label;
|
|
55
|
+
const idx = i; // capture for closure
|
|
56
|
+
chip.addEventListener('click', () => {
|
|
57
|
+
setHighlight(idx);
|
|
58
|
+
});
|
|
59
|
+
chips.push(chip); grid.appendChild(chip);
|
|
60
|
+
}
|
|
61
|
+
ui.body.appendChild(grid);
|
|
62
|
+
|
|
63
|
+
function doConfirm(): void {
|
|
64
|
+
opts.onConfirm(selected);
|
|
65
|
+
opts.onClose();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Single full-bleed Confirm; dismissal is the ✕ (top-right). No Cancel button.
|
|
69
|
+
const confirm = document.createElement('button');
|
|
70
|
+
confirm.className = 'ge-modal-btn ge-modal-btn--accent'; confirm.dataset.ge = 'sheet-confirm';
|
|
71
|
+
confirm.textContent = opts.confirmLabel;
|
|
72
|
+
confirm.addEventListener('click', doConfirm);
|
|
73
|
+
ui.card.appendChild(confirm);
|
|
74
|
+
|
|
75
|
+
function onKey(e: KeyboardEvent): boolean {
|
|
76
|
+
const last = opts.choices.length - 1;
|
|
77
|
+
switch (e.code) {
|
|
78
|
+
case 'ArrowRight':
|
|
79
|
+
case 'ArrowDown':
|
|
80
|
+
case 'Equal': // + on most keyboards
|
|
81
|
+
case 'NumpadAdd':
|
|
82
|
+
if (focusIndex < last) setHighlight(focusIndex + 1);
|
|
83
|
+
return true;
|
|
84
|
+
case 'ArrowLeft':
|
|
85
|
+
case 'ArrowUp':
|
|
86
|
+
case 'Minus':
|
|
87
|
+
case 'NumpadSubtract':
|
|
88
|
+
if (focusIndex > 0) setHighlight(focusIndex - 1);
|
|
89
|
+
return true;
|
|
90
|
+
case 'Enter':
|
|
91
|
+
case 'Space':
|
|
92
|
+
doConfirm();
|
|
93
|
+
return true;
|
|
94
|
+
case 'Escape':
|
|
95
|
+
opts.onClose();
|
|
96
|
+
return true;
|
|
97
|
+
default:
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { root: ui.root, onKey };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Bet picker — all available bets as chips (6 per row, 3 on mobile), accent Confirm applies it. */
|
|
106
|
+
export function openBetModal(host: ShellHost): { root: HTMLElement; onKey: (e: KeyboardEvent) => boolean } {
|
|
107
|
+
return buildSheet({
|
|
108
|
+
ge: 'bet-modal', title: host.t('Bet'), columns: { wide: 6, mobile: 3 }, confirmLabel: host.t('Confirm'),
|
|
109
|
+
choices: host.state.availableBets.map((b) => ({ id: String(b), label: host.formatCurrency(b) })),
|
|
110
|
+
selected: String(host.state.bet),
|
|
111
|
+
onClose: () => host.actions.closeOverlay(),
|
|
112
|
+
onConfirm: (id) => {
|
|
113
|
+
const v = Number(id);
|
|
114
|
+
host.actions.setBet(v);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const AUTOPLAY_COUNTS = [10, 25, 50, 100, 250, 500, 1000, 2000, Infinity];
|
|
120
|
+
|
|
121
|
+
/** The selectable spin counts, honouring an optional jurisdiction max. With a `maxCount`:
|
|
122
|
+
* drop ∞, keep presets ≤ max, and append the max itself when it isn't already a preset
|
|
123
|
+
* (so the cap is always offered). Without one: the default presets including ∞. */
|
|
124
|
+
function autoplayCounts(maxCount?: number): number[] {
|
|
125
|
+
if (maxCount == null) return AUTOPLAY_COUNTS;
|
|
126
|
+
const capped = AUTOPLAY_COUNTS.filter((n) => Number.isFinite(n) && n <= maxCount);
|
|
127
|
+
if (!capped.includes(maxCount)) capped.push(maxCount);
|
|
128
|
+
return capped;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Autoplay picker — spin counts (incl. ∞ unless a maxCount caps them); Confirm starts autoplay. */
|
|
132
|
+
export function openAutoplayModal(host: ShellHost): { root: HTMLElement; onKey: (e: KeyboardEvent) => boolean } {
|
|
133
|
+
const maxCount = host.config.features.autoplay?.maxCount;
|
|
134
|
+
const counts = autoplayCounts(maxCount);
|
|
135
|
+
return buildSheet({
|
|
136
|
+
ge: 'autoplay-modal', title: host.t('Autoplay'), columns: 3, confirmLabel: host.t('Start'),
|
|
137
|
+
choices: counts.map((n) => ({ id: String(n), label: Number.isFinite(n) ? String(n) : '∞' })),
|
|
138
|
+
selected: String(host.state.autoplay.remaining || counts[0]),
|
|
139
|
+
onClose: () => host.actions.closeOverlay(),
|
|
140
|
+
onConfirm: (id) => {
|
|
141
|
+
let remaining = Number(id); // "Infinity" → Infinity
|
|
142
|
+
if (maxCount != null) remaining = Math.min(remaining, maxCount); // defensive cap
|
|
143
|
+
host.actions.startAutoplay(remaining);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="_Слой_1" data-name="Слой_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 716 826">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 30.5.1, SVG Export Plug-In . SVG Version: 2.1.4 Build 3) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0, .st1, .st2, .st3 {
|
|
7
|
+
font-family: MyriadPro-Regular, 'Myriad Pro';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.st0, .st4, .st5 {
|
|
11
|
+
isolation: isolate;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.st0, .st5 {
|
|
15
|
+
fill: #7d8794;
|
|
16
|
+
font-size: 13px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.st1 {
|
|
20
|
+
letter-spacing: 0em;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.st6 {
|
|
24
|
+
font-family: AppleBraille-Outline6Dot, 'Apple Braille';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.st2 {
|
|
28
|
+
letter-spacing: 0em;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.st4 {
|
|
32
|
+
fill: #f0f3f8;
|
|
33
|
+
font-family: Helvetica-Bold, Helvetica;
|
|
34
|
+
font-size: 22px;
|
|
35
|
+
font-weight: 700;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.st7 {
|
|
39
|
+
stroke-miterlimit: 10;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.st7, .st8, .st9 {
|
|
43
|
+
fill: #e6edf3;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.st7, .st10 {
|
|
47
|
+
stroke: #283039;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.st7, .st9 {
|
|
51
|
+
fill-rule: evenodd;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.st11 {
|
|
55
|
+
fill: none;
|
|
56
|
+
stroke: #e6edf3;
|
|
57
|
+
stroke-linecap: round;
|
|
58
|
+
stroke-linejoin: round;
|
|
59
|
+
stroke-width: 3.67px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.st12 {
|
|
63
|
+
fill: #0d1117;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.st10 {
|
|
67
|
+
fill: #161b22;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
70
|
+
</defs>
|
|
71
|
+
<rect class="st12" width="716" height="826"/>
|
|
72
|
+
<text class="st4" transform="translate(28 58)"><tspan x="0" y="0">Energy8 Shell Icons</tspan></text>
|
|
73
|
+
<text class="st5" transform="translate(28 80)"><tspan class="st3" x="0" y="0">21</tspan><tspan class="st6" x="13.34" y="0"> </tspan><tspan class="st3" x="17.78" y="0">glyphs</tspan><tspan class="st6" x="54" y="0"> </tspan><tspan class="st2" x="58.44" y="0">·</tspan><tspan class="st6" x="61.13" y="0"> </tspan><tspan class="st3" x="65.58" y="0">24×24</tspan><tspan class="st6" x="100" y="0"> </tspan><tspan class="st3" x="104.44" y="0">viewBox</tspan><tspan class="st6" x="150.02" y="0"> </tspan><tspan class="st3" x="154.46" y="0">·</tspan><tspan class="st6" x="157.15" y="0"> </tspan><tspan class="st1" x="161.6" y="0">currentColor</tspan></text>
|
|
74
|
+
<g>
|
|
75
|
+
<rect class="st10" x="48" y="98" width="92" height="92" rx="14" ry="14"/>
|
|
76
|
+
<g>
|
|
77
|
+
<path class="st9" d="M104.49,130.91c-3.25-1.64-10.05-4.6-13.51-5.65l2.57,3.59h0c-9.83.29-15.77,10.03-16.57,18.97l2.9,4.78.35.18,3.41-3.08c-.39-.77-1.03-3.43-1.03-3.43h0c-.76-8.34,6.02-14.44,14.21-13.51h0l4.27,1.37,3.41-2.91v-.33Z"/>
|
|
78
|
+
<path class="st9" d="M83.54,157.53c3.29,1.57,10.15,4.38,13.63,5.36l-2.64-3.54h0c9.82-.5,15.55-10.36,16.16-19.32l-3-4.72-.35-.18-3.34,3.15c.41.77,1.1,3.41,1.1,3.41h0c.94,8.32-5.72,14.57-13.92,13.81h0l-4.3-1.28-3.35,2.99v.33Z"/>
|
|
79
|
+
</g>
|
|
80
|
+
<text class="st0" transform="translate(85.11 214)"><tspan x="0" y="0">spin</tspan></text>
|
|
81
|
+
</g>
|
|
82
|
+
<g>
|
|
83
|
+
<rect class="st10" x="180" y="98" width="92" height="92" rx="14" ry="14"/>
|
|
84
|
+
<g>
|
|
85
|
+
<path class="st9" d="M239.13,125.13h-4.93l-.2-.2h-5.74l-.82.2-1.83,3.08-2.25,4.51-.42.4-3.68,7.19-.4.4-1.65,3.48,8.82.42,4.51-4.51-.4-.42-2.47-.2v-.62l7.59-8.8,3.9-4.93Z"/>
|
|
86
|
+
<path class="st9" d="M240.35,134.78l-1.65,1.23-4.71,4.31-12.3,11.88-10.05,9.44v1.43l2.25-1.85,14.56-13.73.2.4-5.54,11.07-.4,1.23-1.03,2.05h.2l17.43-21.32v-.2l-3.08-.2-.2-.4,2.25-2.47,2.05-2.86Z"/>
|
|
87
|
+
</g>
|
|
88
|
+
<text class="st0" transform="translate(214.89 214)"><tspan x="0" y="0">turbo</tspan></text>
|
|
89
|
+
</g>
|
|
90
|
+
<g>
|
|
91
|
+
<rect class="st10" x="312" y="98" width="92" height="92" rx="14" ry="14"/>
|
|
92
|
+
<path class="st7" d="M357.57,126.96l-8.47,19.49h8.05c-3.81,7.07-8.69,15.89-8.69,15.89h.43s21.9-22.3,21.9-22.3l-10.61.05,10.32-15.89-12.92,2.75Z"/>
|
|
93
|
+
<text class="st0" transform="translate(344.67 214)"><tspan x="0" y="0">turbo1</tspan></text>
|
|
94
|
+
</g>
|
|
95
|
+
<g>
|
|
96
|
+
<rect class="st10" x="444" y="98" width="92" height="92" rx="14" ry="14"/>
|
|
97
|
+
<path class="st9" d="M490.06,126.73c-2.38,6.37-6,13.22-8.47,19.49h8.05c-3.81,7.07-5.79,10.85-8.69,15.89h.43c1.95-1.89,20.58-21.23,22.02-22.21v-.45s-11.85.2-11.85.2h0c1.22-2.04,10.5-13.31,11.44-15.67"/>
|
|
98
|
+
<text class="st0" transform="translate(476.67 214)"><tspan x="0" y="0">turbo2</tspan></text>
|
|
99
|
+
</g>
|
|
100
|
+
<g>
|
|
101
|
+
<rect class="st10" x="576" y="98" width="92" height="92" rx="14" ry="14"/>
|
|
102
|
+
<g>
|
|
103
|
+
<path class="st9" d="M641.07,127.5h-.73l-3.12.92h-.73l-.73.37h-.73l-5.87,1.65-3.67,8.25v.37l-.55,1.28-.73,1.28v.37l-1.1,2.2-.55,1.65-.55,1.1h6.97l.18.37-2.75,4.58-1.28,2.57-.92,1.28-.73,1.28-.55,1.28-.92,1.28-.18.92,18.88-19.07v-.18l-9.9.18-.18-.37,10.27-13.02.18-.55Z"/>
|
|
104
|
+
<path class="st9" d="M620.17,142.72h-2.02l-.18.18-3.67.37-3.3.55-1.1.37-2.75.37-1.47.37v.18l4.58.18.18.18h4.03l.18.18h4.4l1.1-2.38v-.55Z"/>
|
|
105
|
+
<path class="st9" d="M623.28,135.38h-1.28l-.18.18-2.2.18-1.1.37h-.92l-4.4.92-.92.37-2.2.37.18.37h2.57l.18.18h2.57l.18.18,6.23.18,1.28-3.3Z"/>
|
|
106
|
+
<path class="st9" d="M619.98,150.23l-1.1.55h-.55l-1.65-.55-2.38.18-.18.18h-1.47l-.18.18h-1.47l-.18.18-3.3.37-1.1.37-1.83.18-1.65.55h1.83l.18.18h15.03v-2.38Z"/>
|
|
107
|
+
</g>
|
|
108
|
+
<text class="st0" transform="translate(608.67 214)"><tspan x="0" y="0">turbo3</tspan></text>
|
|
109
|
+
</g>
|
|
110
|
+
<g>
|
|
111
|
+
<rect class="st10" x="48.47" y="237.74" width="92" height="92" rx="14" ry="14"/>
|
|
112
|
+
<g>
|
|
113
|
+
<path class="st9" d="M90.09,278.19v11.81h.18c1.5-1.2,7.69-4.77,9.44-5.81v-.18c-1.82-1-7.92-4.92-9.62-5.81Z"/>
|
|
114
|
+
<path class="st9" d="M109.73,277.01l-2.75,2.41v13.44l-3.81,3.1s-23.25,0-23.25,0v.18l7.99,6.89-1.08-4h18.52l4.55-4.71h0l-.17-17.31Z"/>
|
|
115
|
+
<path class="st9" d="M77.75,290.87l2.76-2.39.09-13.44,3.83-3.07s23.25.15,23.25.15v-.18s-7.95-6.95-7.95-6.95l1.06,4h0l-18.52-.12-4.58,4.68h0l.05,17.31Z"/>
|
|
116
|
+
</g>
|
|
117
|
+
<text class="st0" transform="translate(76.23 354)"><tspan x="0" y="0">autoplay</tspan></text>
|
|
118
|
+
</g>
|
|
119
|
+
<g>
|
|
120
|
+
<rect class="st10" x="180" y="238" width="92" height="92" rx="14" ry="14"/>
|
|
121
|
+
<g>
|
|
122
|
+
<path class="st9" d="M242.43,264.93h-32.23l-3.26,3.39v32.1l2.38-3.37v-26.34l3.01-3.01h26.73l3.37-2.77Z"/>
|
|
123
|
+
<path class="st9" d="M209.66,303.01l32.23-.07,3.26-3.4-.07-32.1-2.38,3.38.06,26.34-3,3.01-26.73.06h0l-3.37,2.78Z"/>
|
|
124
|
+
</g>
|
|
125
|
+
<text class="st0" transform="translate(217.11 354)"><tspan x="0" y="0">stop</tspan></text>
|
|
126
|
+
</g>
|
|
127
|
+
<g>
|
|
128
|
+
<rect class="st10" x="312" y="238" width="92" height="92" rx="14" ry="14"/>
|
|
129
|
+
<g>
|
|
130
|
+
<path class="st9" d="M338.93,286.79h33.02l5.11-5.81h-33.02l-5.11,5.81h0Z"/>
|
|
131
|
+
<path class="st9" d="M338.93,273.07h33.02l5.11-5.81h-33.02l-5.11,5.81h0Z"/>
|
|
132
|
+
<path class="st9" d="M339.01,300.53h33.02l5.11-5.81h-33.02l-5.11,5.81h0Z"/>
|
|
133
|
+
</g>
|
|
134
|
+
<text class="st0" transform="translate(349.11 354)"><tspan x="0" y="0">menu</tspan></text>
|
|
135
|
+
</g>
|
|
136
|
+
<g>
|
|
137
|
+
<rect class="st10" x="444" y="238" width="92" height="92" rx="14" ry="14"/>
|
|
138
|
+
<g>
|
|
139
|
+
<path class="st9" d="M506.52,290.29l-3.35-4.33-8.25-11.59-4.33-5.7-.59-.4-6.29,8.27-3.94,5.7-6.47,8.63-.2.59,16.9-14.54h.38l15.53,13.36h.6Z"/>
|
|
140
|
+
<path class="st9" d="M509.07,299.14l-4.33-4.53-14.35-13.77h-.38l-10.03,9.44-.2.4-8.45,8.25-.38.79,19.07-11.99h.38l.99.79,9.83,5.88.99.79,1.58.79,5.1,3.34.2-.18Z"/>
|
|
141
|
+
</g>
|
|
142
|
+
<text class="st0" transform="translate(478.89 354)"><tspan x="0" y="0">betUp</tspan></text>
|
|
143
|
+
</g>
|
|
144
|
+
<g>
|
|
145
|
+
<rect class="st10" x="576" y="238" width="92" height="92" rx="14" ry="14"/>
|
|
146
|
+
<g>
|
|
147
|
+
<path class="st9" d="M602.93,267.02l.44.88,6.84,7.72,11.24,12.12.66.44.66-.44,11.68-12.56,6.62-7.72-.22-.22-6.84,4.86-11.9,9.04-11.46-8.82-7.72-5.3Z"/>
|
|
148
|
+
<path class="st9" d="M638.21,279.14l-5.08,3.98-10.8,9.26h-.44l-13.66-11.68-1.78-1.32-.22.22,9.48,12.78.44.88,2.66,3.3.44.88,1.54,1.78.44.88,1.1.88,11.46-15.44,4.42-6.4Z"/>
|
|
149
|
+
</g>
|
|
150
|
+
<text class="st0" transform="translate(606.45 354)"><tspan x="0" y="0">betDown</tspan></text>
|
|
151
|
+
</g>
|
|
152
|
+
<g>
|
|
153
|
+
<rect class="st10" x="48" y="378" width="92" height="92" rx="14" ry="14"/>
|
|
154
|
+
<path class="st9" d="M108.32,426.31l4.75-4.47-33.24-.15-4.89,4.62"/>
|
|
155
|
+
<text class="st0" transform="translate(82.89 494)"><tspan x="0" y="0">minus</tspan></text>
|
|
156
|
+
</g>
|
|
157
|
+
<g>
|
|
158
|
+
<rect class="st10" x="180" y="378" width="92" height="92" rx="14" ry="14"/>
|
|
159
|
+
<path class="st9" d="M228.13,404.93h0l-4.38,7v9.73l-.15.13h-11.66l-4.66,4.4h16.32l.15.13v16.74l4.38-6.86h0v-9.88l.13-.13h11.66l4.8-4.4h-16.46l-.13-.13v-16.74Z"/>
|
|
160
|
+
<text class="st0" transform="translate(217.11 494)"><tspan x="0" y="0">plus</tspan></text>
|
|
161
|
+
</g>
|
|
162
|
+
<g>
|
|
163
|
+
<rect class="st10" x="312" y="378" width="92" height="92" rx="14" ry="14"/>
|
|
164
|
+
<g>
|
|
165
|
+
<rect class="st8" x="343.33" y="418.5" width="29.33" height="20.17" rx="3.67" ry="3.67"/>
|
|
166
|
+
<path class="st8" d="M352.5,418.5c-2.47-.57-4-3.03-3.43-5.5s3.03-4,5.5-3.43c1.7.4,3.03,1.73,3.43,3.43.57-2.47,3.03-4,5.5-3.43,2.47.57,4,3.03,3.43,5.5-.4,1.7-1.73,3.03-3.43,3.43h-11Z"/>
|
|
167
|
+
<rect x="356.17" y="418.5" width="3.67" height="20.17"/>
|
|
168
|
+
</g>
|
|
169
|
+
<text class="st0" transform="translate(349.11 494)"><tspan x="0" y="0">gift</tspan></text>
|
|
170
|
+
</g>
|
|
171
|
+
<g>
|
|
172
|
+
<rect class="st10" x="444" y="378" width="92" height="92" rx="14" ry="14"/>
|
|
173
|
+
<g>
|
|
174
|
+
<path class="st9" d="M494.07,416.26l-5.66.15s-3.33,2.73-3.59,2.62h1.8l-3.72,23.89h0l10.08-8.01h-3.04"/>
|
|
175
|
+
<path class="st9" d="M497.26,404.93h-5.54c-1.57,1.9-3.77,5.46-5.24,7.46l4.97-.15c1.54-1.74,4.45-5.42,5.81-7.31Z"/>
|
|
176
|
+
</g>
|
|
177
|
+
<text class="st0" transform="translate(481.11 494)"><tspan x="0" y="0">info</tspan></text>
|
|
178
|
+
</g>
|
|
179
|
+
<g>
|
|
180
|
+
<rect class="st10" x="576" y="378" width="92" height="92" rx="14" ry="14"/>
|
|
181
|
+
<g>
|
|
182
|
+
<path class="st9" d="M634.61,420.55l-4.16-6.31,1.01,3.15.44,2.88v7.17l-.44,2.86-1.01,3.15,4.16-6.31"/>
|
|
183
|
+
<path class="st9" d="M638.33,426.44c.15,3.51-1.59,8.04-3.01,11.18,2.05-2.53,4.31-6.67,5.74-9.61,0,0,0-8.45,0-8.45-1.38-3.02-3.66-7.02-5.74-9.61,1.56,3.27,3.09,7.48,3.01,11.18"/>
|
|
184
|
+
<path class="st9" d="M624.14,405.23c-3.12,2.66-9.64,8.84-12.61,11.75l-8.6.15v13.47c.25.26,8.02,1.19,8.6,1.28,3.06,2.79,9.4,8.32,12.61,10.89v-37.55Z"/>
|
|
185
|
+
<path class="st9" d="M621.43,412.25l.42.15v24.51l-.42.15-8.47-7.88-4.58-.59-.27-.27v-7.04l.27-.27,4.58-.15,8.47-8.6Z"/>
|
|
186
|
+
</g>
|
|
187
|
+
<text class="st0" transform="translate(606.45 494)"><tspan x="0" y="0">soundOn</tspan></text>
|
|
188
|
+
</g>
|
|
189
|
+
<g>
|
|
190
|
+
<rect class="st10" x="48" y="518" width="92" height="92" rx="14" ry="14"/>
|
|
191
|
+
<g>
|
|
192
|
+
<path class="st9" d="M97.08,566.16l-2.11,2.24v7.41l-.42.13-3.48-3.21-1.96,1.96,6.43,5.59,1.67,1.25v-15.36h-.13Z"/>
|
|
193
|
+
<path class="st9" d="M104.47,557.93l-.4.4.13,1.81s.41,7.68-.97,10.06l3.21-4.6v-4.75l-1.96-2.93Z"/>
|
|
194
|
+
<path class="st9" d="M107.69,554.15l1.94,6.42v5.32l-2.24,7.11,4.05-6.84v-6.01l-3.76-5.99Z"/>
|
|
195
|
+
<path class="st9" d="M110.33,546.05c-3.01,2.99-9.92,9.93-12.7,13l-.42-.15v-13.97c-3.06,2.63-9.36,8.59-12.28,11.46l-8.52.13.15.15v13.13l7.39.99,2.24-2.53-4.45-.55-.15-.15v-6.98l4.75-.42,8.23-8.38.42.15v10.06c-5.56,5.73-13.22,14.84-18.3,21.08,12-10.58,24.26-24.07,33.66-37.01Z"/>
|
|
196
|
+
</g>
|
|
197
|
+
<text class="st0" transform="translate(76.23 634)"><tspan x="0" y="0">soundOff</tspan></text>
|
|
198
|
+
</g>
|
|
199
|
+
<g>
|
|
200
|
+
<rect class="st10" x="180" y="518" width="92" height="92" rx="14" ry="14"/>
|
|
201
|
+
<path class="st9" d="M234.96,551.66l-8.74,9.2-8.74-8.98-9.42-6.05,6.73,9.42,8.3,8.74-9.26,9.51-6.45,9.55,9.88-6.73,8.74-9.2h.22l8.98,9.2,9.42,6.29-6.42-9.15-9.07-9.26v-.44l8.83-9.32,6.66-9.52"/>
|
|
202
|
+
<text class="st0" transform="translate(214.89 634)"><tspan x="0" y="0">close</tspan></text>
|
|
203
|
+
</g>
|
|
204
|
+
<g>
|
|
205
|
+
<rect class="st10" x="312" y="518" width="92" height="92" rx="14" ry="14"/>
|
|
206
|
+
<path class="st11" d="M363.5,553l-11,11,11,11"/>
|
|
207
|
+
<text class="st0" transform="translate(349.11 634)"><tspan x="0" y="0">back</tspan></text>
|
|
208
|
+
</g>
|
|
209
|
+
<g>
|
|
210
|
+
<rect class="st10" x="444" y="518" width="92" height="92" rx="14" ry="14"/>
|
|
211
|
+
<path class="st11" d="M484.5,553l11,11-11,11"/>
|
|
212
|
+
<text class="st0" transform="translate(463.34 634)"><tspan x="0" y="0">chevronRight</tspan></text>
|
|
213
|
+
</g>
|
|
214
|
+
<g>
|
|
215
|
+
<rect class="st10" x="576" y="518" width="92" height="92" rx="14" ry="14"/>
|
|
216
|
+
<path class="st8" d="M622,547.5l4.77,10.27,11.18,1.28-8.25,7.7,2.2,11-9.9-4.77-9.9,4.77,2.2-11-8.25-7.7,11.18-1.28,4.77-10.27Z"/>
|
|
217
|
+
<text class="st0" transform="translate(613.11 634)"><tspan x="0" y="0">star</tspan></text>
|
|
218
|
+
</g>
|
|
219
|
+
<g>
|
|
220
|
+
<rect class="st10" x="48" y="658" width="92" height="92" rx="14" ry="14"/>
|
|
221
|
+
<path class="st8" d="M98.21,689.19l-11.02,14.88,6.63.14-4.39,12.39,10.67-15.3-6.56-.11,4.67-12Z"/>
|
|
222
|
+
<text class="st0" transform="translate(74 774)"><tspan x="0" y="0">lightning</tspan></text>
|
|
223
|
+
</g>
|
|
224
|
+
</svg>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// AUTO-GENERATED from ./icons-preview.svg by scripts/gen-icons.mjs — do not edit by hand.
|
|
2
|
+
// Sharp monochrome icon set: each glyph is a 24×24 viewBox fragment using currentColor
|
|
3
|
+
// (hollow shapes use fill-rule="evenodd"; the few stroked glyphs use stroke="currentColor").
|
|
4
|
+
// Coordinates are baked into the 24×24 space (no <g transform>), so the same fragment renders
|
|
5
|
+
// identically in the DOM <svg> and in Pixi's GraphicsContext.svg.
|
|
6
|
+
// To change an icon: edit icons-preview.svg, then run `node scripts/gen-icons.mjs`.
|
|
7
|
+
const SVGS: Record<string, string> = {
|
|
8
|
+
spin: `<g fill="currentColor" fill-rule="evenodd"><path d="M17.72 4.86c-1.77 -0.89 -5.48 -2.51 -7.37 -3.08l1.4 1.96h0c-5.36 0.16 -8.6 5.47 -9.04 10.35l1.58 2.61l0.19 0.1l1.86 -1.68c-0.21 -0.42 -0.56 -1.87 -0.56 -1.87h0c-0.41 -4.55 3.28 -7.88 7.75 -7.37h0l2.33 0.75l1.86 -1.59v-0.18Z"/><path d="M6.29 19.38c1.79 0.86 5.54 2.39 7.43 2.92l-1.44 -1.93h0c5.36 -0.27 8.48 -5.65 8.81 -10.54l-1.64 -2.57l-0.19 -0.1l-1.82 1.72c0.22 0.42 0.6 1.86 0.6 1.86h0c0.51 4.54 -3.12 7.95 -7.59 7.53h0l-2.35 -0.7l-1.83 1.63v0.18Z"/></g>`,
|
|
9
|
+
turbo1: `<g fill="currentColor" fill-rule="evenodd"><path d="M11.77 2.71l-4.62 10.63h4.39c-2.08 3.86 -4.74 8.67 -4.74 8.67h0.23s11.95 -12.16 11.95 -12.16l-5.79 0.03l5.63 -8.67l-7.05 1.5Z"/></g>`,
|
|
10
|
+
autoplay: `<g fill="currentColor" fill-rule="evenodd"><path d="M9.61 8.97v6.44h0.1c0.82 -0.65 4.19 -2.6 5.15 -3.17v-0.1c-0.99 -0.55 -4.32 -2.68 -5.25 -3.17Z"/><path d="M20.32 8.33l-1.5 1.31v7.33l-2.08 1.69s-12.68 0 -12.68 0v0.1l4.36 3.76l-0.59 -2.18h10.1l2.48 -2.57h0l-0.09 -9.44Z"/><path d="M2.88 15.89l1.51 -1.3l0.05 -7.33l2.09 -1.67s12.68 0.08 12.68 0.08v-0.1s-4.34 -3.79 -4.34 -3.79l0.58 2.18h0l-10.1 -0.07l-2.5 2.55h0l0.03 9.44Z"/></g>`,
|
|
11
|
+
stop: `<g fill="currentColor" fill-rule="evenodd"><path d="M20.96 1.6h-17.58l-1.78 1.85v17.51l1.3 -1.84v-14.37l1.64 -1.64h14.58l1.84 -1.51Z"/><path d="M3.09 22.37l17.58 -0.04l1.78 -1.85l-0.04 -17.51l-1.3 1.84l0.03 14.37l-1.64 1.64l-14.58 0.03h0l-1.84 1.52Z"/></g>`,
|
|
12
|
+
menu: `<g fill="currentColor" fill-rule="evenodd"><path d="M1.6 13.52h18.01l2.79 -3.17h-18.01l-2.79 3.17h0Z"/><path d="M1.6 6.04h18.01l2.79 -3.17h-18.01l-2.79 3.17h0Z"/><path d="M1.64 21.02h18.01l2.79 -3.17h-18.01l-2.79 3.17h0Z"/></g>`,
|
|
13
|
+
minus: `<g fill="currentColor" fill-rule="evenodd"><path d="M19.81 13.26l2.59 -2.44l-18.13 -0.08l-2.67 2.52"/></g>`,
|
|
14
|
+
plus: `<g fill="currentColor" fill-rule="evenodd"><path d="M13.16 1.6h0l-2.39 3.82v5.31l-0.08 0.07h-6.36l-2.54 2.4h8.9l0.08 0.07v9.13l2.39 -3.74h0v-5.39l0.07 -0.07h6.36l2.62 -2.4h-8.98l-0.07 -0.07v-9.13Z"/></g>`,
|
|
15
|
+
gift: `<rect x="4" y="9" width="16" height="11" rx="2" fill="currentColor"/><path d="M9 9c-1.35 -0.31 -2.18 -1.65 -1.87 -3s1.65 -2.18 3 -1.87c0.93 0.22 1.65 0.94 1.87 1.87c0.31 -1.35 1.65 -2.18 3 -1.87c1.35 0.31 2.18 1.65 1.87 3c-0.22 0.93 -0.94 1.65 -1.87 1.87h-6Z" fill="currentColor"/><rect x="11" y="9" width="2" height="11" fill="rgba(0,0,0,.35)"/>`,
|
|
16
|
+
info: `<g fill="currentColor" fill-rule="evenodd"><path d="M14.22 7.78l-3.09 0.08s-1.82 1.49 -1.96 1.43h0.98l-2.03 13.03h0l5.5 -4.37h-1.66"/><path d="M15.96 1.6h-3.02c-0.86 1.04 -2.06 2.98 -2.86 4.07l2.71 -0.08c0.84 -0.95 2.43 -2.96 3.17 -3.99Z"/></g>`,
|
|
17
|
+
soundOn: `<g fill="currentColor" fill-rule="evenodd"><path d="M18.88 10.12l-2.27 -3.44l0.55 1.72l0.24 1.57v3.91l-0.24 1.56l-0.55 1.72l2.27 -3.44"/><path d="M20.91 13.33c0.08 1.91 -0.87 4.39 -1.64 6.1c1.12 -1.38 2.35 -3.64 3.13 -5.24c0 0 0 -4.61 0 -4.61c-0.75 -1.65 -2 -3.83 -3.13 -5.24c0.85 1.78 1.69 4.08 1.64 6.1"/><path d="M13.17 1.76c-1.7 1.45 -5.26 4.82 -6.88 6.41l-4.69 0.08v7.35c0.14 0.14 4.37 0.65 4.69 0.7c1.67 1.52 5.13 4.54 6.88 5.94v-20.48Z"/><path d="M11.69 5.59l0.23 0.08v13.37l-0.23 0.08l-4.62 -4.3l-2.5 -0.32l-0.15 -0.15v-3.84l0.15 -0.15l2.5 -0.08l4.62 -4.69Z"/></g>`,
|
|
18
|
+
soundOff: `<g fill="currentColor" fill-rule="evenodd"><path d="M13.68 13.18l-1.15 1.22v4.04l-0.23 0.07l-1.9 -1.75l-1.07 1.07l3.51 3.05l0.91 0.68v-8.38h-0.07Z"/><path d="M17.71 8.69l-0.22 0.22l0.07 0.99s0.22 4.19 -0.53 5.49l1.75 -2.51v-2.59l-1.07 -1.6Z"/><path d="M19.47 6.63l1.06 3.5v2.9l-1.22 3.88l2.21 -3.73v-3.28l-2.05 -3.27Z"/><path d="M20.91 2.21c-1.64 1.63 -5.41 5.42 -6.93 7.09l-0.23 -0.08v-7.62c-1.67 1.43 -5.11 4.69 -6.7 6.25l-4.65 0.07l0.08 0.08v7.16l4.03 0.54l1.22 -1.38l-2.43 -0.3l-0.08 -0.08v-3.81l2.59 -0.23l4.49 -4.57l0.23 0.08v5.49c-3.03 3.13 -7.21 8.09 -9.98 11.5c6.55 -5.77 13.23 -13.13 18.36 -20.19Z"/></g>`,
|
|
19
|
+
close: `<g fill="currentColor" fill-rule="evenodd"><path d="M16.89 5.27l-4.77 5.02l-4.77 -4.9l-5.14 -3.3l3.67 5.14l4.53 4.77l-5.05 5.19l-3.52 5.21l5.39 -3.67l4.77 -5.02h0.12l4.9 5.02l5.14 3.43l-3.5 -4.99l-4.95 -5.05v-0.24l4.82 -5.08l3.63 -5.19"/></g>`,
|
|
20
|
+
back: `<path d="M15 6l-6 6l6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
21
|
+
chevronRight: `<path d="M9 6l6 6l-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
22
|
+
ticket: `<g transform="translate(12 12) scale(0.0354) translate(-518.2 -535.2)"><g transform="rotate(-15 512 512)"><path d="M250 392H774L774 462C739 474 720 504 720 536C720 568 739 598 774 610V680H250V610C285 598 304 568 304 536C304 504 285 474 250 462V392Z" fill="none" stroke="currentColor" stroke-width="52" stroke-linejoin="miter" stroke-linecap="square"/><path d="M562.9 408.3L441.5 553.9L506.9 546.3L472.9 664.1L604 504L528.9 514.3Z" fill="currentColor"/></g></g>`,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type IconName = keyof typeof SVGS;
|
|
26
|
+
export const ICON_NAMES = Object.keys(SVGS) as IconName[];
|
|
27
|
+
|
|
28
|
+
/** Inline SVG string for an icon, sized to 1em (scale via font-size/width). */
|
|
29
|
+
export function icon(name: IconName): string {
|
|
30
|
+
return `<svg viewBox="0 0 24 24" width="1em" height="1em" aria-hidden="true">${SVGS[name]}</svg>`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createShell, ShellController } from '@/core';
|
|
2
|
+
import type { ShellConfig } from '@/core/types';
|
|
3
|
+
import { HtmlRenderer } from './HtmlRenderer';
|
|
4
|
+
|
|
5
|
+
export interface HtmlShellConfig extends ShellConfig {
|
|
6
|
+
mount: HTMLElement;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let active: ShellController | null = null;
|
|
10
|
+
|
|
11
|
+
/** Drop-in replacement for the platform-core createGameShell.
|
|
12
|
+
* Singleton: returns the existing shell if already created. */
|
|
13
|
+
export function createGameShell(config: HtmlShellConfig): ShellController {
|
|
14
|
+
if (active) return active;
|
|
15
|
+
active = createShell({ ...config, renderer: new HtmlRenderer({ mount: config.mount }) });
|
|
16
|
+
return active;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Tear down the active shell (no argument — singleton). Resolves immediately when nothing is active. */
|
|
20
|
+
export function removeGameShell(): Promise<void> {
|
|
21
|
+
if (!active) return Promise.resolve();
|
|
22
|
+
const shell = active;
|
|
23
|
+
active = null;
|
|
24
|
+
return shell.destroy();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { HtmlRenderer };
|
|
28
|
+
export { ShellController as GameShell };
|
|
29
|
+
export * from '@/core';
|
|
30
|
+
|
|
31
|
+
// Re-export socialize/createI18n/normalizeLang/Lang to mirror platform-core/src/shell/index.ts.
|
|
32
|
+
// They are already re-exported by `@/core` (which includes i18n), so the above wildcard covers it.
|
|
33
|
+
|
|
34
|
+
// HTML-typed narrowed aliases (node = HTMLElement rather than unknown)
|
|
35
|
+
export type { BonusOption, GameInfoSection } from '@/core/types';
|
|
36
|
+
export type { HtmlShellConfig as ShellConfig };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { prefersReducedMotion } from '@/core/motion';
|
|
2
|
+
|
|
3
|
+
/** Animate el's text from→to via fmt on requestAnimationFrame; jumps to final when motion is
|
|
4
|
+
* reduced/unavailable. Returns a canceler. (Verbatim behavior of platform-core motion.ts countUp.) */
|
|
5
|
+
export function countUp(el: HTMLElement, from: number, to: number, fmt: (n: number) => string, durationMs = 450): () => void {
|
|
6
|
+
const raf = (globalThis as { requestAnimationFrame?: typeof requestAnimationFrame }).requestAnimationFrame;
|
|
7
|
+
const caf = (globalThis as { cancelAnimationFrame?: typeof cancelAnimationFrame }).cancelAnimationFrame;
|
|
8
|
+
if (prefersReducedMotion() || typeof raf !== 'function') {
|
|
9
|
+
el.textContent = fmt(to);
|
|
10
|
+
return () => {};
|
|
11
|
+
}
|
|
12
|
+
let handle = 0;
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
const origin = (globalThis as { performance?: { now(): number } }).performance?.now() ?? 0;
|
|
15
|
+
const tick = (t: number) => {
|
|
16
|
+
if (cancelled) return;
|
|
17
|
+
const start = origin;
|
|
18
|
+
const p = Math.min(1, (t - start) / durationMs);
|
|
19
|
+
const eased = 1 - Math.pow(1 - p, 3); // easeOutCubic
|
|
20
|
+
el.textContent = fmt(from + (to - from) * eased);
|
|
21
|
+
if (p < 1) handle = raf(tick);
|
|
22
|
+
else el.textContent = fmt(to);
|
|
23
|
+
};
|
|
24
|
+
handle = raf(tick);
|
|
25
|
+
return () => {
|
|
26
|
+
cancelled = true;
|
|
27
|
+
if (typeof caf === 'function') caf(handle);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { icon } from './icons';
|
|
2
|
+
|
|
3
|
+
/** Render a (possibly socialised) two-word label across two lines — the BUY BONUS badge.
|
|
4
|
+
* Shared so the bottom-bar button and the Game-info control legend break identically. */
|
|
5
|
+
export function twoLine(label: string): string {
|
|
6
|
+
return label.split(/\s+/).join('<br>');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CardModalOpts {
|
|
10
|
+
ge: string;
|
|
11
|
+
title: string;
|
|
12
|
+
/** Accent for the title heading + accent footer button (defaults to the shell accent). */
|
|
13
|
+
accent?: string;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
/** Render the ✕ in the overlay's top-right corner (default true). */
|
|
16
|
+
closable?: boolean;
|
|
17
|
+
/** Backdrop blur in px; omit to use the stylesheet's default blur. */
|
|
18
|
+
blur?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A centred CARD modal — frosted backdrop + opaque card with an accent title heading and an
|
|
22
|
+
* overlay ✕ in the top-right. The shared chrome for every centred modal (buy-bonus confirm,
|
|
23
|
+
* bet, autoplay, generic openModal). Append content to `body`; append full-bleed footer
|
|
24
|
+
* button(s) directly to `card`. Closes only via the ✕ / footer buttons — the backdrop does NOT. */
|
|
25
|
+
export function createCardModal(
|
|
26
|
+
opts: CardModalOpts,
|
|
27
|
+
): { root: HTMLDivElement; card: HTMLDivElement; body: HTMLDivElement } {
|
|
28
|
+
const root = document.createElement('div');
|
|
29
|
+
root.className = 'ge-sheet'; root.dataset.ge = opts.ge;
|
|
30
|
+
if (opts.blur != null) root.style.setProperty('--ge-sheet-blur', `${opts.blur}px`);
|
|
31
|
+
const card = document.createElement('div'); card.className = 'ge-modal-card';
|
|
32
|
+
if (opts.accent) card.style.setProperty('--card-acc', opts.accent);
|
|
33
|
+
const body = document.createElement('div'); body.className = 'ge-modal-body';
|
|
34
|
+
const h = document.createElement('h4'); h.className = 'ge-modal-title'; h.textContent = opts.title;
|
|
35
|
+
body.appendChild(h);
|
|
36
|
+
card.appendChild(body);
|
|
37
|
+
root.appendChild(card);
|
|
38
|
+
// ✕ lives on the overlay itself (top-right of the screen), not on the card
|
|
39
|
+
if (opts.closable !== false) {
|
|
40
|
+
const close = document.createElement('button');
|
|
41
|
+
close.className = 'ge-modal-close'; close.dataset.ge = 'modal-close';
|
|
42
|
+
close.setAttribute('aria-label', 'Close'); close.innerHTML = icon('close');
|
|
43
|
+
close.addEventListener('click', opts.onClose);
|
|
44
|
+
root.appendChild(close);
|
|
45
|
+
}
|
|
46
|
+
return { root, card, body };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface OverlayOpts {
|
|
50
|
+
title: string;
|
|
51
|
+
onClose: () => void;
|
|
52
|
+
onBack?: () => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Full-screen overlay. Returns { root, body, scroll }; append content to body.
|
|
56
|
+
* The `scroll` element is the scrollable container (overflow-y: auto). */
|
|
57
|
+
export function createOverlay(opts: OverlayOpts): { root: HTMLDivElement; body: HTMLDivElement; scroll: HTMLDivElement } {
|
|
58
|
+
const root = document.createElement('div');
|
|
59
|
+
root.className = 'ge-shell-overlay';
|
|
60
|
+
const head = document.createElement('div');
|
|
61
|
+
head.className = 'ge-ov-head';
|
|
62
|
+
if (opts.onBack) {
|
|
63
|
+
const back = document.createElement('button');
|
|
64
|
+
back.className = 'ge-ov-nav'; back.dataset.ge = 'info-back'; back.innerHTML = icon('back');
|
|
65
|
+
back.addEventListener('click', opts.onBack);
|
|
66
|
+
head.appendChild(back);
|
|
67
|
+
} else {
|
|
68
|
+
// reserve a slot equal to the close button so the title stays centred
|
|
69
|
+
const spacer = document.createElement('div');
|
|
70
|
+
spacer.className = 'ge-ov-spacer';
|
|
71
|
+
head.appendChild(spacer);
|
|
72
|
+
}
|
|
73
|
+
const h = document.createElement('h4'); h.className = 'ge-ov-title'; h.textContent = opts.title; head.appendChild(h);
|
|
74
|
+
const close = document.createElement('button');
|
|
75
|
+
close.className = 'ge-ov-nav'; close.setAttribute('aria-label', 'Close'); close.innerHTML = icon('close');
|
|
76
|
+
close.addEventListener('click', opts.onClose);
|
|
77
|
+
head.appendChild(close);
|
|
78
|
+
// Header stays fixed; only this wrapper scrolls — the X never scrolls away,
|
|
79
|
+
// and vh-clamped padding keeps it usable on small popouts (e.g. 400×225).
|
|
80
|
+
const scroll = document.createElement('div'); scroll.className = 'ge-ov-scroll';
|
|
81
|
+
const body = document.createElement('div'); body.className = 'ge-ov-body';
|
|
82
|
+
scroll.appendChild(body);
|
|
83
|
+
root.append(head, scroll);
|
|
84
|
+
return { root, body, scroll };
|
|
85
|
+
}
|