@energy8platform/platform-core 0.26.1 → 0.28.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/index.cjs.js +0 -3615
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -400
- package/dist/index.esm.js +1 -3613
- package/dist/index.esm.js.map +1 -1
- package/package.json +3 -9
- package/scripts/build-digit-font.mjs +72 -0
- package/src/index.ts +2 -17
- package/dist/shell.cjs.js +0 -3671
- package/dist/shell.cjs.js.map +0 -1
- package/dist/shell.d.ts +0 -433
- package/dist/shell.esm.js +0 -3664
- package/dist/shell.esm.js.map +0 -1
- package/scripts/gen-version.mjs +0 -21
- package/src/shell/GameShell.ts +0 -412
- package/src/shell/INTER-LICENSE.txt +0 -93
- package/src/shell/colors.ts +0 -32
- package/src/shell/components/BottomBar.ts +0 -237
- package/src/shell/components/BuyBonus.ts +0 -329
- package/src/shell/components/GameInfo.ts +0 -384
- package/src/shell/components/Modal.ts +0 -36
- package/src/shell/components/ReplayModal.ts +0 -57
- package/src/shell/components/Settings.ts +0 -59
- package/src/shell/components/icons.ts +0 -40
- package/src/shell/components/pickers.ts +0 -150
- package/src/shell/components/primitives.ts +0 -85
- package/src/shell/fonts.ts +0 -13
- package/src/shell/format.ts +0 -39
- package/src/shell/i18n.ts +0 -96
- package/src/shell/index.ts +0 -22
- package/src/shell/keyboard.ts +0 -229
- package/src/shell/locales.ts +0 -864
- package/src/shell/motion.ts +0 -43
- package/src/shell/shell.css.ts +0 -449
- package/src/shell/state.ts +0 -31
- package/src/shell/theme.ts +0 -54
- package/src/shell/types.ts +0 -262
- package/src/shell/version.ts +0 -3
package/dist/shell.cjs.js
DELETED
|
@@ -1,3671 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Minimal typed event emitter — internal utility for platform-core.
|
|
5
|
-
*
|
|
6
|
-
* Supports `void` event types — events that carry no data can be emitted
|
|
7
|
-
* without arguments: `emitter.emit('eventName')`.
|
|
8
|
-
*
|
|
9
|
-
* Mirrors the EventEmitter shipped from game-engine's core, copied here
|
|
10
|
-
* so platform-core has no upward dependency on game-engine.
|
|
11
|
-
*/
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
13
|
-
class EventEmitter {
|
|
14
|
-
listeners = new Map();
|
|
15
|
-
on(event, handler) {
|
|
16
|
-
if (!this.listeners.has(event)) {
|
|
17
|
-
this.listeners.set(event, new Set());
|
|
18
|
-
}
|
|
19
|
-
this.listeners.get(event).add(handler);
|
|
20
|
-
return this;
|
|
21
|
-
}
|
|
22
|
-
once(event, handler) {
|
|
23
|
-
const wrapper = (data) => {
|
|
24
|
-
this.off(event, wrapper);
|
|
25
|
-
handler(data);
|
|
26
|
-
};
|
|
27
|
-
return this.on(event, wrapper);
|
|
28
|
-
}
|
|
29
|
-
off(event, handler) {
|
|
30
|
-
this.listeners.get(event)?.delete(handler);
|
|
31
|
-
return this;
|
|
32
|
-
}
|
|
33
|
-
emit(...args) {
|
|
34
|
-
const [event, data] = args;
|
|
35
|
-
const handlers = this.listeners.get(event);
|
|
36
|
-
if (handlers) {
|
|
37
|
-
for (const handler of handlers) {
|
|
38
|
-
handler(data);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
removeAllListeners(event) {
|
|
43
|
-
if (event) {
|
|
44
|
-
this.listeners.delete(event);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
this.listeners.clear();
|
|
48
|
-
}
|
|
49
|
-
return this;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Bet key detection: bet-up needs Shift for arrow/equal, NumpadAdd is bare; same logic for down.
|
|
54
|
-
// Exported so overlays with their own bet stepper (Buy bonus) honour the SAME keys as the bar.
|
|
55
|
-
function betDir(e) {
|
|
56
|
-
if (e.code === 'ArrowUp' && e.shiftKey)
|
|
57
|
-
return 1;
|
|
58
|
-
if (e.code === 'Equal' && e.shiftKey)
|
|
59
|
-
return 1;
|
|
60
|
-
if (e.code === 'NumpadAdd')
|
|
61
|
-
return 1;
|
|
62
|
-
if (e.code === 'ArrowDown' && e.shiftKey)
|
|
63
|
-
return -1;
|
|
64
|
-
if (e.code === 'Minus' && e.shiftKey)
|
|
65
|
-
return -1;
|
|
66
|
-
if (e.code === 'NumpadSubtract')
|
|
67
|
-
return -1;
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
class KeyboardController {
|
|
71
|
-
host;
|
|
72
|
-
doc;
|
|
73
|
-
spaceHeld = false;
|
|
74
|
-
holdTimer = null;
|
|
75
|
-
// Bet hold-repeat state
|
|
76
|
-
betHeldCode = null;
|
|
77
|
-
betTimer = null;
|
|
78
|
-
constructor(host, doc) {
|
|
79
|
-
this.host = host;
|
|
80
|
-
this.doc = doc ?? (typeof document !== 'undefined' ? document : null);
|
|
81
|
-
}
|
|
82
|
-
isSpinAllowed() {
|
|
83
|
-
const h = this.host;
|
|
84
|
-
const s = h.state;
|
|
85
|
-
return (h.spacebarEnabled &&
|
|
86
|
-
h.hotkeysEnabled &&
|
|
87
|
-
!h.hasOpenLayer() &&
|
|
88
|
-
s.mode === 'base' &&
|
|
89
|
-
!s.autoplay.active);
|
|
90
|
-
}
|
|
91
|
-
isBetAllowed() {
|
|
92
|
-
const h = this.host;
|
|
93
|
-
const s = h.state;
|
|
94
|
-
return (h.hotkeysEnabled &&
|
|
95
|
-
!h.hasOpenLayer() &&
|
|
96
|
-
s.mode === 'base' &&
|
|
97
|
-
!s.busy);
|
|
98
|
-
}
|
|
99
|
-
clearBetTimer() {
|
|
100
|
-
if (this.betTimer !== null) {
|
|
101
|
-
clearTimeout(this.betTimer);
|
|
102
|
-
this.betTimer = null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
startBetRepeat(dir, elapsed) {
|
|
106
|
-
// elapsed is ms already spent holding; use it to accelerate toward 45ms floor.
|
|
107
|
-
// Start at 90ms, decrease ~1ms per 10ms held after the first repeat, floor at 45ms.
|
|
108
|
-
const interval = Math.max(45, 90 - Math.floor(elapsed / 10));
|
|
109
|
-
this.betTimer = setTimeout(() => {
|
|
110
|
-
this.betTimer = null;
|
|
111
|
-
if (this.betHeldCode !== null && this.isBetAllowed()) {
|
|
112
|
-
this.host.stepBet(dir);
|
|
113
|
-
this.startBetRepeat(dir, elapsed + interval);
|
|
114
|
-
}
|
|
115
|
-
}, interval);
|
|
116
|
-
}
|
|
117
|
-
onKeyDown = (e) => {
|
|
118
|
-
const target = e.target;
|
|
119
|
-
// Editable element guard — never intercept keyboard input
|
|
120
|
-
if (target && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName)))
|
|
121
|
-
return;
|
|
122
|
-
// For Space: claim preventDefault early (before layer/mode/busy bail) so the browser's
|
|
123
|
-
// native "Space activates focused button" can't re-fire a shell control and flicker a modal.
|
|
124
|
-
if (e.code === 'Space' && !e.repeat) {
|
|
125
|
-
if (!this.host.spacebarEnabled || !this.host.hotkeysEnabled)
|
|
126
|
-
return;
|
|
127
|
-
e.preventDefault();
|
|
128
|
-
if (this.host.hasOpenLayer()) {
|
|
129
|
-
this.host.routeToLayer(e);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const s = this.host.state;
|
|
133
|
-
if (s.mode !== 'base' || s.busy || s.autoplay.active)
|
|
134
|
-
return;
|
|
135
|
-
this.spaceHeld = true;
|
|
136
|
-
this.host.spin();
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
// Bet step keys (Shift+arrows, Shift+=/-, NumpadAdd/Subtract) — non-repeat only
|
|
140
|
-
if (!e.repeat) {
|
|
141
|
-
const dir = betDir(e);
|
|
142
|
-
if (dir !== null && this.isBetAllowed()) {
|
|
143
|
-
this.betHeldCode = e.code;
|
|
144
|
-
this.host.stepBet(dir);
|
|
145
|
-
// First repeat after 350ms initial delay
|
|
146
|
-
this.clearBetTimer();
|
|
147
|
-
const capturedDir = dir;
|
|
148
|
-
this.betTimer = setTimeout(() => {
|
|
149
|
-
this.betTimer = null;
|
|
150
|
-
if (this.betHeldCode !== null && this.isBetAllowed()) {
|
|
151
|
-
this.host.stepBet(capturedDir);
|
|
152
|
-
this.startBetRepeat(capturedDir, 350);
|
|
153
|
-
}
|
|
154
|
-
}, 350);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// Non-Space keys: give the open layer first refusal. If it consumes the key, done; Escape closes
|
|
159
|
-
// it. Anything the layer does NOT consume falls through to the chrome hotkeys below — so the
|
|
160
|
-
// Settings/Info pages still honour Shift+I (Game info), Shift+M (sound), Shift+S, etc.
|
|
161
|
-
if (this.host.hasOpenLayer()) {
|
|
162
|
-
const consumed = this.host.routeToLayer(e);
|
|
163
|
-
if (consumed)
|
|
164
|
-
return;
|
|
165
|
-
if (e.code === 'Escape') {
|
|
166
|
-
this.host.closeLayer();
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
// not consumed → fall through to the Shift+letter chrome hotkeys
|
|
170
|
-
}
|
|
171
|
-
// Shift+letter bar hotkeys — fire when no layer is open, OR when an open layer left the key
|
|
172
|
-
// unconsumed (see fall-through above); gated on hotkeys being enabled.
|
|
173
|
-
if (!e.repeat && e.shiftKey && this.host.hotkeysEnabled) {
|
|
174
|
-
const h = this.host;
|
|
175
|
-
const s = h.state;
|
|
176
|
-
switch (e.code) {
|
|
177
|
-
case 'KeyA':
|
|
178
|
-
if (h.autoplayEnabled && !s.replay) {
|
|
179
|
-
h.toggleAutoplay();
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
break;
|
|
183
|
-
case 'KeyT':
|
|
184
|
-
if (h.turboLevels > 0 && !s.replay) {
|
|
185
|
-
h.cycleTurbo();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
break;
|
|
189
|
-
case 'KeyB':
|
|
190
|
-
if (h.buyBonusEnabled && s.mode === 'base' && !s.replay) {
|
|
191
|
-
h.openBuyBonus();
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
break;
|
|
195
|
-
case 'KeyI':
|
|
196
|
-
h.openInfo();
|
|
197
|
-
return;
|
|
198
|
-
case 'KeyS':
|
|
199
|
-
h.openMenu();
|
|
200
|
-
return;
|
|
201
|
-
case 'KeyM':
|
|
202
|
-
h.toggleMute();
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
onKeyUp = (e) => {
|
|
208
|
-
if (e.code === 'Space') {
|
|
209
|
-
this.spaceHeld = false;
|
|
210
|
-
this.clearHoldTimer();
|
|
211
|
-
}
|
|
212
|
-
// Stop bet repeat on key release
|
|
213
|
-
if (e.code === this.betHeldCode) {
|
|
214
|
-
this.betHeldCode = null;
|
|
215
|
-
this.clearBetTimer();
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
onBlur = () => {
|
|
219
|
-
// Window blur — stop bet repeat AND hold-to-spin (same as releasing both keys)
|
|
220
|
-
this.betHeldCode = null;
|
|
221
|
-
this.clearBetTimer();
|
|
222
|
-
this.spaceHeld = false;
|
|
223
|
-
this.clearHoldTimer();
|
|
224
|
-
};
|
|
225
|
-
clearHoldTimer() {
|
|
226
|
-
if (this.holdTimer !== null) {
|
|
227
|
-
clearTimeout(this.holdTimer);
|
|
228
|
-
this.holdTimer = null;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
attach() {
|
|
232
|
-
this.doc.addEventListener('keydown', this.onKeyDown);
|
|
233
|
-
this.doc.addEventListener('keyup', this.onKeyUp);
|
|
234
|
-
// Use window if available for blur events
|
|
235
|
-
if (typeof window !== 'undefined') {
|
|
236
|
-
window.addEventListener('blur', this.onBlur);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
detach() {
|
|
240
|
-
this.doc.removeEventListener('keydown', this.onKeyDown);
|
|
241
|
-
this.doc.removeEventListener('keyup', this.onKeyUp);
|
|
242
|
-
if (typeof window !== 'undefined') {
|
|
243
|
-
window.removeEventListener('blur', this.onBlur);
|
|
244
|
-
}
|
|
245
|
-
this.spaceHeld = false;
|
|
246
|
-
this.clearHoldTimer();
|
|
247
|
-
this.betHeldCode = null;
|
|
248
|
-
this.clearBetTimer();
|
|
249
|
-
}
|
|
250
|
-
notifyBusyChanged(busy) {
|
|
251
|
-
if (busy)
|
|
252
|
-
return;
|
|
253
|
-
if (!this.spaceHeld)
|
|
254
|
-
return;
|
|
255
|
-
if (!this.isSpinAllowed())
|
|
256
|
-
return;
|
|
257
|
-
// Schedule the next spin after the 120 ms floor (gap between completion and next spin).
|
|
258
|
-
this.clearHoldTimer();
|
|
259
|
-
this.holdTimer = setTimeout(() => {
|
|
260
|
-
this.holdTimer = null;
|
|
261
|
-
if (this.spaceHeld && this.isSpinAllowed()) {
|
|
262
|
-
this.host.spin();
|
|
263
|
-
}
|
|
264
|
-
}, 120);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function createInitialState(config) {
|
|
269
|
-
return {
|
|
270
|
-
mode: config.mode,
|
|
271
|
-
replay: config.replay ?? config.mode === 'replay',
|
|
272
|
-
balance: config.balance,
|
|
273
|
-
win: config.win,
|
|
274
|
-
bet: config.currentBet ?? config.defaultBet,
|
|
275
|
-
availableBets: [...config.availableBets],
|
|
276
|
-
busy: false,
|
|
277
|
-
autoplay: { active: false, remaining: 0 },
|
|
278
|
-
turbo: 0,
|
|
279
|
-
buyBonusEnabled: true,
|
|
280
|
-
freeSpins: { current: 0, total: 0, totalWin: 0 },
|
|
281
|
-
activeFeature: null,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
/** Step bet up/down within availableBets, clamped at the ends. */
|
|
285
|
-
function stepBet(state, direction) {
|
|
286
|
-
const idx = state.availableBets.indexOf(state.bet);
|
|
287
|
-
const next = Math.max(0, Math.min(state.availableBets.length - 1, idx + direction));
|
|
288
|
-
return state.availableBets[next];
|
|
289
|
-
}
|
|
290
|
-
/** Cycle turbo level 0..maxLevels (wraps back to 0). */
|
|
291
|
-
function nextTurbo(current, maxLevels) {
|
|
292
|
-
if (maxLevels <= 0)
|
|
293
|
-
return 0;
|
|
294
|
-
return current >= maxLevels ? 0 : current + 1;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** The single brand accent (purple). The bar accent default (theme.ts) and the buy-a-bonus
|
|
298
|
-
* card default both derive from this, so a rebrand only touches one constant. */
|
|
299
|
-
const BRAND_ACCENT = '#8b5cf6';
|
|
300
|
-
/** Type-default accents for bonus options. A per-option `accentColor` overrides these. */
|
|
301
|
-
const ACCENT_BONUS = BRAND_ACCENT; // brand purple — buy a bonus round
|
|
302
|
-
const ACCENT_FEATURE = '#f0b429'; // gold — activate a base-game feature (e.g. Ante)
|
|
303
|
-
/** The accent a card/button/bet-tint uses: explicit override, else the type default. */
|
|
304
|
-
function effectiveAccent(b) {
|
|
305
|
-
return b.accentColor ?? (b.type === 'feature' ? ACCENT_FEATURE : ACCENT_BONUS);
|
|
306
|
-
}
|
|
307
|
-
/** Readable text colour for a solid accent button: dark on light accents, white on dark.
|
|
308
|
-
* Only #rgb / #rrggbb are measured; anything else (named, var(), etc.) → white. */
|
|
309
|
-
function contrastText(accent) {
|
|
310
|
-
const rgb = parseHex(accent);
|
|
311
|
-
if (!rgb)
|
|
312
|
-
return '#ffffff';
|
|
313
|
-
// Relative luminance (sRGB, perceptual-ish). >0.6 → use dark ink.
|
|
314
|
-
const lum = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;
|
|
315
|
-
return lum > 0.6 ? '#1a1205' : '#ffffff';
|
|
316
|
-
}
|
|
317
|
-
function parseHex(hex) {
|
|
318
|
-
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
319
|
-
if (!m)
|
|
320
|
-
return null;
|
|
321
|
-
let h = m[1];
|
|
322
|
-
if (h.length === 3)
|
|
323
|
-
h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
324
|
-
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const DEFAULT_ACCENT = BRAND_ACCENT; // brand purple — BUY BONUS + active states (single source: colors.ts)
|
|
328
|
-
// Neutral palettes for each scheme. Everything the shell paints reads from these
|
|
329
|
-
// tokens, so a single `scheme` flip recolours bar, icons, overlays and toggles.
|
|
330
|
-
const PALETTE = {
|
|
331
|
-
dark: {
|
|
332
|
-
fg: '#f3f5fa', muted: '#9aa3b6', icon: '#c7cedb', iconActive: '#ffffff',
|
|
333
|
-
surface: '#0c111c', hairline: 'rgba(255,255,255,.07)',
|
|
334
|
-
veil: 'rgba(255,255,255,.05)', veilStrong: 'rgba(255,255,255,.1)', track: 'rgba(255,255,255,.16)',
|
|
335
|
-
soft: '#dfe4ee', spin: '#f4f6fb', spinFg: '#141a28',
|
|
336
|
-
},
|
|
337
|
-
light: {
|
|
338
|
-
fg: '#15202e', muted: '#5a6678', icon: '#3c4658', iconActive: '#0b1220',
|
|
339
|
-
surface: '#eef1f7', hairline: 'rgba(15,23,42,.12)',
|
|
340
|
-
veil: 'rgba(15,23,42,.05)', veilStrong: 'rgba(15,23,42,.09)', track: 'rgba(15,23,42,.22)',
|
|
341
|
-
soft: '#3a4453', spin: '#1c2434', spinFg: '#f3f6fb',
|
|
342
|
-
},
|
|
343
|
-
};
|
|
344
|
-
/** CSS custom-property block for the shell root. `scheme` picks the palette;
|
|
345
|
-
* only the accent is additionally game-overridable. */
|
|
346
|
-
function buildThemeVars(theme = {}) {
|
|
347
|
-
const p = PALETTE[theme.scheme === 'light' ? 'light' : 'dark'];
|
|
348
|
-
return [
|
|
349
|
-
`--shell-fg: ${p.fg}`,
|
|
350
|
-
`--shell-muted: ${p.muted}`,
|
|
351
|
-
`--shell-icon: ${p.icon}`,
|
|
352
|
-
`--shell-icon-active: ${p.iconActive}`,
|
|
353
|
-
`--shell-surface: ${p.surface}`,
|
|
354
|
-
`--shell-hairline: ${p.hairline}`,
|
|
355
|
-
`--shell-veil: ${p.veil}`,
|
|
356
|
-
`--shell-veil-strong: ${p.veilStrong}`,
|
|
357
|
-
`--shell-track: ${p.track}`,
|
|
358
|
-
`--shell-soft: ${p.soft}`,
|
|
359
|
-
`--shell-spin: ${p.spin}`,
|
|
360
|
-
`--shell-spin-fg: ${p.spinFg}`,
|
|
361
|
-
`--shell-radius: 12px`,
|
|
362
|
-
// Plaque tokens — the grouped dark/glass panel language shared by the control bar
|
|
363
|
-
// AND the overlays. Scheme-independent (always dark, white-on-dark) so bar + overlays
|
|
364
|
-
// stay visually identical regardless of the dark/light `scheme`.
|
|
365
|
-
`--shell-plaque-dark: rgba(6,9,15,.86)`,
|
|
366
|
-
`--shell-plaque-glass: rgba(30,36,48,.70)`,
|
|
367
|
-
`--shell-plaque-glass-hover: rgba(40,48,64,.86)`,
|
|
368
|
-
// Opaque surface for centred modals (confirm, bet/autoplay pickers) so they read solid,
|
|
369
|
-
// not see-through, over the frosted backdrop.
|
|
370
|
-
`--shell-plaque-solid: #1a2030`,
|
|
371
|
-
`--shell-plaque-line: rgba(255,255,255,.22)`,
|
|
372
|
-
`--shell-plaque-label: rgba(255,255,255,.6)`,
|
|
373
|
-
`--shell-accent: ${theme.accent ?? DEFAULT_ACCENT}`,
|
|
374
|
-
].join('; ') + ';';
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/* eslint-disable */
|
|
378
|
-
// Inter (variable, weight 100–900) embedded as base64 woff2 — self-contained so the
|
|
379
|
-
// shell renders identically on every platform with no external asset or network fetch.
|
|
380
|
-
// Subsets: latin (covers EN/ES/FR incl. all accents + œ/Œ) + cyrillic (covers RU/UK).
|
|
381
|
-
// Inter is licensed under the SIL Open Font License 1.1 — see ./INTER-LICENSE.txt
|
|
382
|
-
// (https://github.com/rsms/inter).
|
|
383
|
-
// GENERATED by scripts/build-shell-font.mjs — regenerate, don't hand-edit.
|
|
384
|
-
const SHELL_FONT_CSS = `@font-face{font-family:'Inter';font-style:normal;font-display:swap;font-weight:100 900;src:url(data:font/woff2;base64,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) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}` +
|
|
385
|
-
`@font-face{font-family:'Inter';font-style:normal;font-display:swap;font-weight:100 900;src:url(data:font/woff2;base64,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) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;}`;
|
|
386
|
-
|
|
387
|
-
const SHELL_ROOT_ID = '__ge-game-shell__';
|
|
388
|
-
// Inter (bundled, base64) leads the stack so the shell renders identically on every
|
|
389
|
-
// platform; the system fonts stay as graceful fallback if the webfont ever fails to load.
|
|
390
|
-
const SHELL_CSS = SHELL_FONT_CSS + `
|
|
391
|
-
#${SHELL_ROOT_ID} {
|
|
392
|
-
position: absolute; inset: 0;
|
|
393
|
-
container-type: size; /* query container → centred modals size in cq units (responsive on every screen) */
|
|
394
|
-
pointer-events: none; z-index: 9000;
|
|
395
|
-
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
396
|
-
color: var(--shell-fg);
|
|
397
|
-
}
|
|
398
|
-
#${SHELL_ROOT_ID} svg { display:block; }
|
|
399
|
-
|
|
400
|
-
/* floating readouts (no panel) */
|
|
401
|
-
#${SHELL_ROOT_ID} .ge-rd { font-weight:700; font-size:13px; line-height:1; white-space:nowrap;
|
|
402
|
-
text-shadow:0 1px 3px rgba(0,0,0,.65); font-variant-numeric:tabular-nums; }
|
|
403
|
-
#${SHELL_ROOT_ID} .ge-rd .ge-lbl { display:block; color:var(--shell-muted); font-weight:600;
|
|
404
|
-
font-size:9px; letter-spacing:.1em; text-transform:uppercase; margin-bottom:4px; text-shadow:none; }
|
|
405
|
-
|
|
406
|
-
/* icon buttons (borderless) */
|
|
407
|
-
#${SHELL_ROOT_ID} .ge-iconbtn { pointer-events:auto; cursor:pointer; border:none; background:none;
|
|
408
|
-
padding:0; color:var(--shell-icon); width:40px; height:40px; display:flex; align-items:center;
|
|
409
|
-
justify-content:center; font-size:24px; position:relative; transition:transform .08s ease; }
|
|
410
|
-
#${SHELL_ROOT_ID} .ge-iconbtn:active { transform:scale(.92); }
|
|
411
|
-
#${SHELL_ROOT_ID} .ge-iconbtn[disabled] { opacity:.35; cursor:default; }
|
|
412
|
-
#${SHELL_ROOT_ID} .ge-iconbtn.ge-active { color:var(--shell-icon-active); }
|
|
413
|
-
|
|
414
|
-
/* SPIN — white disc by default, big glyph reaching for the rim; lights up accent on hover */
|
|
415
|
-
#${SHELL_ROOT_ID} .ge-shell-spin { pointer-events:auto; cursor:pointer; border:3px solid #000; border-radius:50%;
|
|
416
|
-
width:86px; height:86px; background:var(--shell-spin); color:var(--shell-spin-fg); box-sizing:border-box; font-size:68px;
|
|
417
|
-
display:flex; align-items:center; justify-content:center;
|
|
418
|
-
transition:transform .08s ease, box-shadow .12s ease, background .12s ease, color .12s ease; }
|
|
419
|
-
#${SHELL_ROOT_ID} .ge-shell-spin:hover { background:var(--shell-accent); color:#fff;
|
|
420
|
-
box-shadow:0 0 0 3px var(--shell-accent), 0 0 18px 2px var(--shell-accent); }
|
|
421
|
-
#${SHELL_ROOT_ID} .ge-shell-spin:active { transform:scale(.94); }
|
|
422
|
-
/* disabled: dim via filter (stays opaque) so the plaque seam doesn't show through the disc */
|
|
423
|
-
#${SHELL_ROOT_ID} .ge-shell-spin[disabled] { filter:grayscale(.4) brightness(.62); box-shadow:none; cursor:default; }
|
|
424
|
-
/* spinning while a spin is in progress */
|
|
425
|
-
#${SHELL_ROOT_ID} .ge-shell-spin.ge-spinning svg { transform-origin:50% 50%; animation:ge-spin-rot .8s linear infinite; }
|
|
426
|
-
@keyframes ge-spin-rot { to { transform:rotate(360deg); } }
|
|
427
|
-
/* autoplay running: STOP glyph + countdown stacked in the disc */
|
|
428
|
-
#${SHELL_ROOT_ID} .ge-shell-spin.ge-stop { position:relative; }
|
|
429
|
-
#${SHELL_ROOT_ID} .ge-spin-stop { display:flex; } /* STOP glyph at the disc's full size, like SPIN */
|
|
430
|
-
/* no entrance animation: the bar re-renders many times per spin, so any animation here
|
|
431
|
-
would replay each time and make the counter visibly jump. tabular-nums keeps it steady. */
|
|
432
|
-
#${SHELL_ROOT_ID} .ge-spin-count { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
|
433
|
-
font-size:22px; font-weight:800; line-height:1; font-variant-numeric:tabular-nums; }
|
|
434
|
-
|
|
435
|
-
/* BUY BONUS — round accent badge, 2-line label, text pulses + accent glow on hover */
|
|
436
|
-
#${SHELL_ROOT_ID} .ge-shell-buybonus { pointer-events:auto; cursor:pointer; box-sizing:border-box;
|
|
437
|
-
width:80px; height:80px; border-radius:50%; border:3px solid #000; background:var(--shell-accent);
|
|
438
|
-
color:#fff; font-weight:800; letter-spacing:.02em; font-size:13px; line-height:1.08; text-align:center;
|
|
439
|
-
display:flex; align-items:center; justify-content:center; transition:transform .08s ease, box-shadow .12s ease; }
|
|
440
|
-
#${SHELL_ROOT_ID} .ge-shell-buybonus span { display:inline-block; will-change:transform; }
|
|
441
|
-
#${SHELL_ROOT_ID} .ge-shell-buybonus:hover { box-shadow:0 0 0 3px var(--shell-accent), 0 0 16px 1px var(--shell-accent); }
|
|
442
|
-
#${SHELL_ROOT_ID} .ge-shell-buybonus:hover span { animation:ge-bb-pulse .7s ease-in-out infinite; }
|
|
443
|
-
#${SHELL_ROOT_ID} .ge-shell-buybonus:active { transform:scale(.96); }
|
|
444
|
-
#${SHELL_ROOT_ID} .ge-shell-buybonus[disabled] { filter:grayscale(.5) brightness(.72); box-shadow:none; cursor:default; }
|
|
445
|
-
#${SHELL_ROOT_ID} .ge-shell-buybonus[disabled] span { animation:none; }
|
|
446
|
-
@keyframes ge-bb-pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.16)} }
|
|
447
|
-
|
|
448
|
-
/* host = bottom-anchored flex column: [win pill (on overflow)] above [the bar] */
|
|
449
|
-
#${SHELL_ROOT_ID} .ge-shell-barhost { position:absolute; left:0; right:0; bottom:0; pointer-events:none;
|
|
450
|
-
display:flex; flex-direction:column; align-items:center; justify-content:flex-end; gap:4px;
|
|
451
|
-
transform-origin:bottom center; }
|
|
452
|
-
/* bottom bar: transparent, two zones (wide default) */
|
|
453
|
-
#${SHELL_ROOT_ID} .ge-shell-bottom { width:100%; box-sizing:border-box; pointer-events:none;
|
|
454
|
-
display:flex; align-items:center; justify-content:space-between; padding:0 18px 6px; gap:14px; }
|
|
455
|
-
#${SHELL_ROOT_ID} .ge-zone { display:flex; align-items:center; gap:14px; pointer-events:none; }
|
|
456
|
-
#${SHELL_ROOT_ID} .ge-zone > * { pointer-events:auto; }
|
|
457
|
-
#${SHELL_ROOT_ID} .ge-betstep { display:flex; flex-direction:column; gap:2px; }
|
|
458
|
-
/* auto stacked over turbo (far-right column, base/desktop) */
|
|
459
|
-
#${SHELL_ROOT_ID} .ge-autoturbo { display:flex; flex-direction:column; gap:2px; }
|
|
460
|
-
#${SHELL_ROOT_ID} .ge-autoturbo .ge-iconbtn { width:40px; height:30px; }
|
|
461
|
-
|
|
462
|
-
/* mobile (portrait) — full-width stacked plaques: [balance · win] · [controls] · [− bet +] */
|
|
463
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-shell-bottom { flex-direction:column; align-items:center; gap:14px; padding:8px 12px 8px; }
|
|
464
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-top { width:100%; height:46px; justify-content:space-between; }
|
|
465
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-controls { display:flex; align-items:center; justify-content:space-between;
|
|
466
|
-
width:100%; box-sizing:border-box; height:62px; border-radius:18px; padding:0 18px; }
|
|
467
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-controls .ge-iconbtn,
|
|
468
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-controls .ge-rd,
|
|
469
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-controls .ge-rd .ge-lbl { color:#fff; }
|
|
470
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-controls .ge-rd { text-shadow:none; text-align:center; }
|
|
471
|
-
/* mobile: restore accent hover + autoplay glow (the white rule above out-specifies the base ones) */
|
|
472
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-controls .ge-iconbtn:hover,
|
|
473
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-controls .ge-iconbtn.ge-glow { color:var(--shell-accent); }
|
|
474
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-m-bet { width:100%; height:46px; padding:0 18px; gap:8px; justify-content:space-between; }
|
|
475
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-shell-spin { width:84px; height:84px; font-size:66px; }
|
|
476
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-shell-buybonus { width:50px; height:50px; font-size:9px; border-width:2px; }
|
|
477
|
-
/* landscape overflow — size the column to content, centre it; JS scales the whole stack to fit */
|
|
478
|
-
#${SHELL_ROOT_ID} .ge-shell-barhost.ge-fit { left:50%; right:auto; width:max-content; max-width:none; }
|
|
479
|
-
#${SHELL_ROOT_ID} .ge-shell-barhost.ge-fit .ge-shell-bottom { width:max-content; }
|
|
480
|
-
|
|
481
|
-
/* full-screen overlays — header (title left, prominent X/back), scrolling body, vh-clamped for popout/mobile.
|
|
482
|
-
Frosted backdrop: the game shows through, blurred, behind a light dark tint (white-on-blur).
|
|
483
|
-
Same glass "plaque" language as the control bar; scheme-independent. */
|
|
484
|
-
#${SHELL_ROOT_ID} .ge-shell-overlay { position:absolute; inset:0; z-index:50; pointer-events:auto;
|
|
485
|
-
background:rgba(12,17,28,.5); backdrop-filter:blur(20px) saturate(120%);
|
|
486
|
-
-webkit-backdrop-filter:blur(20px) saturate(120%);
|
|
487
|
-
display:flex; flex-direction:column; animation:ge-ov-in .16s ease-out; }
|
|
488
|
-
/* SPIN/BUY BONUS use z-index:3 to overlap their plaques; lift the overlay clear above them */
|
|
489
|
-
@keyframes ge-ov-in { from { opacity:0; } to { opacity:1; } }
|
|
490
|
-
#${SHELL_ROOT_ID} .ge-ov-head { flex:0 0 auto; display:flex; align-items:center; gap:8px; padding:6px 10px; }
|
|
491
|
-
#${SHELL_ROOT_ID} .ge-ov-title { flex:1; margin:0; text-align:center; color:#fff; font-weight:800; letter-spacing:.04em;
|
|
492
|
-
text-transform:uppercase; font-size:clamp(13px,2.6vh,16px); }
|
|
493
|
-
#${SHELL_ROOT_ID} .ge-ov-spacer { flex:0 0 auto; width:32px; }
|
|
494
|
-
#${SHELL_ROOT_ID} .ge-ov-nav { flex:0 0 auto; display:flex; align-items:center; justify-content:center;
|
|
495
|
-
width:32px; height:32px; border-radius:9px; cursor:pointer; color:#fff;
|
|
496
|
-
background:var(--shell-plaque-dark); border:none; font-size:18px;
|
|
497
|
-
transition:background .12s ease, color .12s ease; }
|
|
498
|
-
#${SHELL_ROOT_ID} .ge-ov-nav:hover { background:var(--shell-plaque-glass); color:var(--shell-accent); }
|
|
499
|
-
#${SHELL_ROOT_ID} .ge-ov-scroll { flex:1 1 auto; min-height:0; overflow-y:auto; overflow-x:hidden; }
|
|
500
|
-
#${SHELL_ROOT_ID} .ge-ov-body { max-width:800px; margin:0 auto; box-sizing:border-box;
|
|
501
|
-
padding:clamp(6px,2vh,16px) clamp(16px,4vw,24px) clamp(16px,4vh,28px); }
|
|
502
|
-
|
|
503
|
-
/* full-width overlay rows — glass plaque, white-on-dark */
|
|
504
|
-
#${SHELL_ROOT_ID} .ge-ov-row { display:flex; align-items:center; gap:12px; width:100%; box-sizing:border-box;
|
|
505
|
-
padding:clamp(11px,2.2vh,15px) 16px; margin-bottom:10px; border:none;
|
|
506
|
-
border-radius:16px; background:var(--shell-plaque-glass); color:#fff; font-size:14px; font-weight:600; }
|
|
507
|
-
#${SHELL_ROOT_ID} .ge-ov-row .ge-grow { flex:1; text-align:left; }
|
|
508
|
-
#${SHELL_ROOT_ID} button.ge-ov-row { cursor:pointer; font-family:inherit; transition:background .12s ease, color .12s ease; }
|
|
509
|
-
#${SHELL_ROOT_ID} button.ge-ov-row:hover { background:var(--shell-plaque-glass-hover); color:var(--shell-accent); }
|
|
510
|
-
#${SHELL_ROOT_ID} .ge-ov-row.ge-col { flex-direction:column; align-items:stretch; gap:10px; }
|
|
511
|
-
#${SHELL_ROOT_ID} .ge-ov-row .ge-row-head { display:flex; justify-content:space-between; align-items:center; }
|
|
512
|
-
#${SHELL_ROOT_ID} .ge-ov-row .ge-row-head .ge-val { color:var(--shell-plaque-label); font-variant-numeric:tabular-nums; font-weight:700; }
|
|
513
|
-
#${SHELL_ROOT_ID} .ge-slider { width:100%; accent-color:var(--shell-accent); }
|
|
514
|
-
#${SHELL_ROOT_ID} .ge-toggle { flex:0 0 auto; width:42px; height:24px; border-radius:999px; background:var(--shell-plaque-line);
|
|
515
|
-
border:none; cursor:pointer; position:relative; }
|
|
516
|
-
#${SHELL_ROOT_ID} .ge-toggle.ge-on { background:var(--shell-accent); }
|
|
517
|
-
#${SHELL_ROOT_ID} .ge-toggle i { position:absolute; top:2px; left:2px; width:20px; height:20px; border-radius:50%;
|
|
518
|
-
background:#fff; transition:left .12s ease; }
|
|
519
|
-
#${SHELL_ROOT_ID} .ge-toggle.ge-on i { left:20px; }
|
|
520
|
-
/* sound on/off — speaker icon button (replaces the toggle) */
|
|
521
|
-
#${SHELL_ROOT_ID} .ge-snd { pointer-events:auto; cursor:pointer; border:none; background:none; padding:0;
|
|
522
|
-
width:36px; height:36px; display:flex; align-items:center; justify-content:center; font-size:24px;
|
|
523
|
-
color:#fff; transition:color .12s ease, transform .08s ease; }
|
|
524
|
-
#${SHELL_ROOT_ID} .ge-snd:not(.ge-active) { color:var(--shell-plaque-label); }
|
|
525
|
-
#${SHELL_ROOT_ID} .ge-snd:hover { color:var(--shell-accent); }
|
|
526
|
-
#${SHELL_ROOT_ID} .ge-snd:active { transform:scale(.92); }
|
|
527
|
-
|
|
528
|
-
/* game info — each section is its own glass plaque; body text sized for comfortable reading */
|
|
529
|
-
#${SHELL_ROOT_ID} .ge-gi-sec { margin-bottom:12px; background:var(--shell-plaque-glass);
|
|
530
|
-
border-radius:16px; padding:16px 18px; }
|
|
531
|
-
#${SHELL_ROOT_ID} .ge-gi-sec h3 { color:var(--shell-plaque-label); font-size:11px; letter-spacing:.14em;
|
|
532
|
-
text-transform:uppercase; margin:0 0 12px; }
|
|
533
|
-
#${SHELL_ROOT_ID} .ge-gi-sec p { color:rgba(255,255,255,.88); font-size:15px; line-height:1.6; margin:0; }
|
|
534
|
-
#${SHELL_ROOT_ID} .ge-gi-version { text-align:center; color:var(--shell-muted); font-size:11px;
|
|
535
|
-
letter-spacing:.08em; opacity:.7; margin:4px 0 2px; }
|
|
536
|
-
|
|
537
|
-
/* hotkeys — keycap chips → localized action label, mirrors controls row layout */
|
|
538
|
-
#${SHELL_ROOT_ID} .ge-gi-hk-block { display:flex; flex-direction:column; }
|
|
539
|
-
#${SHELL_ROOT_ID} .ge-gi-hk { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:9px 0; }
|
|
540
|
-
#${SHELL_ROOT_ID} .ge-gi-hk + .ge-gi-hk { border-top:1px solid var(--shell-plaque-line); }
|
|
541
|
-
#${SHELL_ROOT_ID} .ge-gi-hk-chips { display:flex; align-items:center; flex-wrap:wrap; gap:4px; flex:0 0 auto; }
|
|
542
|
-
#${SHELL_ROOT_ID} .ge-gi-hk-combo { display:inline-flex; align-items:center; gap:4px; }
|
|
543
|
-
#${SHELL_ROOT_ID} .ge-gi-hk-chip { display:inline-flex; align-items:center; justify-content:center;
|
|
544
|
-
padding:2px 7px; border-radius:6px; border:1px solid var(--shell-plaque-line);
|
|
545
|
-
background:var(--shell-plaque-dark); color:#fff;
|
|
546
|
-
font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace; font-size:12px;
|
|
547
|
-
font-weight:600; line-height:1.5; white-space:nowrap; min-width:1.6em; text-align:center; }
|
|
548
|
-
#${SHELL_ROOT_ID} .ge-gi-hk-sep { color:var(--shell-plaque-label); font-size:11px; padding:0 1px; }
|
|
549
|
-
#${SHELL_ROOT_ID} .ge-gi-hk-sep2 { color:var(--shell-plaque-label); font-size:11px; padding:0 4px; }
|
|
550
|
-
#${SHELL_ROOT_ID} .ge-gi-hk-tx { color:rgba(255,255,255,.88); font-size:14px; font-weight:600; text-align:right; flex:1; }
|
|
551
|
-
|
|
552
|
-
/* controls — two blocks (gameplay / menu & info), icon/name/description per control */
|
|
553
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl-block + .ge-gi-ctl-block { margin-top:16px; padding-top:4px; border-top:1px solid var(--shell-plaque-line); }
|
|
554
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl-block-h { color:var(--shell-plaque-label); font-size:11px; letter-spacing:.12em;
|
|
555
|
-
text-transform:uppercase; margin:8px 0 2px; font-weight:700; }
|
|
556
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl { display:flex; align-items:center; gap:14px; padding:9px 0; }
|
|
557
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl + .ge-gi-ctl { border-top:1px solid var(--shell-plaque-line); }
|
|
558
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl-ic { flex:0 0 auto; width:48px; height:48px; display:flex; align-items:center;
|
|
559
|
-
justify-content:center; font-size:26px; color:#fff; }
|
|
560
|
-
/* bet shows both chevrons, stacked & smaller */
|
|
561
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl-ic--bet { flex-direction:column; font-size:18px; gap:0; }
|
|
562
|
-
/* buy bonus draws the real control-bar badge, scaled down & non-interactive */
|
|
563
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl-ic .ge-shell-buybonus { width:46px; height:46px; font-size:8px; border-width:2px; pointer-events:none; }
|
|
564
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl-tx { display:flex; flex-direction:column; gap:2px; }
|
|
565
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl-tx b { color:#fff; font-size:15px; font-weight:700; }
|
|
566
|
-
#${SHELL_ROOT_ID} .ge-gi-ctl-tx span { color:rgba(255,255,255,.7); font-size:13px; line-height:1.4; }
|
|
567
|
-
|
|
568
|
-
/* paytable — cards: symbol image on top, name, then win tiers "<count> x<mult>" */
|
|
569
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(120px,1fr)); gap:10px; }
|
|
570
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-card { display:flex; flex-direction:column; align-items:center; gap:8px;
|
|
571
|
-
background:var(--shell-plaque-dark); border-radius:14px; padding:14px 12px; }
|
|
572
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-sym { display:flex; flex-direction:column; align-items:center; gap:5px; min-width:0; }
|
|
573
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-sym img { width:56px; height:56px; object-fit:contain; }
|
|
574
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-sym span { color:#fff; font-size:13px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; }
|
|
575
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-wins { display:flex; flex-direction:column; align-items:stretch; gap:2px; width:100%; }
|
|
576
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-win { display:flex; justify-content:space-between; align-items:baseline; gap:14px;
|
|
577
|
-
font-size:13.5px; font-variant-numeric:tabular-nums; white-space:nowrap; }
|
|
578
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-win i { color:var(--shell-plaque-label); font-style:normal; }
|
|
579
|
-
#${SHELL_ROOT_ID} .ge-gi-pt-win b { color:var(--shell-accent); font-weight:700; }
|
|
580
|
-
|
|
581
|
-
/* wins — accent-filled mini-grid(s); classic = one per line, cluster/anywhere = one, ways = two */
|
|
582
|
-
#${SHELL_ROOT_ID} .ge-gi-pl-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(72px,1fr)); gap:12px; }
|
|
583
|
-
#${SHELL_ROOT_ID} .ge-gi-pl-item { display:flex; flex-direction:column; align-items:center; gap:6px; }
|
|
584
|
-
#${SHELL_ROOT_ID} .ge-gi-pl-svg { width:100%; height:auto; max-width:140px; }
|
|
585
|
-
#${SHELL_ROOT_ID} .ge-gi-pl-cell { fill:var(--shell-plaque-line); }
|
|
586
|
-
#${SHELL_ROOT_ID} .ge-gi-pl-on { fill:var(--shell-accent); }
|
|
587
|
-
#${SHELL_ROOT_ID} .ge-gi-pl-cap { color:#fff; font-size:13px; font-weight:700; }
|
|
588
|
-
#${SHELL_ROOT_ID} .ge-gi-win-row { display:flex; align-items:flex-start; gap:16px; flex-wrap:wrap; }
|
|
589
|
-
#${SHELL_ROOT_ID} .ge-gi-win-row .ge-gi-pl-svg { flex:0 0 auto; width:140px; }
|
|
590
|
-
#${SHELL_ROOT_ID} .ge-gi-win-desc { flex:1; min-width:160px; color:rgba(255,255,255,.78); font-size:14px; line-height:1.5; margin:0; }
|
|
591
|
-
#${SHELL_ROOT_ID} .ge-gi-win-two { display:flex; gap:22px; flex-wrap:wrap; }
|
|
592
|
-
#${SHELL_ROOT_ID} .ge-gi-win-col { display:flex; flex-direction:column; align-items:center; gap:6px; }
|
|
593
|
-
#${SHELL_ROOT_ID} .ge-gi-win-col .ge-gi-pl-svg { width:140px; }
|
|
594
|
-
#${SHELL_ROOT_ID} .ge-gi-win-tag { font-size:12px; font-weight:700; }
|
|
595
|
-
#${SHELL_ROOT_ID} .ge-gi-win-ok { color:#4ade80; }
|
|
596
|
-
#${SHELL_ROOT_ID} .ge-gi-win-no { color:#f87171; }
|
|
597
|
-
#${SHELL_ROOT_ID} .ge-gi-win-badge { display:inline-block; margin-left:10px; padding:2px 8px; border-radius:999px;
|
|
598
|
-
font-size:11px; font-weight:700; letter-spacing:.02em; color:var(--shell-accent);
|
|
599
|
-
background:var(--shell-plaque-dark); vertical-align:middle; }
|
|
600
|
-
|
|
601
|
-
/* modes — comparison cards */
|
|
602
|
-
#${SHELL_ROOT_ID} .ge-gi-modes { display:flex; flex-direction:column; }
|
|
603
|
-
#${SHELL_ROOT_ID} .ge-gi-mode { padding:12px 0; }
|
|
604
|
-
#${SHELL_ROOT_ID} .ge-gi-mode + .ge-gi-mode { border-top:1px solid var(--shell-plaque-line); }
|
|
605
|
-
#${SHELL_ROOT_ID} .ge-gi-mode-top { display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:6px 16px; margin-bottom:6px; }
|
|
606
|
-
#${SHELL_ROOT_ID} .ge-gi-mode-h { color:#fff; font-size:16px; font-weight:800; }
|
|
607
|
-
#${SHELL_ROOT_ID} .ge-gi-mode-stats { display:flex; flex-wrap:wrap; gap:14px; }
|
|
608
|
-
#${SHELL_ROOT_ID} .ge-gi-mode-st { display:flex; align-items:baseline; gap:5px; }
|
|
609
|
-
#${SHELL_ROOT_ID} .ge-gi-mode-st span { color:var(--shell-plaque-label); font-size:10px; letter-spacing:.1em; text-transform:uppercase; }
|
|
610
|
-
#${SHELL_ROOT_ID} .ge-gi-mode-st b { color:#fff; font-size:14px; font-weight:800; font-variant-numeric:tabular-nums; }
|
|
611
|
-
#${SHELL_ROOT_ID} .ge-gi-mode-desc { color:rgba(255,255,255,.78); font-size:14px; line-height:1.5; margin:0; }
|
|
612
|
-
/* shapes — row list (grid illustration left, name + description right), modes-style text */
|
|
613
|
-
#${SHELL_ROOT_ID} .ge-gi-shapes { display:flex; flex-direction:column; }
|
|
614
|
-
#${SHELL_ROOT_ID} .ge-gi-shape { display:flex; align-items:center; gap:16px; padding:12px 0; }
|
|
615
|
-
#${SHELL_ROOT_ID} .ge-gi-shape + .ge-gi-shape { border-top:1px solid var(--shell-plaque-line); }
|
|
616
|
-
#${SHELL_ROOT_ID} .ge-gi-shape .ge-gi-pl-svg { flex:0 0 auto; width:96px; }
|
|
617
|
-
#${SHELL_ROOT_ID} .ge-gi-shape-tx { flex:1; min-width:0; display:flex; flex-direction:column; gap:4px; }
|
|
618
|
-
#${SHELL_ROOT_ID} .ge-gi-custom { color:rgba(255,255,255,.88); font-size:15px; line-height:1.6; }
|
|
619
|
-
|
|
620
|
-
/* buy bonus cards — art-forward, centred, flat (no gradients); --card-acc/--card-ink per card.
|
|
621
|
-
order: title → thumb → description → (spacer) → volatility → price → full-bleed button. */
|
|
622
|
-
/* wide: horizontal scrolling strip. Cards scale PROPORTIONALLY with the viewport — every
|
|
623
|
-
dimension is in em-units, so the single font-size knob shrinks the whole card uniformly
|
|
624
|
-
(like the control bar's fit-scale), keeping small popouts readable. mobile: vertical stack. */
|
|
625
|
-
/* the buy-bonus scroll area is a SIZE CONTAINER, so the cards' cqh units measure the overlay
|
|
626
|
-
(the popout frame) and not the browser window — cards fit without any vertical scroll. */
|
|
627
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-scroll { container-type:size; }
|
|
628
|
-
/* Popout / landscape: the horizontal card strip must be the ONLY scroll axis. The cqh-sized cards
|
|
629
|
-
are meant to fit the overlay height (no vertical scroll), but the old 7px font floor stopped them
|
|
630
|
-
shrinking on the tiniest popouts — so they spilled past the frame and the overlay grew a SECOND,
|
|
631
|
-
vertical scrollbar (scrollable in both directions). Two changes keep it single-axis:
|
|
632
|
-
1. the card font floor drops (below) so cards actually fit the frame height; and
|
|
633
|
-
2. vertical scrolling is locked off as a belt-and-braces guard, with the strip centred (the body
|
|
634
|
-
fills the frame and centres the grid) so any sub-pixel slack splits evenly instead of clipping
|
|
635
|
-
the price/CTA off the bottom.
|
|
636
|
-
This mirrors the pixi shell, which masks the cards and drag-scrolls on the X axis alone. (Centring
|
|
637
|
-
lives on the body, not the scroll box, so the grid keeps its width and its own X-scroll.) */
|
|
638
|
-
#${SHELL_ROOT_ID}:not(.ge-mobile) [data-ge="buybonus-overlay"] .ge-ov-scroll { overflow-y:hidden; }
|
|
639
|
-
#${SHELL_ROOT_ID}:not(.ge-mobile) [data-ge="buybonus-overlay"] .ge-ov-body {
|
|
640
|
-
min-height:100%; box-sizing:border-box; display:flex; flex-direction:column; justify-content:center; }
|
|
641
|
-
/* buy-bonus uses the FULL overlay width (no 800px centre cap) so the card row isn't cropped at
|
|
642
|
-
the sides; small horizontal padding keeps the cards off the screen edges. */
|
|
643
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-body { max-width:none; padding:clamp(6px,2.5cqh,16px) clamp(12px,3vw,28px); }
|
|
644
|
-
#${SHELL_ROOT_ID} .ge-bb-grid { display:flex; gap:14px; justify-content:safe center; overflow-x:auto; overflow-y:hidden; padding-bottom:6px;
|
|
645
|
-
scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
|
|
646
|
-
/* the one knob that scales the whole card (its whole layout is em-relative). It is the SMALLER of two
|
|
647
|
-
fits, so the card is always fully visible:
|
|
648
|
-
• 3.4cqh — height: the card stays inside the band between the header and the bet footer (cqh
|
|
649
|
-
measures the overlay popout frame, not the browser window);
|
|
650
|
-
• 84cqw / (N*18) — width: the N cards (each 18em) fit the frame width side-by-side instead of
|
|
651
|
-
overflowing into an X-scroll. --ge-bb-n is the live card count, set in BuyBonus.ts.
|
|
652
|
-
Floor is deliberately tiny: on a 400×225 popout a fully visible card beats a bigger clipped one. */
|
|
653
|
-
#${SHELL_ROOT_ID} .ge-bb-grid .ge-bonus-card { flex:0 0 18em; scroll-snap-align:start;
|
|
654
|
-
font-size:clamp(4px, min(3.4cqh, calc(84cqw / (var(--ge-bb-n,3) * 18))), 12px); }
|
|
655
|
-
/* mobile: vertical stack at a fixed, readable size — scroll the list; only shrink if the card would
|
|
656
|
-
be wider than the frame (very narrow viewport), so it never overflows horizontally. */
|
|
657
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid { display:flex; flex-direction:column; gap:14px; overflow:visible; }
|
|
658
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:min(12px, calc(92cqw / 18)); }
|
|
659
|
-
#${SHELL_ROOT_ID} .ge-bonus-card { display:flex; flex-direction:column; border-radius:1.4em; overflow:hidden;
|
|
660
|
-
background:var(--shell-plaque-glass); border:1px solid var(--shell-plaque-line); color:#fff; text-align:center;
|
|
661
|
-
pointer-events:auto; cursor:pointer; transition:box-shadow .12s ease, background .12s ease; }
|
|
662
|
-
#${SHELL_ROOT_ID} .ge-bonus-card:hover:not(.ge-bonus-off) {
|
|
663
|
-
box-shadow:0 0 0 1px var(--card-acc), 0 12px 34px -12px var(--card-acc); }
|
|
664
|
-
#${SHELL_ROOT_ID} .ge-bonus-card--kbd-focus:not(.ge-bonus-off) {
|
|
665
|
-
box-shadow:0 0 0 1px var(--card-acc), 0 12px 34px -12px var(--card-acc); }
|
|
666
|
-
/* custom card (BonusOption.custom): keep grid sizing + accent vars, drop the default chrome so the game owns the UI */
|
|
667
|
-
#${SHELL_ROOT_ID} .ge-bonus-card--custom { background:none; border:none; cursor:default; }
|
|
668
|
-
#${SHELL_ROOT_ID} .ge-bonus-body { display:flex; flex-direction:column; align-items:center; flex:1; padding:1.25em 1.1em .9em; }
|
|
669
|
-
#${SHELL_ROOT_ID} .ge-bonus-title { font-size:1.3em; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
|
|
670
|
-
color:var(--card-acc); margin-bottom:.75em; }
|
|
671
|
-
#${SHELL_ROOT_ID} .ge-bonus-thumb { height:6.2em; display:flex; align-items:center; justify-content:center; margin-bottom:.7em; }
|
|
672
|
-
#${SHELL_ROOT_ID} .ge-bonus-thumb img { max-height:100%; max-width:100%; object-fit:contain; filter:drop-shadow(0 6px 14px rgba(0,0,0,.45)); }
|
|
673
|
-
#${SHELL_ROOT_ID} .ge-bonus-thumb-ph { font-size:3.5em; color:var(--card-acc); opacity:.85; display:flex; }
|
|
674
|
-
#${SHELL_ROOT_ID} .ge-bonus-desc { font-size:.96em; line-height:1.45; color:rgba(255,255,255,.82); }
|
|
675
|
-
#${SHELL_ROOT_ID} .ge-bonus-spacer { flex:1; min-height:.7em; }
|
|
676
|
-
#${SHELL_ROOT_ID} .ge-bonus-vol { display:flex; justify-content:center; align-items:center; gap:0;
|
|
677
|
-
font-size:2.1em; line-height:1; margin-bottom:.55em; }
|
|
678
|
-
#${SHELL_ROOT_ID} .ge-bonus-vol-on, #${SHELL_ROOT_ID} .ge-bonus-vol-off { display:inline-flex; gap:0; }
|
|
679
|
-
#${SHELL_ROOT_ID} .ge-bonus-vol-on { color:var(--card-acc); }
|
|
680
|
-
#${SHELL_ROOT_ID} .ge-bonus-vol-off { color:rgba(255,255,255,.18); }
|
|
681
|
-
#${SHELL_ROOT_ID} .ge-bonus-price { font-size:1.6em; font-weight:800; color:#fff; font-variant-numeric:tabular-nums; white-space:nowrap; }
|
|
682
|
-
#${SHELL_ROOT_ID} .ge-bonus-cta { width:100%; border:none; padding:1.15em; font-weight:800; font-size:1.05em;
|
|
683
|
-
letter-spacing:.05em; text-transform:uppercase; cursor:pointer; background:var(--card-acc); color:var(--card-ink);
|
|
684
|
-
transition:filter .12s ease; }
|
|
685
|
-
#${SHELL_ROOT_ID} .ge-bonus-cta:hover:not([disabled]) { filter:brightness(1.06); }
|
|
686
|
-
/* unaffordable / disabled */
|
|
687
|
-
#${SHELL_ROOT_ID} .ge-bonus-card.ge-bonus-off { opacity:.62; cursor:default; }
|
|
688
|
-
#${SHELL_ROOT_ID} .ge-bonus-off .ge-bonus-title { color:rgba(255,255,255,.6); }
|
|
689
|
-
#${SHELL_ROOT_ID} .ge-bonus-off .ge-bonus-vol-on { color:rgba(255,255,255,.4); }
|
|
690
|
-
#${SHELL_ROOT_ID} .ge-bonus-off .ge-bonus-cta { background:#8d939e; color:#3a3f47; cursor:default; }
|
|
691
|
-
/* buy-bonus confirm — bonus preview inside the shared sheet card (see .ge-sheet-actions below) */
|
|
692
|
-
#${SHELL_ROOT_ID} .ge-confirm-preview { display:flex; flex-direction:column; align-items:center; gap:10px; text-align:center; }
|
|
693
|
-
#${SHELL_ROOT_ID} .ge-confirm-preview .ge-bonus-thumb,
|
|
694
|
-
#${SHELL_ROOT_ID} .ge-confirm-preview .ge-bonus-vol { margin:0; }
|
|
695
|
-
/* effective-bet readout while a feature is active (value colour set inline to the feature accent) */
|
|
696
|
-
#${SHELL_ROOT_ID} .ge-rd.ge-bet-feature { font-weight:800; }
|
|
697
|
-
/* bet control — compact pill in a thin footer at the screen bottom (footer only as tall as the pill) */
|
|
698
|
-
#${SHELL_ROOT_ID} .ge-bb-betbar { flex:0 0 auto; display:flex; justify-content:center; padding:4px; }
|
|
699
|
-
#${SHELL_ROOT_ID} .ge-bb-betpill { display:inline-flex; align-items:center; gap:4px;
|
|
700
|
-
background:var(--shell-plaque-dark); border-radius:999px; padding:3px 5px; }
|
|
701
|
-
#${SHELL_ROOT_ID} .ge-bb-betstep { pointer-events:auto; cursor:pointer; width:32px; height:32px; border:none; border-radius:50%;
|
|
702
|
-
background:none; color:#fff; font-size:20px; display:flex; align-items:center; justify-content:center;
|
|
703
|
-
transition:color .12s ease, transform .08s ease; }
|
|
704
|
-
#${SHELL_ROOT_ID} .ge-bb-betstep:hover { color:var(--shell-accent); }
|
|
705
|
-
#${SHELL_ROOT_ID} .ge-bb-betstep:active { transform:scale(.9); }
|
|
706
|
-
#${SHELL_ROOT_ID} .ge-bb-betval { min-width:80px; text-align:center; padding:0 2px; }
|
|
707
|
-
#${SHELL_ROOT_ID} .ge-bb-betval span { display:block; font-size:7px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
|
|
708
|
-
color:var(--shell-plaque-label); }
|
|
709
|
-
#${SHELL_ROOT_ID} .ge-bb-betval b { font-size:14px; font-weight:800; font-variant-numeric:tabular-nums; color:#fff; }
|
|
710
|
-
/* Popout S (short landscape): the header (title + ✕) and the bet footer are fixed-px and dwarf the
|
|
711
|
-
shrunk cards. Scale the buy-bonus chrome down with the FRAME height. Units are cqh (the overlay is a
|
|
712
|
-
size container below), NOT vh — vh tracks the browser window, which only equals the frame inside the
|
|
713
|
-
Stake iframe, so it wouldn't shrink in the demo's device-frame view. Coefficients hit the normal cap
|
|
714
|
-
by ~450px tall (Popout L) and shrink below that, to a readable floor at Popout S (225px). */
|
|
715
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] { container:ge-bb-frame / size; }
|
|
716
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-head { padding:clamp(3px,1.33cqh,6px) 10px; }
|
|
717
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-title { font-size:clamp(11px,3.5cqh,16px); }
|
|
718
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-spacer { width:clamp(24px,7cqh,32px); }
|
|
719
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-nav { width:clamp(24px,7cqh,32px); height:clamp(24px,7cqh,32px);
|
|
720
|
-
font-size:clamp(14px,4cqh,18px); border-radius:clamp(7px,2cqh,9px); }
|
|
721
|
-
/* the bet pill read too large next to the shrunk cards — size it ~0.8× across the board (the floors
|
|
722
|
-
stay readable; the maxima are what dominate on Popout L / wide frames). */
|
|
723
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betbar { padding:clamp(2px,.75cqh,3px); }
|
|
724
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betpill { padding:clamp(2px,.55cqh,3px) clamp(3px,.9cqh,4px); }
|
|
725
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betstep { width:clamp(20px,5.7cqh,26px); height:clamp(20px,5.7cqh,26px);
|
|
726
|
-
font-size:clamp(12px,3.5cqh,16px); }
|
|
727
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betval { min-width:clamp(50px,14cqh,66px); }
|
|
728
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betval b { font-size:clamp(9px,2.5cqh,11px); }
|
|
729
|
-
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-bb-betval span { font-size:clamp(5px,1.25cqh,6px); }
|
|
730
|
-
|
|
731
|
-
/* ═══ base/wide plaque bar — grouped dark + glass panels (reference-style) ═══ */
|
|
732
|
-
#${SHELL_ROOT_ID} .ge-zone-plaques { gap:0; } /* panels connect; buttons overlap */
|
|
733
|
-
#${SHELL_ROOT_ID} .ge-pl { display:flex; align-items:center; height:56px; box-sizing:border-box;
|
|
734
|
-
border-radius:16px; padding:0 20px; gap:18px; }
|
|
735
|
-
#${SHELL_ROOT_ID} .ge-pl-dark { background:var(--shell-plaque-dark); }
|
|
736
|
-
#${SHELL_ROOT_ID} .ge-pl-glass { background:var(--shell-plaque-glass); }
|
|
737
|
-
/* FS/replay left blocks — Free Spins counter (compact) + Total Win, standalone glass plaques
|
|
738
|
-
sitting just right of the balance pill */
|
|
739
|
-
#${SHELL_ROOT_ID} .ge-pl-fs, #${SHELL_ROOT_ID} .ge-pl-totalwin { margin-left:8px; }
|
|
740
|
-
#${SHELL_ROOT_ID} .ge-pl-fs { padding:0 16px; }
|
|
741
|
-
#${SHELL_ROOT_ID} .ge-pl .ge-rd { color:#fff; text-shadow:none; }
|
|
742
|
-
#${SHELL_ROOT_ID} .ge-pl .ge-rd .ge-lbl { color:var(--shell-plaque-label); }
|
|
743
|
-
#${SHELL_ROOT_ID} .ge-pl .ge-iconbtn { color:#fff; }
|
|
744
|
-
/* LEFT: [menu] ⊐ coin ⊏ [balance] — coin overlaps both; balance fixed-wide so it doesn't jiggle */
|
|
745
|
-
#${SHELL_ROOT_ID} .ge-pl-menu { border-radius:16px 0 0 16px; padding-right:20px; }
|
|
746
|
-
#${SHELL_ROOT_ID} .ge-pl-bal { border-radius:0 16px 16px 0; padding-left:24px; min-width:200px; }
|
|
747
|
-
#${SHELL_ROOT_ID} .ge-zone-plaques .ge-shell-buybonus { margin:0 -16px; position:relative; z-index:3; }
|
|
748
|
-
/* RIGHT: [bet] · |divider| · [auto · SPIN · turbo] */
|
|
749
|
-
#${SHELL_ROOT_ID} .ge-pl-bet { border-radius:16px 0 0 16px; justify-content:space-between;
|
|
750
|
-
width:210px; padding-right:8px; } /* fixed: bet value never reflows the panel */
|
|
751
|
-
#${SHELL_ROOT_ID} .ge-pl-divider { align-self:center; flex:0 0 auto; width:1px; height:30px;
|
|
752
|
-
background:var(--shell-plaque-line); }
|
|
753
|
-
#${SHELL_ROOT_ID} .ge-spinwrap { display:flex; align-items:center; gap:10px; height:56px;
|
|
754
|
-
border-radius:0 16px 16px 0; padding:0 8px 0 14px; }
|
|
755
|
-
#${SHELL_ROOT_ID} .ge-spinwrap .ge-iconbtn { color:#fff; }
|
|
756
|
-
#${SHELL_ROOT_ID} .ge-spinwrap .ge-shell-spin { margin:0 -2px; position:relative; z-index:3; }
|
|
757
|
-
/* tighter bet chevrons, parked next to autoplay */
|
|
758
|
-
#${SHELL_ROOT_ID} .ge-pl-bet .ge-betstep { gap:0; }
|
|
759
|
-
#${SHELL_ROOT_ID} .ge-pl-bet .ge-betstep .ge-iconbtn { height:24px; }
|
|
760
|
-
/* accent hover highlight on every control */
|
|
761
|
-
#${SHELL_ROOT_ID} .ge-pl .ge-iconbtn:hover, #${SHELL_ROOT_ID} .ge-spinwrap .ge-iconbtn:hover,
|
|
762
|
-
#${SHELL_ROOT_ID} .ge-iconbtn:hover { color:var(--shell-accent); }
|
|
763
|
-
/* turbo at rest (level 0) reads as dimmed — clearly off, but lighter than a true [disabled] control */
|
|
764
|
-
#${SHELL_ROOT_ID} [data-ge="turbo"]:not(.ge-active) { opacity:.5; }
|
|
765
|
-
#${SHELL_ROOT_ID} [data-ge="turbo"]:not(.ge-active):hover { opacity:1; }
|
|
766
|
-
/* autoplay glow while running (beats the white .ge-spinwrap/.ge-pl colour rules) */
|
|
767
|
-
#${SHELL_ROOT_ID} .ge-iconbtn.ge-glow { color:var(--shell-accent); filter:drop-shadow(0 0 6px var(--shell-accent)); }
|
|
768
|
-
/* tappable stake readout opens the bet picker */
|
|
769
|
-
#${SHELL_ROOT_ID} .ge-betbtn { pointer-events:auto; cursor:pointer; border-radius:8px; transition:color .12s ease; }
|
|
770
|
-
#${SHELL_ROOT_ID} .ge-betbtn:hover { color:var(--shell-accent); }
|
|
771
|
-
#${SHELL_ROOT_ID} .ge-betbtn.ge-disabled { pointer-events:none; }
|
|
772
|
-
/* WIN — inline between the groups, same block height/shape as the other plaques (glass tone) */
|
|
773
|
-
#${SHELL_ROOT_ID} .ge-winpill { flex:0 0 auto; align-self:center; display:flex; align-items:center;
|
|
774
|
-
justify-content:center; gap:8px; height:56px; box-sizing:border-box; padding:0 24px; border-radius:16px;
|
|
775
|
-
white-space:nowrap; pointer-events:none; background:var(--shell-plaque-glass); color:#fff;
|
|
776
|
-
font-size:16px; font-weight:800; font-variant-numeric:tabular-nums; text-shadow:none; }
|
|
777
|
-
#${SHELL_ROOT_ID} .ge-winpill .ge-lbl { display:inline; margin:0; color:rgba(255,255,255,.7);
|
|
778
|
-
font-size:10px; letter-spacing:.12em; }
|
|
779
|
-
/* lifted above the bar on overflow — compact pill (flex child of the host, scaled with it) */
|
|
780
|
-
#${SHELL_ROOT_ID} .ge-winpill.ge-up { height:auto; padding:6px 16px; border-radius:999px; }
|
|
781
|
-
|
|
782
|
-
/* centred CARD modal — opaque card on a frosted backdrop, accent title heading at the top
|
|
783
|
-
(no ✕), content, full-bleed footer button(s). Shared by the buy-bonus confirm AND the
|
|
784
|
-
bet/autoplay pickers so every centred modal is the same type. Close via backdrop click. */
|
|
785
|
-
#${SHELL_ROOT_ID} .ge-sheet { position:absolute; inset:0; z-index:60; pointer-events:auto; display:flex;
|
|
786
|
-
align-items:center; justify-content:center; padding:clamp(10px,4vh,24px); box-sizing:border-box;
|
|
787
|
-
background:rgba(12,17,28,.5); backdrop-filter:blur(var(--ge-sheet-blur,20px)) saturate(120%);
|
|
788
|
-
-webkit-backdrop-filter:blur(var(--ge-sheet-blur,20px)) saturate(120%); animation:ge-ov-in .16s ease-out; }
|
|
789
|
-
/* Card sizes in cq units of the shell root → responsive on EVERY screen, not just popouts. The
|
|
790
|
-
card's font-size is the one knob (clamped for readability); everything inside is em-relative so
|
|
791
|
-
the whole card scales as a unit. GameShell.fitModal() still transform-scales it down as a
|
|
792
|
-
backstop for very short popouts. */
|
|
793
|
-
#${SHELL_ROOT_ID} .ge-modal-card { font-size:clamp(11px, 2cqmin, 15px); width:100%; max-width:28em; box-sizing:border-box;
|
|
794
|
-
overflow:hidden; transform-origin:center center; background:var(--shell-plaque-solid); border-radius:1.3em;
|
|
795
|
-
display:flex; flex-direction:column; }
|
|
796
|
-
/* ✕ pinned to the overlay corner (the screen), not the card */
|
|
797
|
-
#${SHELL_ROOT_ID} .ge-modal-close { position:absolute; top:12px; right:12px; z-index:2; width:36px; height:36px;
|
|
798
|
-
border:none; border-radius:50%; cursor:pointer; pointer-events:auto; background:var(--shell-plaque-dark); color:#fff;
|
|
799
|
-
display:flex; align-items:center; justify-content:center; font-size:20px; transition:background .12s ease, color .12s ease; }
|
|
800
|
-
#${SHELL_ROOT_ID} .ge-modal-close:hover { background:var(--shell-plaque-glass); color:var(--shell-accent); }
|
|
801
|
-
#${SHELL_ROOT_ID} .ge-modal-body { padding:1.2em; display:flex; flex-direction:column; gap:1.05em; }
|
|
802
|
-
#${SHELL_ROOT_ID} .ge-modal-title { margin:0; text-align:center; color:var(--card-acc, var(--shell-accent));
|
|
803
|
-
font-weight:800; letter-spacing:.04em; text-transform:uppercase; font-size:1.2em; }
|
|
804
|
-
#${SHELL_ROOT_ID} .ge-modal-text { margin:0; text-align:center; color:rgba(255,255,255,.85); font-size:.93em; line-height:1.5; }
|
|
805
|
-
#${SHELL_ROOT_ID} .ge-sheet-grid { display:grid; gap:.65em; grid-template-columns:repeat(var(--cols,3),1fr); }
|
|
806
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-sheet-grid { grid-template-columns:repeat(var(--cols-m,var(--cols,3)),1fr); }
|
|
807
|
-
/* the bet picker packs 6 chips/row → give its card room (autoplay & co. stay at the 28em default) */
|
|
808
|
-
#${SHELL_ROOT_ID} .ge-sheet[data-ge="bet-modal"] .ge-modal-card { max-width:44em; }
|
|
809
|
-
#${SHELL_ROOT_ID} .ge-chip { pointer-events:auto; cursor:pointer; border:1px solid var(--shell-plaque-line);
|
|
810
|
-
border-radius:.8em; background:rgba(255,255,255,.04); color:#fff; font-size:1em; font-weight:700;
|
|
811
|
-
font-variant-numeric:tabular-nums; padding:.8em .55em; transition:background .12s ease, border-color .12s ease; }
|
|
812
|
-
#${SHELL_ROOT_ID} .ge-chip:hover { background:var(--shell-plaque-glass-hover); }
|
|
813
|
-
#${SHELL_ROOT_ID} .ge-chip.ge-on { border-color:var(--shell-accent); background:var(--shell-accent); color:#fff; }
|
|
814
|
-
/* full-bleed footer button(s), flush to the card's bottom edge (card clips the corners) */
|
|
815
|
-
#${SHELL_ROOT_ID} .ge-modal-actions { display:flex; }
|
|
816
|
-
#${SHELL_ROOT_ID} .ge-modal-actions > * { flex:1; }
|
|
817
|
-
#${SHELL_ROOT_ID} .ge-modal-btn { width:100%; border:none; padding:1.05em; font-size:1em; font-weight:800;
|
|
818
|
-
letter-spacing:.04em; text-transform:uppercase; cursor:pointer; pointer-events:auto; transition:filter .12s ease; }
|
|
819
|
-
#${SHELL_ROOT_ID} .ge-modal-btn:hover:not([disabled]) { filter:brightness(1.08); }
|
|
820
|
-
#${SHELL_ROOT_ID} .ge-modal-btn--accent { background:var(--card-acc, var(--shell-accent)); color:#fff; }
|
|
821
|
-
#${SHELL_ROOT_ID} .ge-modal-btn--ghost { background:var(--shell-plaque-glass-hover); color:#fff; }
|
|
822
|
-
/* replay summary — label/value rows, accented total-win row */
|
|
823
|
-
#${SHELL_ROOT_ID} .ge-replay-rows { display:flex; flex-direction:column; }
|
|
824
|
-
#${SHELL_ROOT_ID} .ge-replay-row { display:flex; justify-content:space-between; align-items:baseline; gap:1.05em; padding:.73em .13em; }
|
|
825
|
-
#${SHELL_ROOT_ID} .ge-replay-row + .ge-replay-row { border-top:1px solid var(--shell-plaque-line); }
|
|
826
|
-
#${SHELL_ROOT_ID} .ge-replay-row span { color:var(--shell-plaque-label); text-transform:uppercase; letter-spacing:.07em; font-size:.73em; font-weight:700; }
|
|
827
|
-
#${SHELL_ROOT_ID} .ge-replay-row b { color:#fff; font-weight:800; font-size:1em; font-variant-numeric:tabular-nums; }
|
|
828
|
-
#${SHELL_ROOT_ID} .ge-replay-total span { color:#fff; font-size:.8em; }
|
|
829
|
-
#${SHELL_ROOT_ID} .ge-replay-total b { color:var(--shell-accent); font-size:1.27em; }
|
|
830
|
-
|
|
831
|
-
#${SHELL_ROOT_ID}.ge-shell-hidden { opacity:0; pointer-events:none; transition:opacity .25s ease; }
|
|
832
|
-
`;
|
|
833
|
-
|
|
834
|
-
/** The shared money formatter for every shell readout (balance, win, total win, bet, prices).
|
|
835
|
-
*
|
|
836
|
-
* `maxDecimals` is the MAXIMUM fraction digits (default 2); `minDecimals` (defaults to
|
|
837
|
-
* `maxDecimals`) is the MINIMUM. By default the value is shown at exactly `minDecimals` places.
|
|
838
|
-
* With `variableDecimals` — used only for win / total-win — it is rounded to `maxDecimals`, then
|
|
839
|
-
* trailing zeros are trimmed down to (but never past) `minDecimals`, so small wins keep their
|
|
840
|
-
* significant digits. Balance / bet / prices stay fixed at `minDecimals`.
|
|
841
|
-
*
|
|
842
|
-
* Example with `maxDecimals: 4, minDecimals: 2`:
|
|
843
|
-
* fixed → 0.0673 → 0,07 0.3 → 0,30
|
|
844
|
-
* variable → 0.0673 → 0,0673 0.067 → 0,067 0.3 → 0,30 0 → 0,00
|
|
845
|
-
*/
|
|
846
|
-
function formatCurrency(value, currency, variableDecimals = false) {
|
|
847
|
-
const maxDecimals = currency.maxDecimals ?? 2;
|
|
848
|
-
const minDecimals = Math.max(0, Math.min(maxDecimals, currency.minDecimals ?? maxDecimals));
|
|
849
|
-
const thousands = currency.separator?.thousands ?? '.';
|
|
850
|
-
const decimal = currency.separator?.decimal ?? ',';
|
|
851
|
-
const safe = Number.isFinite(value) ? value : 0;
|
|
852
|
-
// fixed callers round at minDecimals; variable callers round at maxDecimals then trim back down.
|
|
853
|
-
const places = variableDecimals ? maxDecimals : minDecimals;
|
|
854
|
-
const fixed = safe.toFixed(places);
|
|
855
|
-
const [intPart, rawFrac = ''] = fixed.split('.');
|
|
856
|
-
// trim trailing zeros, but keep at least `minDecimals` fraction digits
|
|
857
|
-
let fracPart = rawFrac;
|
|
858
|
-
if (fracPart.length > minDecimals) {
|
|
859
|
-
fracPart = fracPart.replace(/0+$/, '');
|
|
860
|
-
if (fracPart.length < minDecimals)
|
|
861
|
-
fracPart = fracPart.padEnd(minDecimals, '0');
|
|
862
|
-
}
|
|
863
|
-
const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousands);
|
|
864
|
-
const number = fracPart.length ? `${grouped}${decimal}${fracPart}` : grouped;
|
|
865
|
-
return currency.position === 'left'
|
|
866
|
-
? `${currency.symbol}${number}`
|
|
867
|
-
: `${number} ${currency.symbol}`;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// Sharp monochrome icon set, traced from the brand sheet. Every glyph uses
|
|
871
|
-
// currentColor so the caller's color cascades (active states recolour via CSS);
|
|
872
|
-
// hollow shapes set fill-rule="evenodd". All return an inline <svg> sized 1em.
|
|
873
|
-
const SVGS = {
|
|
874
|
-
// sharp angular set, traced from the brand sheet — monochrome currentColor
|
|
875
|
-
// (glow via CSS drop-shadow). Hollow shapes rely on fill-rule="evenodd".
|
|
876
|
-
// hollow loop ring + two arrowheads (renders dark-on-bright inside SPIN)
|
|
877
|
-
spin: `<g fill="currentColor" fill-rule="evenodd"><path d="M17.92 4.68 L13.91 2.81 L10.55 1.6 L12.05 3.47 L11.95 3.56 L10.93 3.65 L9.25 4.12 L7.94 4.77 L7.01 5.42 L5.61 6.82 L4.58 8.41 L4.03 9.53 L3.28 11.67 L2.91 13.91 L4.49 16.52 L4.68 16.62 L6.54 14.94 L6.17 14.01 L6.08 13.17 L5.98 13.07 L5.98 11.49 L6.08 10.93 L6.45 9.71 L6.82 8.97 L7.48 8.04 L8.22 7.29 L9.25 6.54 L9.99 6.17 L11.21 5.8 L11.77 5.7 L13.73 5.7 L13.82 5.8 L14.57 5.89 L15.87 6.45 L16.06 6.45 L17.92 4.86Z"/><path d="M19.6 7.66 L19.42 7.57 L17.83 9.16 L18.11 9.9 L18.2 10.74 L18.3 10.83 L18.3 12.7 L18.2 12.79 L18.11 13.54 L17.46 15.03 L16.9 15.87 L15.87 16.9 L14.94 17.55 L14.1 17.92 L13.17 18.2 L12.05 18.39 L11.02 18.39 L10.93 18.3 L9.9 18.2 L9.06 17.92 L8.41 17.55 L8.22 17.55 L6.73 18.76 L6.26 19.32 L11.21 21.56 L12.7 22.03 L13.45 22.4 L13.73 22.4 L12.23 20.53 L12.33 20.44 L13.54 20.35 L15.31 19.79 L16.8 18.95 L17.64 18.3 L18.76 17.08 L19.51 15.96 L20.16 14.66 L20.25 14.19 L20.53 13.63 L20.72 12.98 L20.72 12.61 L20.91 12.14 L21.09 10.37 L19.97 8.5Z"/></g>`,
|
|
878
|
-
turbo: `<g fill="currentColor" fill-rule="evenodd"><path d="M19.16 1.71 L16.47 1.71 L16.36 1.6 L13.23 1.6 L12.78 1.71 L11.78 3.39 L10.55 5.85 L10.32 6.07 L8.31 9.99 L8.09 10.21 L7.19 12.11 L12 12.34 L14.46 9.88 L14.24 9.65 L12.89 9.54 L12.89 9.2 L17.03 4.4Z"/><path d="M19.83 6.97 L18.93 7.64 L16.36 9.99 L9.65 16.47 L4.17 21.62 L4.17 22.4 L5.4 21.39 L13.34 13.9 L13.45 14.12 L10.43 20.16 L10.21 20.83 L9.65 21.95 L9.76 21.95 L19.27 10.32 L19.27 10.21 L17.59 10.1 L17.48 9.88 L18.71 8.53Z"/></g>`,
|
|
879
|
-
// bolt with 1/2/3 speed lines — escalating turbo level
|
|
880
|
-
turbo1: `<g fill="currentColor" fill-rule="evenodd"><path d="M21.82 1.6 L21.59 1.72 L21.01 1.72 L20.44 1.95 L18.7 2.29 L18.12 2.29 L18.01 2.41 L16.39 2.64 L14.77 3.1 L13.73 5.41 L13.73 5.64 L13.27 6.45 L13.27 6.68 L12.81 7.49 L12.81 7.72 L12.35 8.53 L12.35 8.76 L11.77 9.8 L11.42 10.84 L10.15 13.5 L10.15 13.73 L14.54 13.73 L14.66 13.85 L13.39 16.16 L12.81 16.97 L11.54 19.4 L11.31 19.63 L11.08 20.2 L10.04 21.82 L9.8 22.4 L9.92 22.4 L20.9 11.19 L22.05 10.15 L22.05 10.04 L16.62 10.15 L16.51 10.27 L15.7 10.27 L15.58 10.15 L15.81 9.69 L16.28 9.23 L21.48 2.29Z"/><path d="M8.53 12.46 L7.72 12.58 L7.26 12.81 L6.92 12.81 L6.45 13.04 L6.11 13.04 L5.64 13.27 L3.68 13.73 L1.95 14.31 L1.95 14.43 L7.61 14.43Z"/></g>`,
|
|
881
|
-
turbo2: `<g fill="currentColor" fill-rule="evenodd"><path d="M22.4 2.68 L18.38 3.61 L15.4 4.43 L14.68 5.98 L13.96 7.93 L13.65 8.45 L13.65 8.65 L13.34 9.17 L13.34 9.37 L13.03 9.89 L13.03 10.1 L12.72 10.61 L12.72 10.82 L12.41 11.33 L12.41 11.54 L12.1 12.05 L12.1 12.26 L11.79 12.77 L11.49 13.7 L15.3 13.7 L15.5 13.9 L14.47 15.55 L14.16 16.27 L13.96 16.48 L11.28 21.32 L11.9 20.91 L20.65 11.95 L22.09 10.61 L22.09 10.51 L20.55 10.51 L20.44 10.61 L16.32 10.71 L16.22 10.51 L19.83 6.08Z"/><path d="M10.04 15.04 L9.43 15.14 L8.81 14.93 L7.98 14.93 L7.88 15.04 L6.34 15.14 L6.23 15.24 L5.62 15.24 L4.28 15.55 L2.84 15.76 L1.6 16.17 L10.04 16.17Z"/><path d="M10.56 9.99 L9.43 10.1 L9.32 10.2 L7.37 10.51 L6.85 10.71 L6.44 10.71 L5.51 11.02 L3.04 11.54 L2.94 11.74 L5.1 11.85 L5.2 11.95 L6.65 11.95 L6.75 12.05 L9.01 12.05 L9.12 12.15 L9.73 12.15 L10.56 10.3Z"/></g>`,
|
|
882
|
-
turbo3: `<g fill="currentColor" fill-rule="evenodd"><path d="M22.4 3 L22 3 L20.3 3.5 L19.9 3.5 L19.5 3.7 L19.1 3.7 L15.9 4.6 L13.9 9.1 L13.9 9.3 L13.6 10 L13.2 10.7 L13.2 10.9 L12.6 12.1 L12.3 13 L12 13.6 L15.8 13.6 L15.9 13.8 L14.4 16.3 L13.7 17.7 L13.2 18.4 L12.8 19.1 L12.5 19.8 L12 20.5 L11.9 21 L22.2 10.6 L22.2 10.5 L16.8 10.6 L16.7 10.4 L22.3 3.3Z"/><path d="M11 11.3 L9.9 11.3 L9.8 11.4 L7.8 11.6 L6 11.9 L5.4 12.1 L3.9 12.3 L3.1 12.5 L3.1 12.6 L5.6 12.7 L5.7 12.8 L7.9 12.8 L8 12.9 L10.4 12.9 L11 11.6Z"/><path d="M12.7 7.3 L12 7.3 L11.9 7.4 L10.7 7.5 L10.1 7.7 L9.6 7.7 L7.2 8.2 L6.7 8.4 L5.5 8.6 L5.6 8.8 L7 8.8 L7.1 8.9 L8.5 8.9 L8.6 9 L12 9.1Z"/><path d="M10.9 15.4 L10.3 15.7 L10 15.7 L9.1 15.4 L7.8 15.5 L7.7 15.6 L6.9 15.6 L6.8 15.7 L6 15.7 L5.9 15.8 L4.1 16 L3.5 16.2 L2.5 16.3 L1.6 16.6 L2.6 16.6 L2.7 16.7 L10.9 16.7Z"/></g>`,
|
|
883
|
-
autoplay: `<g fill="currentColor" fill-rule="evenodd"><path d="M19.48 5.36 L15.02 1.6 L14.82 1.6 L15.62 3.28 L15.52 3.78 L14.82 3.78 L14.72 3.88 L12.64 3.88 L12.54 3.78 L5.71 3.78 L3.23 6.35 L3.23 16.26 L3.33 16.26 L4.82 14.48 L4.82 12.5 L4.72 12.4 L4.72 7.25 L4.82 7.05 L6.4 5.56 L8.29 5.56 L8.38 5.46 L19.48 5.46Z"/><path d="M9.87 8.83 L9.87 15.27 L9.97 15.27 L11.26 14.38 L15.12 12.1 L15.12 12 L11.16 9.62 L10.66 9.23Z"/><path d="M20.67 7.94 L20.17 8.34 L19.18 9.52 L19.18 16.85 L17.5 18.44 L17.1 18.54 L4.42 18.54 L4.42 18.64 L7.3 21.21 L8.78 22.4 L8.78 22 L8.09 20.42 L8.19 20.22 L18.29 20.22 L20.77 17.65 L20.67 17.35Z"/></g>`,
|
|
884
|
-
// hollow square — stop autoplay / halt
|
|
885
|
-
stop: `<g fill="currentColor" fill-rule="evenodd"><path d="M22.4 3.04 L21.1 4.82 L21.1 9.47 L21.03 9.54 L21.03 12.41 L21.1 12.96 L21.03 18.84 L21.1 19.18 L20.35 20.01 L19.39 20.89 L17.27 20.89 L16.72 20.96 L4.75 20.96 L4.68 20.89 L3.04 22.33 L4.13 22.33 L4.2 22.4 L6.66 22.4 L6.73 22.33 L9.13 22.33 L9.19 22.4 L20.48 22.4 L22.4 20.48Z"/><path d="M20.96 1.6 L3.38 1.6 L1.6 3.45 L1.6 20.96 L2.9 19.12 L2.9 4.75 L4.54 3.11 L19.12 3.11 L19.46 2.97Z"/></g>`,
|
|
886
|
-
menu: `<g fill="currentColor" fill-rule="evenodd"><path d="M1.6 13.52 L19.61 13.52 L22.27 10.73 L22.4 10.35 L4.39 10.35 L2.36 12.51Z"/><path d="M1.6 6.04 L19.61 6.04 L22.15 3.38 L22.4 2.87 L4.39 2.87 L2.36 5.02Z"/><path d="M1.6 21 L3.25 21 L3.38 21.13 L4.9 21.13 L5.02 21 L19.61 21 L22.4 17.96 L4.52 17.83 L4.26 17.96 L2.36 19.99Z"/></g>`,
|
|
887
|
-
betUp: `<g fill="currentColor" fill-rule="evenodd"><path d="M21.01 15.43 L19.18 13.07 L14.68 6.75 L12.32 3.64 L12 3.42 L8.57 7.93 L6.42 11.04 L2.89 15.75 L2.78 16.07 L12 8.14 L12.21 8.14 L20.68 15.43Z"/><path d="M22.4 20.26 L20.04 17.79 L12.21 10.28 L12 10.28 L6.53 15.43 L6.42 15.65 L1.81 20.15 L1.6 20.58 L12 14.04 L12.21 14.04 L12.75 14.47 L18.11 17.68 L18.65 18.11 L19.51 18.54 L22.29 20.36Z"/></g>`,
|
|
888
|
-
betDown: `<g fill="currentColor" fill-rule="evenodd"><path d="M1.6 2.74 L1.84 3.22 L5.57 7.43 L11.7 14.04 L12.06 14.28 L12.42 14.04 L18.79 7.19 L22.4 2.98 L22.28 2.86 L18.55 5.51 L12.06 10.44 L5.81 5.63Z"/><path d="M20.84 9.35 L18.07 11.52 L12.18 16.57 L11.94 16.57 L4.49 10.2 L3.52 9.48 L3.4 9.6 L8.57 16.57 L8.81 17.05 L10.26 18.85 L10.5 19.33 L11.34 20.3 L11.58 20.78 L12.18 21.26 L18.43 12.84Z"/></g>`,
|
|
889
|
-
minus: `<g fill="currentColor" fill-rule="evenodd"><path d="M1.6 13.26 L12.42 13.26 L12.5 13.18 L19.81 13.26 L22.4 10.82 L4.27 10.74Z"/></g>`,
|
|
890
|
-
plus: `<g fill="currentColor" fill-rule="evenodd"><path d="M13.16 1.6 L12.26 3.17 L10.77 5.42 L10.77 10.73 L10.69 10.8 L4.33 10.8 L1.79 13.2 L10.69 13.2 L10.77 13.27 L10.77 22.4 L12.41 19.71 L13.16 18.66 L13.16 13.27 L13.23 13.2 L19.59 13.2 L22.21 10.8 L13.23 10.8 L13.16 10.73Z"/></g>`,
|
|
891
|
-
gift: `<rect x="4" y="9" width="16" height="11" rx="2" fill="currentColor"/><path d="M9 9a2.5 2.5 0 1 1 3-3 2.5 2.5 0 1 1 3 3z" fill="currentColor"/><rect x="11" y="9" width="2" height="11" fill="rgba(0,0,0,.35)"/>`,
|
|
892
|
-
info: `<g fill="currentColor" fill-rule="evenodd"><path d="M14.22 7.78 L11.13 7.86 L9.4 9.21 L9.17 9.29 L10.15 9.29 L10.23 9.59 L9.32 13.73 L8.87 17.12 L8.57 18.78 L8.5 19.69 L8.27 20.74 L8.12 22.32 L8.04 22.4 L13.62 17.95 L11.96 17.95 L11.89 17.58 L12.41 15.69 L12.49 15.01 L12.87 13.73 L12.87 13.43 L13.24 12.15 L13.77 9.97 L14 8.68 L14.22 8.08Z"/><path d="M15.96 1.6 L12.94 1.6 L11.74 3.18 L11.51 3.63 L10.08 5.67 L12.79 5.59 L14.37 3.71Z"/></g>`,
|
|
893
|
-
soundOn: `<g fill="currentColor" fill-rule="evenodd"><path d="M16.61 6.68 L17.16 8.4 L17.4 9.97 L17.4 13.88 L17.32 13.95 L17.16 15.44 L16.61 17.16 L18.88 13.72 L18.88 12.86 L18.96 12.78 L18.88 10.12Z"/><path d="M19.27 4.34 L19.58 4.88 L19.66 5.28 L20.05 6.14 L20.6 7.93 L20.91 9.5 L20.91 10.44 L20.99 10.51 L20.99 13.25 L20.91 13.33 L20.91 14.19 L20.76 15.13 L20.05 17.63 L19.27 19.43 L20.29 18.02 L21.15 16.61 L22.4 14.19 L22.4 9.58 L21.31 7.39 L20.29 5.74Z"/><path d="M13.17 1.76 L10.28 4.34 L6.29 8.17 L1.6 8.25 L1.6 15.6 L2.15 15.75 L6.29 16.3 L9.97 19.58 L13.17 22.24Z"/><path d="M11.69 5.59 L11.92 5.67 L11.92 19.04 L11.69 19.12 L7.07 14.82 L4.57 14.5 L4.42 14.35 L4.42 10.51 L4.57 10.36 L7.07 10.28Z"/></g>`,
|
|
894
|
-
soundOff: `<g fill="currentColor" fill-rule="evenodd"><path d="M13.68 13.18 L12.53 14.4 L12.53 18.44 L12.3 18.51 L10.4 16.76 L9.33 17.83 L12.84 20.88 L13.75 21.56 L13.75 13.18Z"/><path d="M17.71 8.69 L17.49 8.91 L17.56 9.9 L17.64 9.98 L17.64 13.1 L17.33 14.7 L17.03 15.39 L18.78 12.88 L18.78 11.66 L18.86 11.58 L18.78 10.29Z"/><path d="M19.47 6.63 L19.85 7.47 L20.46 9.6 L20.53 10.13 L20.53 13.03 L20.08 15.01 L19.31 16.91 L20.61 14.93 L21.3 13.49 L21.52 13.18 L21.52 9.9 L20.76 8.53Z"/><path d="M20.91 2.21 L16.5 6.63 L13.98 9.3 L13.75 9.22 L13.75 1.6 L10.86 4.19 L7.05 7.85 L2.4 7.92 L2.48 8 L2.48 15.16 L6.51 15.7 L7.73 14.32 L5.3 14.02 L5.22 13.94 L5.22 10.13 L5.75 9.98 L7.81 9.9 L12.3 5.33 L12.53 5.41 L12.53 10.9 L10.32 13.26 L6.9 17.14 L2.55 22.4 L6.9 18.36 L9.79 15.47 L13.52 11.5 L18.25 5.71Z"/></g>`,
|
|
895
|
-
close: `<g fill="currentColor" fill-rule="evenodd"><path d="M22.16 1.6 L21.3 2.09 L20.44 2.82 L16.89 5.27 L12.12 10.29 L10.53 8.82 L7.35 5.39 L2.21 2.09 L5.88 7.23 L8.21 9.55 L10.41 12 L5.76 16.89 L2.33 21.54 L1.84 22.4 L2.58 22.03 L2.95 21.67 L4.17 20.93 L5.88 19.59 L7.23 18.73 L12 13.71 L12.12 13.71 L17.02 18.73 L22.16 22.16 L22.16 21.91 L20.2 19.46 L18.48 17.02 L13.71 12.12 L13.71 11.88 L18.48 6.86 L20.32 4.17 L21.67 2.46Z"/></g>`,
|
|
896
|
-
back: `<path d="M15 6l-6 6 6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
897
|
-
chevronRight: `<path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
898
|
-
star: `<path d="M12 3l2.6 5.6 6.1.7-4.5 4.2 1.2 6L12 16.9 6.6 19.5l1.2-6L3.3 9.3l6.1-.7z" fill="currentColor"/>`,
|
|
899
|
-
// volatility bolt (buy-bonus cards) — supplied art, scaled from its 1254 viewBox into 24×24
|
|
900
|
-
lightning: `<path transform="scale(0.019139)" d="M747,205L433,629L622,633L497,986L801,550L614,547Z" fill="currentColor"/>`,
|
|
901
|
-
};
|
|
902
|
-
/** Inline SVG string for an icon, sized to 1em (scale via font-size/width). */
|
|
903
|
-
function icon(name) {
|
|
904
|
-
return `<svg viewBox="0 0 24 24" width="1em" height="1em" aria-hidden="true">${SVGS[name]}</svg>`;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/** Render a (possibly socialised) two-word label across two lines — the BUY BONUS badge.
|
|
908
|
-
* Shared so the bottom-bar button and the Game-info control legend break identically. */
|
|
909
|
-
function twoLine(label) {
|
|
910
|
-
return label.split(/\s+/).join('<br>');
|
|
911
|
-
}
|
|
912
|
-
/** A centred CARD modal — frosted backdrop + opaque card with an accent title heading and an
|
|
913
|
-
* overlay ✕ in the top-right. The shared chrome for every centred modal (buy-bonus confirm,
|
|
914
|
-
* bet, autoplay, generic openModal). Append content to `body`; append full-bleed footer
|
|
915
|
-
* button(s) directly to `card`. Closes only via the ✕ / footer buttons — the backdrop does NOT. */
|
|
916
|
-
function createCardModal(opts) {
|
|
917
|
-
const root = document.createElement('div');
|
|
918
|
-
root.className = 'ge-sheet';
|
|
919
|
-
root.dataset.ge = opts.ge;
|
|
920
|
-
if (opts.blur != null)
|
|
921
|
-
root.style.setProperty('--ge-sheet-blur', `${opts.blur}px`);
|
|
922
|
-
const card = document.createElement('div');
|
|
923
|
-
card.className = 'ge-modal-card';
|
|
924
|
-
if (opts.accent)
|
|
925
|
-
card.style.setProperty('--card-acc', opts.accent);
|
|
926
|
-
const body = document.createElement('div');
|
|
927
|
-
body.className = 'ge-modal-body';
|
|
928
|
-
const h = document.createElement('h4');
|
|
929
|
-
h.className = 'ge-modal-title';
|
|
930
|
-
h.textContent = opts.title;
|
|
931
|
-
body.appendChild(h);
|
|
932
|
-
card.appendChild(body);
|
|
933
|
-
root.appendChild(card);
|
|
934
|
-
// ✕ lives on the overlay itself (top-right of the screen), not on the card
|
|
935
|
-
if (opts.closable !== false) {
|
|
936
|
-
const close = document.createElement('button');
|
|
937
|
-
close.className = 'ge-modal-close';
|
|
938
|
-
close.dataset.ge = 'modal-close';
|
|
939
|
-
close.setAttribute('aria-label', 'Close');
|
|
940
|
-
close.innerHTML = icon('close');
|
|
941
|
-
close.addEventListener('click', opts.onClose);
|
|
942
|
-
root.appendChild(close);
|
|
943
|
-
}
|
|
944
|
-
return { root, card, body };
|
|
945
|
-
}
|
|
946
|
-
/** Full-screen overlay. Returns { root, body, scroll }; append content to body.
|
|
947
|
-
* The `scroll` element is the scrollable container (overflow-y: auto). */
|
|
948
|
-
function createOverlay(opts) {
|
|
949
|
-
const root = document.createElement('div');
|
|
950
|
-
root.className = 'ge-shell-overlay';
|
|
951
|
-
const head = document.createElement('div');
|
|
952
|
-
head.className = 'ge-ov-head';
|
|
953
|
-
if (opts.onBack) {
|
|
954
|
-
const back = document.createElement('button');
|
|
955
|
-
back.className = 'ge-ov-nav';
|
|
956
|
-
back.dataset.ge = 'info-back';
|
|
957
|
-
back.innerHTML = icon('back');
|
|
958
|
-
back.addEventListener('click', opts.onBack);
|
|
959
|
-
head.appendChild(back);
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
// reserve a slot equal to the close button so the title stays centred
|
|
963
|
-
const spacer = document.createElement('div');
|
|
964
|
-
spacer.className = 'ge-ov-spacer';
|
|
965
|
-
head.appendChild(spacer);
|
|
966
|
-
}
|
|
967
|
-
const h = document.createElement('h4');
|
|
968
|
-
h.className = 'ge-ov-title';
|
|
969
|
-
h.textContent = opts.title;
|
|
970
|
-
head.appendChild(h);
|
|
971
|
-
const close = document.createElement('button');
|
|
972
|
-
close.className = 'ge-ov-nav';
|
|
973
|
-
close.setAttribute('aria-label', 'Close');
|
|
974
|
-
close.innerHTML = icon('close');
|
|
975
|
-
close.addEventListener('click', opts.onClose);
|
|
976
|
-
head.appendChild(close);
|
|
977
|
-
// Header stays fixed; only this wrapper scrolls — the X never scrolls away,
|
|
978
|
-
// and vh-clamped padding keeps it usable on small popouts (e.g. 400×225).
|
|
979
|
-
const scroll = document.createElement('div');
|
|
980
|
-
scroll.className = 'ge-ov-scroll';
|
|
981
|
-
const body = document.createElement('div');
|
|
982
|
-
body.className = 'ge-ov-body';
|
|
983
|
-
scroll.appendChild(body);
|
|
984
|
-
root.append(head, scroll);
|
|
985
|
-
return { root, body, scroll };
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
/** A floating labelled money readout (balance/win/bet). */
|
|
989
|
-
function readout(ge, label, value) {
|
|
990
|
-
const el = document.createElement('div');
|
|
991
|
-
el.dataset.ge = ge;
|
|
992
|
-
el.className = `ge-rd ge-${ge}`;
|
|
993
|
-
el.innerHTML = `<span class="ge-lbl">${label}</span>`;
|
|
994
|
-
el.append(document.createTextNode(value));
|
|
995
|
-
return el;
|
|
996
|
-
}
|
|
997
|
-
// Resting icon is turbo1 (1 line, grey via .ge-iconbtn); engaging turbo adds the
|
|
998
|
-
// .ge-active class which paints it white. Higher levels add more speed lines.
|
|
999
|
-
// level: 0 → turbo1 (grey), 1 → turbo1 (white), 2 → turbo2, 3 → turbo3.
|
|
1000
|
-
function turboIcon(level) {
|
|
1001
|
-
return ['turbo1', 'turbo1', 'turbo2', 'turbo3'][Math.max(0, Math.min(3, level))];
|
|
1002
|
-
}
|
|
1003
|
-
/** A borderless icon button. */
|
|
1004
|
-
function iconBtn(ge, name, onClick, active = false) {
|
|
1005
|
-
const b = document.createElement('button');
|
|
1006
|
-
b.className = `ge-iconbtn${active ? ' ge-active' : ''}`;
|
|
1007
|
-
b.dataset.ge = ge;
|
|
1008
|
-
b.innerHTML = icon(name);
|
|
1009
|
-
b.addEventListener('click', () => { if (!b.disabled)
|
|
1010
|
-
onClick(); });
|
|
1011
|
-
return b;
|
|
1012
|
-
}
|
|
1013
|
-
function renderBottomBar(shell) {
|
|
1014
|
-
const { state, config } = shell;
|
|
1015
|
-
const fmt = (n) => formatCurrency(n, config.currency);
|
|
1016
|
-
const fmtWin = (n) => formatCurrency(n, config.currency, true); // win / total-win: variable decimals
|
|
1017
|
-
const mobile = shell.layout === 'mobile';
|
|
1018
|
-
const bar = document.createElement('div');
|
|
1019
|
-
bar.className = 'ge-shell-bottom';
|
|
1020
|
-
bar.dataset.geMode = state.mode;
|
|
1021
|
-
// menu icon button (always)
|
|
1022
|
-
const menu = iconBtn('menu', 'menu', () => shell.openMenu());
|
|
1023
|
-
// All three modes share the base plaque layout. FS/replay hide the controls that don't apply
|
|
1024
|
-
// and add Free Spins + Total Win blocks on the left; the per-spin WIN uses the base pill.
|
|
1025
|
-
const isBase = state.mode === 'base';
|
|
1026
|
-
const isFS = state.mode === 'freeSpins';
|
|
1027
|
-
// FS always shows the spins counter + accumulated Total Win (even €0); a replay shows them
|
|
1028
|
-
// only when it's a free-spins replay (freeSpins.total > 0).
|
|
1029
|
-
const showFsBlocks = isFS || (state.mode === 'replay' && state.freeSpins.total > 0);
|
|
1030
|
-
// Replay is a read-only historical round — there's no real balance to show, so hide it. Keyed on
|
|
1031
|
-
// the sticky `replay` flag (not `mode`) so it stays hidden through a replay's free-spins phase.
|
|
1032
|
-
const balance = state.replay
|
|
1033
|
-
? null
|
|
1034
|
-
: readout('balance', shell.t('Balance'), fmt(state.balance));
|
|
1035
|
-
// With a feature active (e.g. Ante) the BET readout shows the effective stake, tinted with
|
|
1036
|
-
// the feature accent; the base state.bet is unchanged and returns once the feature is off.
|
|
1037
|
-
const feature = state.activeFeature;
|
|
1038
|
-
const betShown = feature ? state.bet * feature.priceMultiplier : state.bet;
|
|
1039
|
-
const betValue = readout('bet-value', shell.t('Bet'), fmt(betShown));
|
|
1040
|
-
if (feature) {
|
|
1041
|
-
const accent = effectiveAccent(feature);
|
|
1042
|
-
betValue.classList.add('ge-bet-feature');
|
|
1043
|
-
betValue.style.color = accent;
|
|
1044
|
-
// tint the "BET" label too (its .ge-lbl colour is set in CSS, so override inline)
|
|
1045
|
-
const lbl = betValue.querySelector('.ge-lbl');
|
|
1046
|
-
if (lbl)
|
|
1047
|
-
lbl.style.color = accent;
|
|
1048
|
-
}
|
|
1049
|
-
const turbo = config.features.turbo > 0
|
|
1050
|
-
? iconBtn('turbo', turboIcon(state.turbo), () => onTurbo(shell), state.turbo > 0) : null;
|
|
1051
|
-
// interactive controls — base mode only
|
|
1052
|
-
let betDown = null, betUp = null;
|
|
1053
|
-
let spin = null, auto = null, buy = null;
|
|
1054
|
-
if (isBase) {
|
|
1055
|
-
betDown = iconBtn('bet-down', 'minus', () => onBet(shell, -1));
|
|
1056
|
-
betUp = iconBtn('bet-up', 'plus', () => onBet(shell, 1));
|
|
1057
|
-
betValue.classList.add('ge-betbtn'); // tap the stake → bet picker
|
|
1058
|
-
betValue.addEventListener('click', () => { if (!betLocked(shell))
|
|
1059
|
-
shell.openBetPicker(); });
|
|
1060
|
-
spin = spinButton(shell);
|
|
1061
|
-
auto = config.features.autoplay ? autoButton(shell) : null;
|
|
1062
|
-
buy = (config.features.buyBonus !== false || config.onBonusBuy) ? buyBtn(shell) : null;
|
|
1063
|
-
}
|
|
1064
|
-
const winEl = state.win > 0 ? readout('win', shell.t('Win'), fmtWin(state.win)) : null;
|
|
1065
|
-
// FS/replay left blocks: spins counter + accumulated Total Win (shown even at €0).
|
|
1066
|
-
// current = number → "current / total"; current = null/undefined → just the (game-driven) total.
|
|
1067
|
-
const fs = state.freeSpins;
|
|
1068
|
-
const fsText = fs.current == null ? `${fs.total}` : `${fs.current} / ${fs.total}`;
|
|
1069
|
-
const fsCounter = showFsBlocks ? readout('fs-counter', shell.t('Free spins'), fsText) : null;
|
|
1070
|
-
const fsTotalWin = showFsBlocks ? readout('fs-totalwin', shell.t('Total win'), fmtWin(fs.totalWin)) : null;
|
|
1071
|
-
if (mobile) {
|
|
1072
|
-
// rows: [balance · win] · [menu · auto · spin · FS counter · Total Win · turbo · buy] · [− bet +]
|
|
1073
|
-
// FS counter + Total Win live in the controls row (alongside menu/turbo), not the top readouts.
|
|
1074
|
-
bar.appendChild(plaque('ge-m-top ge-pl ge-pl-glass', compact([balance, winEl])));
|
|
1075
|
-
const center = isBase ? spin : null;
|
|
1076
|
-
bar.appendChild(plaque('ge-m-controls ge-pl-dark', compact([menu, auto, center, fsCounter, fsTotalWin, turbo, buy])));
|
|
1077
|
-
bar.appendChild(plaque('ge-m-bet ge-pl ge-pl-dark', compact([betDown, betValue, betUp])));
|
|
1078
|
-
}
|
|
1079
|
-
else {
|
|
1080
|
-
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance] · [Free Spins] · [Total Win]
|
|
1081
|
-
// (the last two only render in FS / a free-spins replay)
|
|
1082
|
-
const menuPlaque = plaque('ge-pl ge-pl-dark ge-pl-menu', [menu]);
|
|
1083
|
-
const balPlaque = balance ? plaque('ge-pl ge-pl-glass ge-pl-bal', [balance]) : null;
|
|
1084
|
-
const fsPlaque = fsCounter ? plaque('ge-pl ge-pl-glass ge-pl-fs', [fsCounter]) : null;
|
|
1085
|
-
const totalWinPlaque = fsTotalWin ? plaque('ge-pl ge-pl-glass ge-pl-totalwin', [fsTotalWin]) : null;
|
|
1086
|
-
const left = zone('ge-zone-left ge-zone-plaques', ...compact([menuPlaque, buy, balPlaque, fsPlaque, totalWinPlaque]));
|
|
1087
|
-
// RIGHT: [bet (+ step)] · |divider| · [auto · SPIN · turbo]
|
|
1088
|
-
const betKids = [betValue];
|
|
1089
|
-
if (betUp && betDown) {
|
|
1090
|
-
const step = document.createElement('div');
|
|
1091
|
-
step.className = 'ge-betstep';
|
|
1092
|
-
step.append(betUp, betDown);
|
|
1093
|
-
betKids.push(step);
|
|
1094
|
-
}
|
|
1095
|
-
const betPlaque = plaque('ge-pl ge-pl-dark ge-pl-bet', betKids);
|
|
1096
|
-
const divider = document.createElement('div');
|
|
1097
|
-
divider.className = 'ge-pl-divider';
|
|
1098
|
-
const spinWrap = document.createElement('div');
|
|
1099
|
-
spinWrap.className = 'ge-spinwrap ge-pl-dark';
|
|
1100
|
-
spinWrap.append(...compact([auto, spin, turbo]));
|
|
1101
|
-
const right = zone('ge-zone-right ge-zone-plaques', betPlaque, divider, spinWrap);
|
|
1102
|
-
// MIDDLE: per-spin WIN pill in every mode — lifts above the bar on overflow.
|
|
1103
|
-
let middle = null;
|
|
1104
|
-
if (winEl) {
|
|
1105
|
-
winEl.classList.add('ge-winpill');
|
|
1106
|
-
middle = winEl;
|
|
1107
|
-
}
|
|
1108
|
-
bar.append(...compact([left, middle, right]));
|
|
1109
|
-
}
|
|
1110
|
-
applyBusy(shell, bar);
|
|
1111
|
-
return bar;
|
|
1112
|
-
}
|
|
1113
|
-
function zone(cls, ...children) {
|
|
1114
|
-
const z = document.createElement('div');
|
|
1115
|
-
z.className = `ge-zone ${cls}`;
|
|
1116
|
-
z.append(...children);
|
|
1117
|
-
return z;
|
|
1118
|
-
}
|
|
1119
|
-
/** A rounded background panel ("plaque") grouping a set of controls. */
|
|
1120
|
-
function plaque(cls, children) {
|
|
1121
|
-
const d = document.createElement('div');
|
|
1122
|
-
d.className = cls;
|
|
1123
|
-
d.append(...children);
|
|
1124
|
-
return d;
|
|
1125
|
-
}
|
|
1126
|
-
function compact(items) { return items.filter((x) => x !== null); }
|
|
1127
|
-
function buyBtn(shell) {
|
|
1128
|
-
const buy = document.createElement('button');
|
|
1129
|
-
buy.className = 'ge-shell-buybonus';
|
|
1130
|
-
buy.dataset.ge = 'buybonus';
|
|
1131
|
-
const feature = shell.state.activeFeature;
|
|
1132
|
-
if (feature) {
|
|
1133
|
-
// A feature is active → this button turns into DISABLE (tinted with the feature accent).
|
|
1134
|
-
const accent = effectiveAccent(feature);
|
|
1135
|
-
buy.classList.add('ge-disable');
|
|
1136
|
-
buy.innerHTML = `<span>${shell.t('DISABLE')}</span>`;
|
|
1137
|
-
buy.style.background = accent;
|
|
1138
|
-
buy.style.color = contrastText(accent);
|
|
1139
|
-
buy.addEventListener('click', () => { if (!buy.disabled)
|
|
1140
|
-
shell.deactivateFeature(); });
|
|
1141
|
-
}
|
|
1142
|
-
else {
|
|
1143
|
-
buy.innerHTML = `<span>${twoLine(shell.t('BUY BONUS'))}</span>`;
|
|
1144
|
-
buy.addEventListener('click', () => { if (!buy.disabled)
|
|
1145
|
-
shell.openBuyBonus(); });
|
|
1146
|
-
}
|
|
1147
|
-
return buy;
|
|
1148
|
-
}
|
|
1149
|
-
function onBet(shell, dir) {
|
|
1150
|
-
if (shell.state.busy)
|
|
1151
|
-
return;
|
|
1152
|
-
const next = stepBet(shell.state, dir);
|
|
1153
|
-
if (next !== shell.state.bet) {
|
|
1154
|
-
shell.state.bet = next;
|
|
1155
|
-
shell.emit('betChange', next);
|
|
1156
|
-
shell.render();
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
function onTurbo(shell) {
|
|
1160
|
-
const next = nextTurbo(shell.state.turbo, shell.config.features.turbo);
|
|
1161
|
-
shell.state.turbo = next;
|
|
1162
|
-
shell.emit('turboChange', next);
|
|
1163
|
-
shell.render();
|
|
1164
|
-
}
|
|
1165
|
-
function betLocked(shell) {
|
|
1166
|
-
return shell.state.busy || shell.state.autoplay.active;
|
|
1167
|
-
}
|
|
1168
|
-
/** SPIN disc — rotates while busy; becomes a STOP + countdown while autoplay runs. */
|
|
1169
|
-
function spinButton(shell) {
|
|
1170
|
-
const { state } = shell;
|
|
1171
|
-
const sp = document.createElement('button');
|
|
1172
|
-
sp.className = 'ge-shell-spin';
|
|
1173
|
-
sp.dataset.ge = 'spin';
|
|
1174
|
-
if (state.autoplay.active) {
|
|
1175
|
-
sp.classList.add('ge-stop');
|
|
1176
|
-
const rem = state.autoplay.remaining;
|
|
1177
|
-
const label = Number.isFinite(rem) ? String(rem) : '∞';
|
|
1178
|
-
sp.innerHTML = `<span class="ge-spin-stop">${icon('stop')}</span><span class="ge-spin-count">${label}</span>`;
|
|
1179
|
-
sp.addEventListener('click', () => { if (!sp.disabled)
|
|
1180
|
-
stopAutoplay(shell); });
|
|
1181
|
-
}
|
|
1182
|
-
else {
|
|
1183
|
-
sp.innerHTML = icon('spin');
|
|
1184
|
-
if (state.busy)
|
|
1185
|
-
sp.classList.add('ge-spinning');
|
|
1186
|
-
sp.addEventListener('click', () => { if (!sp.disabled)
|
|
1187
|
-
shell.emit('spin'); });
|
|
1188
|
-
}
|
|
1189
|
-
return sp;
|
|
1190
|
-
}
|
|
1191
|
-
/** Autoplay icon button — opens the count picker; glows accent while running. */
|
|
1192
|
-
function autoButton(shell) {
|
|
1193
|
-
const active = shell.state.autoplay.active;
|
|
1194
|
-
const b = iconBtn('autoplay', 'autoplay', () => onAutoplay(shell), active);
|
|
1195
|
-
if (active)
|
|
1196
|
-
b.classList.add('ge-glow');
|
|
1197
|
-
return b;
|
|
1198
|
-
}
|
|
1199
|
-
function onAutoplay(shell) {
|
|
1200
|
-
if (shell.state.autoplay.active)
|
|
1201
|
-
stopAutoplay(shell);
|
|
1202
|
-
else
|
|
1203
|
-
shell.openAutoplayPicker();
|
|
1204
|
-
}
|
|
1205
|
-
function stopAutoplay(shell) {
|
|
1206
|
-
shell.state.autoplay = { active: false, remaining: 0 };
|
|
1207
|
-
shell.emit('autoplayStop');
|
|
1208
|
-
shell.render();
|
|
1209
|
-
}
|
|
1210
|
-
function applyBusy(shell, bar) {
|
|
1211
|
-
const { busy } = shell.state;
|
|
1212
|
-
const auto = shell.state.autoplay.active;
|
|
1213
|
-
const lockBet = busy || auto;
|
|
1214
|
-
const disable = (ge, off) => {
|
|
1215
|
-
const el = bar.querySelector(`[data-ge="${ge}"]`);
|
|
1216
|
-
if (el)
|
|
1217
|
-
el.disabled = off;
|
|
1218
|
-
};
|
|
1219
|
-
// also disable the stepper that's already at the end of the bet range
|
|
1220
|
-
const i = shell.state.availableBets.indexOf(shell.state.bet);
|
|
1221
|
-
disable('bet-up', lockBet || i >= shell.state.availableBets.length - 1);
|
|
1222
|
-
disable('bet-down', lockBet || i <= 0);
|
|
1223
|
-
disable('spin', busy && !auto); // keep the STOP disc clickable through autoplay
|
|
1224
|
-
disable('autoplay', busy && !auto); // keep autoplay (stop) clickable through autoplay
|
|
1225
|
-
const betVal = bar.querySelector('[data-ge="bet-value"]');
|
|
1226
|
-
if (betVal)
|
|
1227
|
-
betVal.classList.toggle('ge-disabled', lockBet);
|
|
1228
|
-
const buy = bar.querySelector('[data-ge="buybonus"]');
|
|
1229
|
-
// disabled for the whole autoplay run (not just per-spin busy) so it doesn't flicker/pulse
|
|
1230
|
-
if (buy)
|
|
1231
|
-
buy.disabled = busy || auto || !shell.state.buyBonusEnabled;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
function openSettingsModal(shell) {
|
|
1235
|
-
const { root, body } = createOverlay({ title: shell.t('Settings'), onClose: () => root.remove() });
|
|
1236
|
-
root.dataset.ge = 'settings-modal';
|
|
1237
|
-
// Sound on/off — backed by the shell's shared `soundOn` state so this toggle and the Shift+M
|
|
1238
|
-
// hotkey stay in sync; `setSound` emits `settingChange({ key: 'sound' })` and refreshes the icon.
|
|
1239
|
-
const sound = (() => {
|
|
1240
|
-
const btn = document.createElement('button');
|
|
1241
|
-
btn.className = 'ge-snd';
|
|
1242
|
-
btn.dataset.ge = 'setting-sound';
|
|
1243
|
-
btn.setAttribute('aria-label', shell.t('Sound'));
|
|
1244
|
-
const paint = (on) => {
|
|
1245
|
-
btn.innerHTML = icon(on ? 'soundOn' : 'soundOff');
|
|
1246
|
-
btn.classList.toggle('ge-active', on);
|
|
1247
|
-
btn.setAttribute('aria-pressed', String(on));
|
|
1248
|
-
};
|
|
1249
|
-
paint(shell.soundOn);
|
|
1250
|
-
btn.addEventListener('click', () => shell.setSound(!shell.soundOn));
|
|
1251
|
-
// Live-update the icon when sound changes from here OR via Shift+M (shell clears on close).
|
|
1252
|
-
shell.setSoundRefresh(paint);
|
|
1253
|
-
const row = document.createElement('div');
|
|
1254
|
-
row.className = 'ge-ov-row';
|
|
1255
|
-
row.innerHTML = `<span class="ge-grow">${shell.t('Sound')}</span>`;
|
|
1256
|
-
row.appendChild(btn);
|
|
1257
|
-
return row;
|
|
1258
|
-
})();
|
|
1259
|
-
body.appendChild(sound);
|
|
1260
|
-
// Volume sliders — full-width column rows with a live value readout
|
|
1261
|
-
const slider = (key, label) => {
|
|
1262
|
-
const row = document.createElement('div');
|
|
1263
|
-
row.className = 'ge-ov-row ge-col';
|
|
1264
|
-
const head = document.createElement('div');
|
|
1265
|
-
head.className = 'ge-row-head';
|
|
1266
|
-
const val = document.createElement('span');
|
|
1267
|
-
val.className = 'ge-val';
|
|
1268
|
-
val.textContent = '100%';
|
|
1269
|
-
head.innerHTML = `<span>${label}</span>`;
|
|
1270
|
-
head.appendChild(val);
|
|
1271
|
-
const input = document.createElement('input');
|
|
1272
|
-
input.type = 'range';
|
|
1273
|
-
input.min = '0';
|
|
1274
|
-
input.max = '1';
|
|
1275
|
-
input.step = '0.05';
|
|
1276
|
-
input.value = '1';
|
|
1277
|
-
input.className = 'ge-slider';
|
|
1278
|
-
input.dataset.ge = `setting-${key}`;
|
|
1279
|
-
input.addEventListener('input', () => {
|
|
1280
|
-
val.textContent = `${Math.round(Number(input.value) * 100)}%`;
|
|
1281
|
-
shell.emit('settingChange', { key, value: Number(input.value) });
|
|
1282
|
-
});
|
|
1283
|
-
row.append(head, input);
|
|
1284
|
-
return row;
|
|
1285
|
-
};
|
|
1286
|
-
body.appendChild(slider('master', shell.t('Master volume')));
|
|
1287
|
-
body.appendChild(slider('music', shell.t('Music')));
|
|
1288
|
-
body.appendChild(slider('sfx', shell.t('SFX')));
|
|
1289
|
-
// Game info — full-width row button that opens its own overlay
|
|
1290
|
-
const gameInfo = document.createElement('button');
|
|
1291
|
-
gameInfo.className = 'ge-ov-row';
|
|
1292
|
-
gameInfo.dataset.ge = 'game-info-btn';
|
|
1293
|
-
gameInfo.style.marginTop = '6px';
|
|
1294
|
-
gameInfo.innerHTML = `<span style="width:22px;font-size:22px">${icon('info')}</span><span class="ge-grow">${shell.t('Game info')}</span><span style="width:20px;font-size:20px;color:var(--shell-muted)">${icon('chevronRight')}</span>`;
|
|
1295
|
-
gameInfo.addEventListener('click', () => { root.remove(); shell.openInfo(); });
|
|
1296
|
-
body.appendChild(gameInfo);
|
|
1297
|
-
return root;
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
// AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
|
|
1301
|
-
/** The @energy8platform/platform-core package version, stamped at build time. */
|
|
1302
|
-
const PACKAGE_VERSION = '0.26.1';
|
|
1303
|
-
|
|
1304
|
-
/** Default order key for the auto-injected hotkeys section: just after `controls` (-1). */
|
|
1305
|
-
const HOTKEYS_DEFAULT_ORDER = -0.5;
|
|
1306
|
-
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
1307
|
-
function openGameInfoModal(shell) {
|
|
1308
|
-
const { root, body, scroll } = createOverlay({
|
|
1309
|
-
title: shell.t('Game info'),
|
|
1310
|
-
onClose: () => root.remove(),
|
|
1311
|
-
onBack: () => { root.remove(); shell.openSettings(); },
|
|
1312
|
-
});
|
|
1313
|
-
root.dataset.ge = 'info-modal';
|
|
1314
|
-
const rawSections = shell.config.gameInfo.sections ?? [];
|
|
1315
|
-
// Auto-inject a hotkeys section unless the game already provides one or features.hotkeys === false.
|
|
1316
|
-
const sectionsWithHotkeys = [...rawSections];
|
|
1317
|
-
if (shell.config.features.hotkeys !== false && !rawSections.some((s) => s.type === 'hotkeys')) {
|
|
1318
|
-
sectionsWithHotkeys.push({ type: 'hotkeys', order: HOTKEYS_DEFAULT_ORDER });
|
|
1319
|
-
}
|
|
1320
|
-
const sections = sectionsWithHotkeys;
|
|
1321
|
-
// Default placement: modes first, controls second, the rest in declaration order.
|
|
1322
|
-
// An explicit `order` overrides; ties keep declaration order (stable).
|
|
1323
|
-
const base = (s, i) => s.order ?? (s.type === 'modes' ? -2 : s.type === 'controls' ? -1 : i);
|
|
1324
|
-
sections
|
|
1325
|
-
.map((s, i) => ({ s, i, k: base(s, i) }))
|
|
1326
|
-
.sort((a, b) => a.k - b.k || a.i - b.i)
|
|
1327
|
-
.forEach(({ s }) => body.appendChild(renderSection(shell, s)));
|
|
1328
|
-
body.appendChild(versionFooter(shell));
|
|
1329
|
-
const LINE = 60;
|
|
1330
|
-
const PAGE = () => Math.floor(scroll.clientHeight * 0.9) || Math.floor(540 * 0.9);
|
|
1331
|
-
const onKey = (e) => {
|
|
1332
|
-
switch (e.code) {
|
|
1333
|
-
case 'ArrowDown':
|
|
1334
|
-
scroll.scrollTop += LINE;
|
|
1335
|
-
return true;
|
|
1336
|
-
case 'ArrowUp':
|
|
1337
|
-
scroll.scrollTop = Math.max(0, scroll.scrollTop - LINE);
|
|
1338
|
-
return true;
|
|
1339
|
-
case 'PageDown':
|
|
1340
|
-
scroll.scrollTop += PAGE();
|
|
1341
|
-
return true;
|
|
1342
|
-
case 'PageUp':
|
|
1343
|
-
scroll.scrollTop = Math.max(0, scroll.scrollTop - PAGE());
|
|
1344
|
-
return true;
|
|
1345
|
-
case 'Space':
|
|
1346
|
-
if (e.shiftKey) {
|
|
1347
|
-
scroll.scrollTop = Math.max(0, scroll.scrollTop - PAGE());
|
|
1348
|
-
}
|
|
1349
|
-
else {
|
|
1350
|
-
scroll.scrollTop += PAGE();
|
|
1351
|
-
}
|
|
1352
|
-
return true;
|
|
1353
|
-
case 'Home':
|
|
1354
|
-
scroll.scrollTop = 0;
|
|
1355
|
-
return true;
|
|
1356
|
-
case 'End':
|
|
1357
|
-
scroll.scrollTop = scroll.scrollHeight - scroll.clientHeight;
|
|
1358
|
-
return true;
|
|
1359
|
-
default: return false;
|
|
1360
|
-
}
|
|
1361
|
-
};
|
|
1362
|
-
return { root, onKey };
|
|
1363
|
-
}
|
|
1364
|
-
/** A muted version stamp pinned to the bottom of the game-info modal:
|
|
1365
|
-
* `${config.version ?? '1.0.0'}.${engine version without dots}` (e.g. '1.0.0.0246'). */
|
|
1366
|
-
function versionFooter(shell) {
|
|
1367
|
-
const gameVersion = shell.config.version ?? '1.0.0';
|
|
1368
|
-
const el = document.createElement('div');
|
|
1369
|
-
el.dataset.ge = 'info-version';
|
|
1370
|
-
el.className = 'ge-gi-version';
|
|
1371
|
-
el.textContent = `${gameVersion}.${PACKAGE_VERSION.replaceAll('.', '')}`;
|
|
1372
|
-
return el;
|
|
1373
|
-
}
|
|
1374
|
-
function renderSection(shell, s) {
|
|
1375
|
-
switch (s.type) {
|
|
1376
|
-
case 'modes': return sectionModes(shell, s.modes, sec('info-modes', s.title, shell.t('Modes')));
|
|
1377
|
-
case 'controls': return sectionControls(shell, sec('info-controls', s.title, shell.t('Controls')));
|
|
1378
|
-
case 'hotkeys': return sectionHotkeys(shell, sec('info-hotkeys', s.title, shell.t('Hotkeys')));
|
|
1379
|
-
case 'paytable': return sectionPaytable(s.rows, sec('info-paytable', s.title, shell.t('Paytable')));
|
|
1380
|
-
case 'wins': return sectionWins(s, sec('info-wins', s.title, shell.t(winFallbackTitle(s.kind))));
|
|
1381
|
-
// Translate the heading (e.g. the host-built DISCLAIMER title); the body stays verbatim.
|
|
1382
|
-
case 'custom': return sectionCustom(s, sec('info-custom', s.title != null ? shell.t(s.title) : undefined, ''));
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
/** A titled glass-plaque section shell. */
|
|
1386
|
-
function sec(ge, title, fallback) {
|
|
1387
|
-
const el = document.createElement('section');
|
|
1388
|
-
el.dataset.ge = ge;
|
|
1389
|
-
el.className = 'ge-gi-sec';
|
|
1390
|
-
const t = title ?? fallback;
|
|
1391
|
-
if (t) {
|
|
1392
|
-
const h = document.createElement('h3');
|
|
1393
|
-
h.textContent = t;
|
|
1394
|
-
el.appendChild(h);
|
|
1395
|
-
}
|
|
1396
|
-
return el;
|
|
1397
|
-
}
|
|
1398
|
-
// ── modes (rows — varying description lengths read better than fixed cards) ────
|
|
1399
|
-
function sectionModes(shell, modes, el) {
|
|
1400
|
-
const list = document.createElement('div');
|
|
1401
|
-
list.className = 'ge-gi-modes';
|
|
1402
|
-
for (const m of modes)
|
|
1403
|
-
list.appendChild(modeRow(shell, m));
|
|
1404
|
-
el.appendChild(list);
|
|
1405
|
-
return el;
|
|
1406
|
-
}
|
|
1407
|
-
function modeRow(shell, m) {
|
|
1408
|
-
const row = document.createElement('div');
|
|
1409
|
-
row.className = 'ge-gi-mode';
|
|
1410
|
-
const stat = (label, val) => `<span class="ge-gi-mode-st"><span>${label}</span><b>${val}</b></span>`;
|
|
1411
|
-
let stats = '';
|
|
1412
|
-
if (m.price != null)
|
|
1413
|
-
stats += stat(shell.t('Price'), m.price);
|
|
1414
|
-
if (typeof m.rtp === 'number')
|
|
1415
|
-
stats += stat(shell.t('RTP'), `${m.rtp}%`);
|
|
1416
|
-
if (m.maxWin != null)
|
|
1417
|
-
stats += stat(shell.t('Max win'), m.maxWin);
|
|
1418
|
-
row.innerHTML =
|
|
1419
|
-
`<div class="ge-gi-mode-top"><span class="ge-gi-mode-h">${m.title}</span>` +
|
|
1420
|
-
(stats ? `<span class="ge-gi-mode-stats">${stats}</span>` : '') + '</div>' +
|
|
1421
|
-
(m.description ? `<p class="ge-gi-mode-desc">${m.description}</p>` : '');
|
|
1422
|
-
return row;
|
|
1423
|
-
}
|
|
1424
|
-
function sectionControls(shell, el) {
|
|
1425
|
-
const { features } = shell.config;
|
|
1426
|
-
const slot = (inner, cls = '') => `<span class="ge-gi-ctl-ic ${cls}">${inner}</span>`;
|
|
1427
|
-
const buyLabel = twoLine(shell.t('BUY BONUS'));
|
|
1428
|
-
const buyBadge = slot(`<span class="ge-shell-buybonus"><span>${buyLabel}</span></span>`);
|
|
1429
|
-
// Block 1 — gameplay. Bet is split into two rows: one to raise, one to lower.
|
|
1430
|
-
const game = [
|
|
1431
|
-
{ vis: slot(icon('spin')), name: 'Spin', desc: 'Start a spin at the current bet.', on: true },
|
|
1432
|
-
{ vis: slot(icon('plus')), name: 'Raise bet', desc: 'Increase your stake.', on: true },
|
|
1433
|
-
{ vis: slot(icon('minus')), name: 'Lower bet', desc: 'Decrease your stake.', on: true },
|
|
1434
|
-
{ vis: slot(icon('autoplay')), name: 'Autoplay', desc: 'Spin automatically a set number of times.', on: features.autoplay != null },
|
|
1435
|
-
{ vis: slot(icon('turbo1')), name: 'Turbo', desc: 'Speed up spin animations.', on: features.turbo > 0 },
|
|
1436
|
-
{ vis: buyBadge, name: 'Buy bonus', desc: 'Pay a fixed cost to enter a bonus feature.', on: features.buyBonus !== false },
|
|
1437
|
-
];
|
|
1438
|
-
// Block 2 — menu & overlay chrome (always available).
|
|
1439
|
-
const menu = [
|
|
1440
|
-
{ vis: slot(icon('menu')), name: 'Menu', desc: 'Open settings and game info.', on: true },
|
|
1441
|
-
{ vis: slot(icon('soundOn')), name: 'Sound', desc: 'Mute or unmute the game.', on: true },
|
|
1442
|
-
{ vis: slot(icon('info')), name: 'Game info', desc: 'Open the paytable and rules.', on: true },
|
|
1443
|
-
{ vis: slot(icon('close')), name: 'Close', desc: 'Dismiss the current overlay.', on: true },
|
|
1444
|
-
];
|
|
1445
|
-
el.appendChild(ctlBlock(shell, 'Game', game));
|
|
1446
|
-
el.appendChild(ctlBlock(shell, 'Menu & info', menu));
|
|
1447
|
-
return el;
|
|
1448
|
-
}
|
|
1449
|
-
function ctlBlock(shell, label, rows) {
|
|
1450
|
-
const block = document.createElement('div');
|
|
1451
|
-
block.className = 'ge-gi-ctl-block';
|
|
1452
|
-
const h = document.createElement('h4');
|
|
1453
|
-
h.className = 'ge-gi-ctl-block-h';
|
|
1454
|
-
h.textContent = shell.t(label);
|
|
1455
|
-
block.appendChild(h);
|
|
1456
|
-
for (const r of rows.filter((x) => x.on)) {
|
|
1457
|
-
const row = document.createElement('div');
|
|
1458
|
-
row.className = 'ge-gi-ctl';
|
|
1459
|
-
row.innerHTML = `${r.vis}<div class="ge-gi-ctl-tx"><b>${shell.t(r.name)}</b><span>${shell.t(r.desc)}</span></div>`;
|
|
1460
|
-
block.appendChild(row);
|
|
1461
|
-
}
|
|
1462
|
-
return block;
|
|
1463
|
-
}
|
|
1464
|
-
function sectionHotkeys(shell, el) {
|
|
1465
|
-
const { features } = shell.config;
|
|
1466
|
-
/** Render one or more key names as keycap chips joined by " / ". */
|
|
1467
|
-
const chips = (...keys) => keys.map((k) => `<span class="ge-gi-hk-chip">${k}</span>`).join('<span class="ge-gi-hk-sep"> / </span>');
|
|
1468
|
-
const rows = [
|
|
1469
|
-
{ chips: ['Space'], name: 'Spin', on: true },
|
|
1470
|
-
{ chips: ['Shift', '↑', 'Shift', '='], name: 'Raise bet', on: true },
|
|
1471
|
-
{ chips: ['Shift', '↓', 'Shift', '-'], name: 'Lower bet', on: true },
|
|
1472
|
-
{ chips: ['Shift', 'A'], name: 'Autoplay', on: features.autoplay != null },
|
|
1473
|
-
{ chips: ['Shift', 'T'], name: 'Turbo', on: features.turbo > 0 },
|
|
1474
|
-
{ chips: ['Shift', 'B'], name: 'Buy bonus', on: features.buyBonus !== false },
|
|
1475
|
-
{ chips: ['Shift', 'I'], name: 'Game info', on: true },
|
|
1476
|
-
{ chips: ['Shift', 'S'], name: 'Menu', on: true },
|
|
1477
|
-
{ chips: ['Shift', 'M'], name: 'Mute', on: true },
|
|
1478
|
-
{ chips: ['←', '→'], name: 'Navigate', on: true },
|
|
1479
|
-
{ chips: ['Enter'], name: 'Confirm', on: true },
|
|
1480
|
-
{ chips: ['Esc'], name: 'Close', on: true },
|
|
1481
|
-
];
|
|
1482
|
-
const block = document.createElement('div');
|
|
1483
|
-
block.className = 'ge-gi-hk-block';
|
|
1484
|
-
for (const r of rows.filter((x) => x.on)) {
|
|
1485
|
-
const row = document.createElement('div');
|
|
1486
|
-
row.className = 'ge-gi-hk';
|
|
1487
|
-
// Build the chips column
|
|
1488
|
-
const chipsEl = document.createElement('div');
|
|
1489
|
-
chipsEl.className = 'ge-gi-hk-chips';
|
|
1490
|
-
if (r.name === 'Raise bet' || r.name === 'Lower bet') {
|
|
1491
|
-
// Two combos separated by " / ": Shift+↑ / Shift+= and Shift+↓ / Shift+-
|
|
1492
|
-
const [k1, k2, k3, k4] = r.chips;
|
|
1493
|
-
chipsEl.innerHTML =
|
|
1494
|
-
`<span class="ge-gi-hk-combo">${chips(k1, k2)}</span>` +
|
|
1495
|
-
`<span class="ge-gi-hk-sep2"> / </span>` +
|
|
1496
|
-
`<span class="ge-gi-hk-combo">${chips(k3, k4)}</span>`;
|
|
1497
|
-
}
|
|
1498
|
-
else if (r.chips.length > 1) {
|
|
1499
|
-
// Chord: Shift + X
|
|
1500
|
-
chipsEl.innerHTML = `<span class="ge-gi-hk-combo">${chips(...r.chips)}</span>`;
|
|
1501
|
-
}
|
|
1502
|
-
else {
|
|
1503
|
-
chipsEl.innerHTML = chips(...r.chips);
|
|
1504
|
-
}
|
|
1505
|
-
const tx = document.createElement('div');
|
|
1506
|
-
tx.className = 'ge-gi-hk-tx';
|
|
1507
|
-
tx.textContent = shell.t(r.name);
|
|
1508
|
-
row.appendChild(chipsEl);
|
|
1509
|
-
row.appendChild(tx);
|
|
1510
|
-
block.appendChild(row);
|
|
1511
|
-
}
|
|
1512
|
-
el.appendChild(block);
|
|
1513
|
-
return el;
|
|
1514
|
-
}
|
|
1515
|
-
// ── paytable (cards — image on top, name, then win tiers "<count> x<mult>") ────
|
|
1516
|
-
function sectionPaytable(rows, el) {
|
|
1517
|
-
const grid = document.createElement('div');
|
|
1518
|
-
grid.className = 'ge-gi-pt-grid';
|
|
1519
|
-
for (const r of rows)
|
|
1520
|
-
grid.appendChild(paytableCard(r));
|
|
1521
|
-
el.appendChild(grid);
|
|
1522
|
-
return el;
|
|
1523
|
-
}
|
|
1524
|
-
function paytableCard(r) {
|
|
1525
|
-
const card = document.createElement('div');
|
|
1526
|
-
card.className = 'ge-gi-pt-card';
|
|
1527
|
-
const sym = document.createElement('div');
|
|
1528
|
-
sym.className = 'ge-gi-pt-sym';
|
|
1529
|
-
if (r.symbol.image) {
|
|
1530
|
-
const img = document.createElement('img');
|
|
1531
|
-
img.src = r.symbol.image;
|
|
1532
|
-
img.alt = r.symbol.text ?? '';
|
|
1533
|
-
sym.appendChild(img);
|
|
1534
|
-
}
|
|
1535
|
-
if (r.symbol.text) {
|
|
1536
|
-
const t = document.createElement('span');
|
|
1537
|
-
t.textContent = r.symbol.text;
|
|
1538
|
-
sym.appendChild(t);
|
|
1539
|
-
}
|
|
1540
|
-
const wins = document.createElement('div');
|
|
1541
|
-
wins.className = 'ge-gi-pt-wins';
|
|
1542
|
-
for (const w of r.wins) {
|
|
1543
|
-
const wi = document.createElement('span');
|
|
1544
|
-
wi.className = 'ge-gi-pt-win';
|
|
1545
|
-
wi.innerHTML = (w.count ? `<i>${w.count}</i> ` : '') + `<b>x${w.multiplier}</b>`;
|
|
1546
|
-
wins.appendChild(wi);
|
|
1547
|
-
}
|
|
1548
|
-
card.append(sym, wins);
|
|
1549
|
-
return card;
|
|
1550
|
-
}
|
|
1551
|
-
// ── wins (one section = one pay type; cells filled in the accent colour, no line) ──
|
|
1552
|
-
function winFallbackTitle(kind) {
|
|
1553
|
-
return { classic: 'Paylines', cluster: 'Cluster pays', anywhere: 'Pays anywhere', ways: 'Ways to win', shapes: 'Winning shapes' }[kind];
|
|
1554
|
-
}
|
|
1555
|
-
function sectionWins(s, el) {
|
|
1556
|
-
if (s.kind === 'classic') {
|
|
1557
|
-
if (s.description)
|
|
1558
|
-
el.appendChild(winDesc(s.description));
|
|
1559
|
-
const wrap = document.createElement('div');
|
|
1560
|
-
wrap.className = 'ge-gi-pl-grid';
|
|
1561
|
-
s.lines.forEach((line, i) => {
|
|
1562
|
-
const def = Array.isArray(line) ? { pattern: line } : line;
|
|
1563
|
-
wrap.appendChild(lineItem(s.grid, def, i + 1));
|
|
1564
|
-
});
|
|
1565
|
-
el.appendChild(wrap);
|
|
1566
|
-
}
|
|
1567
|
-
else if (s.kind === 'cluster' || s.kind === 'anywhere') {
|
|
1568
|
-
badge(el, `min ${s.minCount}`);
|
|
1569
|
-
const row = document.createElement('div');
|
|
1570
|
-
row.className = 'ge-gi-win-row';
|
|
1571
|
-
const example = s.example ?? (s.kind === 'cluster' ? clusterExample(s.grid, s.minCount) : anywhereExample(s.grid, s.minCount));
|
|
1572
|
-
row.appendChild(gridSvg(s.grid, example));
|
|
1573
|
-
if (s.description)
|
|
1574
|
-
row.appendChild(winDesc(s.description));
|
|
1575
|
-
el.appendChild(row);
|
|
1576
|
-
}
|
|
1577
|
-
else if (s.kind === 'shapes') {
|
|
1578
|
-
if (s.description)
|
|
1579
|
-
el.appendChild(winDesc(s.description));
|
|
1580
|
-
const list = document.createElement('div');
|
|
1581
|
-
list.className = 'ge-gi-shapes';
|
|
1582
|
-
for (const sh of s.shapes)
|
|
1583
|
-
list.appendChild(shapeRow(s.grid, sh));
|
|
1584
|
-
el.appendChild(list);
|
|
1585
|
-
}
|
|
1586
|
-
else {
|
|
1587
|
-
if (s.description)
|
|
1588
|
-
el.appendChild(winDesc(s.description));
|
|
1589
|
-
const two = document.createElement('div');
|
|
1590
|
-
two.className = 'ge-gi-win-two';
|
|
1591
|
-
two.append(waysCol('✓ wins', 'ge-gi-win-ok', s.grid, s.winExample ?? waysWin(s.grid)), waysCol('✗ no win', 'ge-gi-win-no', s.grid, s.loseExample ?? waysLose(s.grid)));
|
|
1592
|
-
el.appendChild(two);
|
|
1593
|
-
}
|
|
1594
|
-
return el;
|
|
1595
|
-
}
|
|
1596
|
-
/** One named shape: grid illustration (left) + name and optional description (right) — modes-style. */
|
|
1597
|
-
function shapeRow(grid, sh) {
|
|
1598
|
-
const row = document.createElement('div');
|
|
1599
|
-
row.className = 'ge-gi-shape';
|
|
1600
|
-
const tx = document.createElement('div');
|
|
1601
|
-
tx.className = 'ge-gi-shape-tx';
|
|
1602
|
-
const h = document.createElement('b');
|
|
1603
|
-
h.className = 'ge-gi-mode-h';
|
|
1604
|
-
h.textContent = sh.name;
|
|
1605
|
-
tx.appendChild(h);
|
|
1606
|
-
if (sh.description) {
|
|
1607
|
-
const p = document.createElement('p');
|
|
1608
|
-
p.className = 'ge-gi-mode-desc';
|
|
1609
|
-
p.textContent = sh.description;
|
|
1610
|
-
tx.appendChild(p);
|
|
1611
|
-
}
|
|
1612
|
-
row.append(gridSvg(grid, sh.cells), tx);
|
|
1613
|
-
return row;
|
|
1614
|
-
}
|
|
1615
|
-
function winDesc(text) {
|
|
1616
|
-
const p = document.createElement('p');
|
|
1617
|
-
p.className = 'ge-gi-win-desc';
|
|
1618
|
-
p.textContent = text;
|
|
1619
|
-
return p;
|
|
1620
|
-
}
|
|
1621
|
-
/** Append a "min N" pill to the section header. */
|
|
1622
|
-
function badge(el, text) {
|
|
1623
|
-
const h = el.querySelector('h3');
|
|
1624
|
-
if (!h)
|
|
1625
|
-
return;
|
|
1626
|
-
const b = document.createElement('span');
|
|
1627
|
-
b.className = 'ge-gi-win-badge';
|
|
1628
|
-
b.textContent = text;
|
|
1629
|
-
h.appendChild(b);
|
|
1630
|
-
}
|
|
1631
|
-
/** A cols×rows grid SVG; `on` cells are filled in the accent colour, the rest are faint. */
|
|
1632
|
-
function gridSvg(grid, on) {
|
|
1633
|
-
const { cols, rows } = grid;
|
|
1634
|
-
const W = 100, H = Math.round((rows / cols) * 100);
|
|
1635
|
-
const cw = W / cols, ch = H / rows;
|
|
1636
|
-
const svg = document.createElementNS(SVG_NS, 'svg');
|
|
1637
|
-
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
|
|
1638
|
-
svg.setAttribute('class', 'ge-gi-pl-svg');
|
|
1639
|
-
const onSet = new Set(on.map(([c, r]) => `${c},${r}`));
|
|
1640
|
-
for (let y = 0; y < rows; y++)
|
|
1641
|
-
for (let x = 0; x < cols; x++) {
|
|
1642
|
-
const r = document.createElementNS(SVG_NS, 'rect');
|
|
1643
|
-
r.setAttribute('x', String(x * cw + 1));
|
|
1644
|
-
r.setAttribute('y', String(y * ch + 1));
|
|
1645
|
-
r.setAttribute('width', String(cw - 2));
|
|
1646
|
-
r.setAttribute('height', String(ch - 2));
|
|
1647
|
-
r.setAttribute('rx', '2');
|
|
1648
|
-
r.setAttribute('class', onSet.has(`${x},${y}`) ? 'ge-gi-pl-on' : 'ge-gi-pl-cell');
|
|
1649
|
-
svg.appendChild(r);
|
|
1650
|
-
}
|
|
1651
|
-
return svg;
|
|
1652
|
-
}
|
|
1653
|
-
/** A classic payline: number caption on top, filled cells (no connecting line). */
|
|
1654
|
-
function lineItem(grid, def, n) {
|
|
1655
|
-
const item = document.createElement('div');
|
|
1656
|
-
item.className = 'ge-gi-pl-item';
|
|
1657
|
-
const cap = document.createElement('span');
|
|
1658
|
-
cap.className = 'ge-gi-pl-cap';
|
|
1659
|
-
cap.textContent = String(n);
|
|
1660
|
-
const on = def.pattern.map((rowIdx, col) => [col, rowIdx]);
|
|
1661
|
-
item.append(cap, gridSvg(grid, on)); // caption first → renders above the grid
|
|
1662
|
-
return item;
|
|
1663
|
-
}
|
|
1664
|
-
function waysCol(tag, tagCls, grid, cells) {
|
|
1665
|
-
const col = document.createElement('div');
|
|
1666
|
-
col.className = 'ge-gi-win-col';
|
|
1667
|
-
const t = document.createElement('span');
|
|
1668
|
-
t.className = `ge-gi-win-tag ${tagCls}`;
|
|
1669
|
-
t.textContent = tag;
|
|
1670
|
-
col.append(t, gridSvg(grid, cells));
|
|
1671
|
-
return col;
|
|
1672
|
-
}
|
|
1673
|
-
// Default illustrations (used when the section omits an explicit example).
|
|
1674
|
-
function clusterExample(grid, n) {
|
|
1675
|
-
const w = Math.min(grid.cols, Math.max(1, Math.ceil(Math.sqrt(n))));
|
|
1676
|
-
const cells = [];
|
|
1677
|
-
for (let y = 0; y < grid.rows && cells.length < n; y++)
|
|
1678
|
-
for (let x = 0; x < w && cells.length < n; x++)
|
|
1679
|
-
cells.push([x, y]);
|
|
1680
|
-
return cells;
|
|
1681
|
-
}
|
|
1682
|
-
function anywhereExample(grid, n) {
|
|
1683
|
-
const count = Math.min(n, grid.cols * grid.rows);
|
|
1684
|
-
const cells = [];
|
|
1685
|
-
for (let i = 0; i < count; i++)
|
|
1686
|
-
cells.push([Math.floor((i * grid.cols) / count), (i * 2 + 1) % grid.rows]);
|
|
1687
|
-
return cells;
|
|
1688
|
-
}
|
|
1689
|
-
function waysWin(grid) {
|
|
1690
|
-
const cells = [];
|
|
1691
|
-
for (let c = 0; c < grid.cols; c++)
|
|
1692
|
-
cells.push([c, c % grid.rows]); // one symbol on every reel
|
|
1693
|
-
return cells;
|
|
1694
|
-
}
|
|
1695
|
-
function waysLose(grid) {
|
|
1696
|
-
const gap = Math.floor(grid.cols / 2);
|
|
1697
|
-
return waysWin(grid).filter(([c]) => c !== gap); // a broken chain (reel `gap` empty)
|
|
1698
|
-
}
|
|
1699
|
-
// ── custom ───────────────────────────────────────────────────────────────────
|
|
1700
|
-
function sectionCustom(s, el) {
|
|
1701
|
-
if (s.node) {
|
|
1702
|
-
el.appendChild(s.node);
|
|
1703
|
-
}
|
|
1704
|
-
else if (s.html) {
|
|
1705
|
-
const d = document.createElement('div');
|
|
1706
|
-
d.className = 'ge-gi-custom';
|
|
1707
|
-
d.innerHTML = s.html;
|
|
1708
|
-
el.appendChild(d);
|
|
1709
|
-
}
|
|
1710
|
-
return el;
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
/** Buy-bonus overlay — a grid of art-forward cards, one per option.
|
|
1714
|
-
* Returns the overlay element + a keyboard handler for the shell's `showModal`. */
|
|
1715
|
-
function openBuyBonusOverlay(shell) {
|
|
1716
|
-
const bonuses = shell.config.features.buyBonus;
|
|
1717
|
-
if (bonuses === false || bonuses.length === 0)
|
|
1718
|
-
return null;
|
|
1719
|
-
const st = { focusIndex: -1, confirmBonus: undefined };
|
|
1720
|
-
const { root, body } = createOverlay({ title: shell.t('Buy bonus'), onClose: () => shell.closeModal() });
|
|
1721
|
-
root.dataset.ge = 'buybonus-overlay';
|
|
1722
|
-
// Re-render the grid whenever the bet changes so every card's price stays live.
|
|
1723
|
-
const renderGrid = () => {
|
|
1724
|
-
body.innerHTML = '';
|
|
1725
|
-
const grid = document.createElement('div');
|
|
1726
|
-
grid.className = 'ge-bb-grid';
|
|
1727
|
-
// Card count drives the width-fit clamp in CSS (each card is 18em; N cards must fit the frame
|
|
1728
|
-
// width), so the row scales to the available width instead of overflowing into an X-scroll.
|
|
1729
|
-
grid.style.setProperty('--ge-bb-n', String(bonuses.length));
|
|
1730
|
-
const affordable = [];
|
|
1731
|
-
for (const bonus of bonuses) {
|
|
1732
|
-
const card = buildCard(shell, bonus, root, st);
|
|
1733
|
-
grid.appendChild(card);
|
|
1734
|
-
if (isAffordable(shell, bonus))
|
|
1735
|
-
affordable.push(bonus);
|
|
1736
|
-
}
|
|
1737
|
-
body.appendChild(grid);
|
|
1738
|
-
// Initialize or restore focus index
|
|
1739
|
-
if (affordable.length > 0) {
|
|
1740
|
-
if (st.focusIndex < 0)
|
|
1741
|
-
st.focusIndex = 0;
|
|
1742
|
-
else
|
|
1743
|
-
st.focusIndex = Math.min(st.focusIndex, affordable.length - 1);
|
|
1744
|
-
applyFocusClass(root, bonuses, affordable, st.focusIndex);
|
|
1745
|
-
}
|
|
1746
|
-
else {
|
|
1747
|
-
st.focusIndex = -1;
|
|
1748
|
-
}
|
|
1749
|
-
};
|
|
1750
|
-
renderGrid();
|
|
1751
|
-
root.appendChild(buildBetBar(shell, renderGrid)); // thin bottom footer, only as tall as the pill
|
|
1752
|
-
/** Step the bet by `dir` and re-render the grid (live prices + affordability) when it changed.
|
|
1753
|
-
* Shared by the keyboard bet keys (the footer ± buttons keep their own copy). */
|
|
1754
|
-
const stepBetBy = (dir) => {
|
|
1755
|
-
const next = stepBet(shell.state, dir);
|
|
1756
|
-
if (next === shell.state.bet)
|
|
1757
|
-
return;
|
|
1758
|
-
shell.state.bet = next;
|
|
1759
|
-
shell.emit('betChange', next);
|
|
1760
|
-
shell.render();
|
|
1761
|
-
renderGrid();
|
|
1762
|
-
};
|
|
1763
|
-
/** Keyboard handler for both browse and confirm phases. */
|
|
1764
|
-
const onKey = (e) => {
|
|
1765
|
-
const affordable = bonuses.filter((b) => isAffordable(shell, b));
|
|
1766
|
-
// ── Confirm phase ──
|
|
1767
|
-
if (st.confirmBonus) {
|
|
1768
|
-
switch (e.code) {
|
|
1769
|
-
case 'Enter':
|
|
1770
|
-
case 'Space': {
|
|
1771
|
-
const bonus = st.confirmBonus;
|
|
1772
|
-
if (!isAffordable(shell, bonus))
|
|
1773
|
-
return true;
|
|
1774
|
-
if (bonus.type === 'feature')
|
|
1775
|
-
shell.activateFeature(bonus);
|
|
1776
|
-
else
|
|
1777
|
-
shell.emit('buyBonusSelect', { id: bonus.id });
|
|
1778
|
-
shell.closeModal();
|
|
1779
|
-
return true;
|
|
1780
|
-
}
|
|
1781
|
-
case 'Escape':
|
|
1782
|
-
// Remove the confirm dialog, return to browse
|
|
1783
|
-
closeConfirm(root, st);
|
|
1784
|
-
return true;
|
|
1785
|
-
default:
|
|
1786
|
-
return false;
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
// ── Browse phase ──
|
|
1790
|
-
const last = affordable.length - 1;
|
|
1791
|
-
const mobile = shell.layout === 'mobile';
|
|
1792
|
-
// Bet stepping mirrors the bar's keys (Shift+↑/↓, Shift+=/-, Numpad ±). Checked BEFORE arrow
|
|
1793
|
-
// navigation so a bare arrow still moves card focus while a Shift+arrow changes the bet.
|
|
1794
|
-
const bet = betDir(e);
|
|
1795
|
-
if (bet !== null) {
|
|
1796
|
-
stepBetBy(bet);
|
|
1797
|
-
return true;
|
|
1798
|
-
}
|
|
1799
|
-
// Determine navigation direction from key code + layout (mobile uses vertical arrows)
|
|
1800
|
-
const fwdKey = e.code === 'ArrowRight' || (mobile && e.code === 'ArrowDown');
|
|
1801
|
-
const bwdKey = e.code === 'ArrowLeft' || (mobile && e.code === 'ArrowUp');
|
|
1802
|
-
if (fwdKey) {
|
|
1803
|
-
if (last < 0)
|
|
1804
|
-
return true;
|
|
1805
|
-
if (st.focusIndex < last) {
|
|
1806
|
-
st.focusIndex++;
|
|
1807
|
-
applyFocusClass(root, bonuses, affordable, st.focusIndex);
|
|
1808
|
-
}
|
|
1809
|
-
return true;
|
|
1810
|
-
}
|
|
1811
|
-
if (bwdKey) {
|
|
1812
|
-
if (last < 0)
|
|
1813
|
-
return true;
|
|
1814
|
-
if (st.focusIndex > 0) {
|
|
1815
|
-
st.focusIndex--;
|
|
1816
|
-
applyFocusClass(root, bonuses, affordable, st.focusIndex);
|
|
1817
|
-
}
|
|
1818
|
-
return true;
|
|
1819
|
-
}
|
|
1820
|
-
switch (e.code) {
|
|
1821
|
-
case 'Enter':
|
|
1822
|
-
case 'Space':
|
|
1823
|
-
if (last < 0 || st.focusIndex < 0)
|
|
1824
|
-
return true;
|
|
1825
|
-
{
|
|
1826
|
-
const bonus = affordable[st.focusIndex];
|
|
1827
|
-
openConfirm(shell, bonus, root, st);
|
|
1828
|
-
}
|
|
1829
|
-
return true;
|
|
1830
|
-
// Bare =/- also step the bet (the Shift+=/- and Numpad variants are handled by betDir above).
|
|
1831
|
-
case 'Equal':
|
|
1832
|
-
stepBetBy(1);
|
|
1833
|
-
return true;
|
|
1834
|
-
case 'Minus':
|
|
1835
|
-
stepBetBy(-1);
|
|
1836
|
-
return true;
|
|
1837
|
-
case 'Escape':
|
|
1838
|
-
shell.closeModal();
|
|
1839
|
-
return true;
|
|
1840
|
-
default:
|
|
1841
|
-
return false;
|
|
1842
|
-
}
|
|
1843
|
-
};
|
|
1844
|
-
return { root, onKey };
|
|
1845
|
-
}
|
|
1846
|
-
/** Apply a CSS keyboard-focus class to the currently focused affordable card. */
|
|
1847
|
-
function applyFocusClass(overlay, bonuses, affordable, focusIndex) {
|
|
1848
|
-
for (const b of bonuses) {
|
|
1849
|
-
const card = overlay.querySelector(`[data-ge="bonus-card-${b.id}"]`);
|
|
1850
|
-
if (!card)
|
|
1851
|
-
continue;
|
|
1852
|
-
card.classList.remove('ge-bonus-card--kbd-focus');
|
|
1853
|
-
}
|
|
1854
|
-
const focused = affordable[focusIndex];
|
|
1855
|
-
if (!focused)
|
|
1856
|
-
return;
|
|
1857
|
-
const card = overlay.querySelector(`[data-ge="bonus-card-${focused.id}"]`);
|
|
1858
|
-
if (card)
|
|
1859
|
-
card.classList.add('ge-bonus-card--kbd-focus');
|
|
1860
|
-
}
|
|
1861
|
-
/** Open the confirm dialog for the given bonus and track it in overlay state. */
|
|
1862
|
-
function openConfirm(shell, bonus, overlay, st) {
|
|
1863
|
-
closeConfirm(overlay, st); // remove any existing confirm
|
|
1864
|
-
st.confirmBonus = bonus;
|
|
1865
|
-
overlay.appendChild(buildConfirm(shell, bonus, overlay, st));
|
|
1866
|
-
shell.fitModals();
|
|
1867
|
-
}
|
|
1868
|
-
/** Remove the confirm dialog and clear the overlay state. */
|
|
1869
|
-
function closeConfirm(overlay, st) {
|
|
1870
|
-
// The confirm dialog is a .ge-sheet with data-ge="bonus-confirm" appended directly to overlay.
|
|
1871
|
-
const sheet = overlay.querySelector('[data-ge="bonus-confirm"]');
|
|
1872
|
-
if (sheet)
|
|
1873
|
-
sheet.remove();
|
|
1874
|
-
st.confirmBonus = undefined;
|
|
1875
|
-
}
|
|
1876
|
-
/** Bet control — a compact −/+ pill around the live stake, in a thin footer at the screen bottom.
|
|
1877
|
-
* Stepping repaints the value, re-prices the cards, and updates the control bar. */
|
|
1878
|
-
function buildBetBar(shell, onChange) {
|
|
1879
|
-
const bar = document.createElement('div');
|
|
1880
|
-
bar.className = 'ge-bb-betbar';
|
|
1881
|
-
const pill = document.createElement('div');
|
|
1882
|
-
pill.className = 'ge-bb-betpill';
|
|
1883
|
-
const val = document.createElement('div');
|
|
1884
|
-
val.className = 'ge-bb-betval';
|
|
1885
|
-
const down = stepButton('bb-bet-down', 'minus');
|
|
1886
|
-
const up = stepButton('bb-bet-up', 'plus');
|
|
1887
|
-
// Mirror the control bar: disable a stepper at the end of the bet range, and lock both
|
|
1888
|
-
// while busy — so changing the stake behaves identically here and on the bottom bar.
|
|
1889
|
-
const paint = () => {
|
|
1890
|
-
val.innerHTML = `<span>${shell.t('Bet')}</span><b>${formatCurrency(shell.state.bet, shell.config.currency)}</b>`;
|
|
1891
|
-
const i = shell.state.availableBets.indexOf(shell.state.bet);
|
|
1892
|
-
down.disabled = shell.state.busy || i <= 0;
|
|
1893
|
-
up.disabled = shell.state.busy || i >= shell.state.availableBets.length - 1;
|
|
1894
|
-
};
|
|
1895
|
-
const step = (dir) => () => {
|
|
1896
|
-
const next = stepBet(shell.state, dir);
|
|
1897
|
-
if (next === shell.state.bet)
|
|
1898
|
-
return;
|
|
1899
|
-
shell.state.bet = next;
|
|
1900
|
-
shell.emit('betChange', next);
|
|
1901
|
-
shell.render();
|
|
1902
|
-
paint();
|
|
1903
|
-
onChange();
|
|
1904
|
-
};
|
|
1905
|
-
down.addEventListener('click', step(-1));
|
|
1906
|
-
up.addEventListener('click', step(1));
|
|
1907
|
-
paint();
|
|
1908
|
-
pill.append(down, val, up);
|
|
1909
|
-
bar.appendChild(pill);
|
|
1910
|
-
return bar;
|
|
1911
|
-
}
|
|
1912
|
-
function stepButton(ge, name) {
|
|
1913
|
-
const b = document.createElement('button');
|
|
1914
|
-
b.className = 'ge-bb-betstep';
|
|
1915
|
-
b.dataset.ge = ge;
|
|
1916
|
-
b.innerHTML = icon(name);
|
|
1917
|
-
return b;
|
|
1918
|
-
}
|
|
1919
|
-
/** A grid card: title → thumbnail → description → volatility → price → full-bleed CTA.
|
|
1920
|
-
* Clicking (when affordable) opens the confirmation modal. */
|
|
1921
|
-
function buildCard(shell, bonus, overlay, st) {
|
|
1922
|
-
const accent = effectiveAccent(bonus);
|
|
1923
|
-
const card = document.createElement('div');
|
|
1924
|
-
card.className = 'ge-bonus-card';
|
|
1925
|
-
card.dataset.ge = `bonus-card-${bonus.id}`;
|
|
1926
|
-
card.style.setProperty('--card-acc', accent);
|
|
1927
|
-
card.style.setProperty('--card-ink', contrastText(accent));
|
|
1928
|
-
const enabled = isAffordable(shell, bonus);
|
|
1929
|
-
// Stack the confirm on top of the overlay grid (cancel returns to the grid). Re-checks
|
|
1930
|
-
// affordability at click time, so it's a safe no-op when the option can't be bought.
|
|
1931
|
-
const select = () => {
|
|
1932
|
-
if (!isAffordable(shell, bonus))
|
|
1933
|
-
return;
|
|
1934
|
-
openConfirm(shell, bonus, overlay, st);
|
|
1935
|
-
};
|
|
1936
|
-
// Game-supplied card UI: the shell keeps the wrapper (grid sizing + accent vars) and runs the
|
|
1937
|
-
// buy flow when the game calls ctx.select(); the game owns everything inside.
|
|
1938
|
-
if (bonus.custom) {
|
|
1939
|
-
card.classList.add('ge-bonus-card--custom');
|
|
1940
|
-
const price = bonus.priceMultiplier * shell.state.bet;
|
|
1941
|
-
card.appendChild(bonus.custom({
|
|
1942
|
-
bonus, bet: shell.state.bet, price,
|
|
1943
|
-
priceText: formatCurrency(price, shell.config.currency),
|
|
1944
|
-
disabled: !enabled, accent, select,
|
|
1945
|
-
}));
|
|
1946
|
-
return card;
|
|
1947
|
-
}
|
|
1948
|
-
card.appendChild(cardBody(shell, bonus));
|
|
1949
|
-
const cta = document.createElement('button');
|
|
1950
|
-
cta.className = 'ge-bonus-cta';
|
|
1951
|
-
cta.dataset.ge = `bonus-cta-${bonus.id}`;
|
|
1952
|
-
cta.textContent = shell.t(actionLabel(bonus));
|
|
1953
|
-
card.appendChild(cta);
|
|
1954
|
-
if (!enabled) {
|
|
1955
|
-
card.classList.add('ge-bonus-off');
|
|
1956
|
-
cta.disabled = true;
|
|
1957
|
-
}
|
|
1958
|
-
else {
|
|
1959
|
-
card.addEventListener('click', select);
|
|
1960
|
-
}
|
|
1961
|
-
return card;
|
|
1962
|
-
}
|
|
1963
|
-
/** The shared card interior (everything above the action area), reused by the confirm modal. */
|
|
1964
|
-
function cardBody(shell, bonus) {
|
|
1965
|
-
const price = bonus.priceMultiplier * shell.state.bet;
|
|
1966
|
-
const wrap = document.createElement('div');
|
|
1967
|
-
wrap.className = 'ge-bonus-body';
|
|
1968
|
-
wrap.innerHTML =
|
|
1969
|
-
`<div class="ge-bonus-title">${bonus.title}</div>` +
|
|
1970
|
-
`<div class="ge-bonus-thumb">${thumb(bonus)}</div>` +
|
|
1971
|
-
`<div class="ge-bonus-desc">${bonus.description}</div>` +
|
|
1972
|
-
`<div class="ge-bonus-spacer"></div>` +
|
|
1973
|
-
(bonus.volatility ? `<div class="ge-bonus-vol">${volatility(bonus.volatility)}</div>` : '') +
|
|
1974
|
-
`<div class="ge-bonus-price">${formatCurrency(price, shell.config.currency)}</div>`;
|
|
1975
|
-
return wrap;
|
|
1976
|
-
}
|
|
1977
|
-
/** Confirmation modal — the shared card chrome (accent title heading, no ✕) with a bonus
|
|
1978
|
-
* preview body and a full-bleed Cancel + action footer. */
|
|
1979
|
-
function buildConfirm(shell, bonus, overlay, st) {
|
|
1980
|
-
const accent = effectiveAccent(bonus);
|
|
1981
|
-
const ui = createCardModal({ ge: 'bonus-confirm', title: bonus.title, accent, onClose: () => { closeConfirm(overlay, st); } });
|
|
1982
|
-
const price = bonus.priceMultiplier * shell.state.bet;
|
|
1983
|
-
const preview = document.createElement('div');
|
|
1984
|
-
preview.className = 'ge-confirm-preview';
|
|
1985
|
-
preview.innerHTML =
|
|
1986
|
-
`<div class="ge-bonus-thumb">${thumb(bonus)}</div>` +
|
|
1987
|
-
`<div class="ge-bonus-desc">${bonus.description}</div>` +
|
|
1988
|
-
(bonus.volatility ? `<div class="ge-bonus-vol">${volatility(bonus.volatility)}</div>` : '') +
|
|
1989
|
-
`<div class="ge-bonus-price">${formatCurrency(price, shell.config.currency)}</div>`;
|
|
1990
|
-
ui.body.appendChild(preview);
|
|
1991
|
-
const actions = document.createElement('div');
|
|
1992
|
-
actions.className = 'ge-modal-actions';
|
|
1993
|
-
const cancel = document.createElement('button');
|
|
1994
|
-
cancel.className = 'ge-modal-btn ge-modal-btn--ghost';
|
|
1995
|
-
cancel.dataset.ge = 'bonus-confirm-cancel';
|
|
1996
|
-
cancel.textContent = shell.t('Cancel');
|
|
1997
|
-
cancel.addEventListener('click', () => closeConfirm(overlay, st));
|
|
1998
|
-
const buy = document.createElement('button');
|
|
1999
|
-
buy.className = 'ge-modal-btn ge-modal-btn--accent';
|
|
2000
|
-
buy.dataset.ge = 'bonus-confirm-buy';
|
|
2001
|
-
buy.textContent = shell.t(actionLabel(bonus));
|
|
2002
|
-
buy.style.color = contrastText(accent); // bg comes from --card-acc on the card
|
|
2003
|
-
buy.addEventListener('click', () => {
|
|
2004
|
-
// Re-check at click time: the confirm modal stays open across state changes, so a spin
|
|
2005
|
-
// starting (busy), buy-bonus being disabled, or the balance dropping must block the purchase.
|
|
2006
|
-
if (!isAffordable(shell, bonus))
|
|
2007
|
-
return;
|
|
2008
|
-
if (bonus.type === 'feature')
|
|
2009
|
-
shell.activateFeature(bonus);
|
|
2010
|
-
else
|
|
2011
|
-
shell.emit('buyBonusSelect', { id: bonus.id });
|
|
2012
|
-
shell.closeModal();
|
|
2013
|
-
});
|
|
2014
|
-
actions.append(cancel, buy);
|
|
2015
|
-
ui.card.appendChild(actions);
|
|
2016
|
-
return ui.root;
|
|
2017
|
-
}
|
|
2018
|
-
function thumb(bonus) {
|
|
2019
|
-
if (bonus.thumbnail)
|
|
2020
|
-
return `<img src="${bonus.thumbnail}" alt="${bonus.title}">`;
|
|
2021
|
-
return `<span class="ge-bonus-thumb-ph">${icon('gift')}</span>`;
|
|
2022
|
-
}
|
|
2023
|
-
/** Volatility as five lightning bolts (the supplied SVG); `level` lit in the accent, rest dimmed. */
|
|
2024
|
-
function volatility(level) {
|
|
2025
|
-
const n = Math.max(0, Math.min(5, level));
|
|
2026
|
-
const bolt = icon('lightning');
|
|
2027
|
-
return `<span class="ge-bonus-vol-on">${bolt.repeat(n)}</span>` +
|
|
2028
|
-
`<span class="ge-bonus-vol-off">${bolt.repeat(5 - n)}</span>`;
|
|
2029
|
-
}
|
|
2030
|
-
function actionLabel(bonus) {
|
|
2031
|
-
return bonus.type === 'feature' ? 'Activate' : 'Buy';
|
|
2032
|
-
}
|
|
2033
|
-
function isAffordable(shell, bonus) {
|
|
2034
|
-
if (shell.state.busy || !shell.state.buyBonusEnabled)
|
|
2035
|
-
return false;
|
|
2036
|
-
return bonus.priceMultiplier * shell.state.bet <= shell.state.balance;
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
/** A centred picker (chips grid + accent Confirm) on the shared card modal. */
|
|
2040
|
-
function buildSheet(opts) {
|
|
2041
|
-
const ui = createCardModal({ ge: opts.ge, title: opts.title, onClose: () => opts.onClose() });
|
|
2042
|
-
const grid = document.createElement('div');
|
|
2043
|
-
grid.className = 'ge-sheet-grid';
|
|
2044
|
-
const cols = typeof opts.columns === 'number' ? { wide: opts.columns, mobile: opts.columns } : opts.columns;
|
|
2045
|
-
grid.style.setProperty('--cols', String(cols.wide));
|
|
2046
|
-
grid.style.setProperty('--cols-m', String(cols.mobile));
|
|
2047
|
-
let selected = opts.selected;
|
|
2048
|
-
let focusIndex = opts.choices.findIndex((c) => c.id === selected);
|
|
2049
|
-
if (focusIndex < 0)
|
|
2050
|
-
focusIndex = 0;
|
|
2051
|
-
const chips = [];
|
|
2052
|
-
/** Update chip visuals to reflect the current selected/focused index. */
|
|
2053
|
-
function setHighlight(newIndex) {
|
|
2054
|
-
focusIndex = newIndex;
|
|
2055
|
-
selected = opts.choices[focusIndex].id;
|
|
2056
|
-
for (let i = 0; i < chips.length; i++) {
|
|
2057
|
-
chips[i].classList.toggle('ge-on', i === focusIndex);
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
for (let i = 0; i < opts.choices.length; i++) {
|
|
2061
|
-
const c = opts.choices[i];
|
|
2062
|
-
const chip = document.createElement('button');
|
|
2063
|
-
chip.className = 'ge-chip' + (i === focusIndex ? ' ge-on' : '');
|
|
2064
|
-
chip.dataset.id = c.id;
|
|
2065
|
-
chip.textContent = c.label;
|
|
2066
|
-
const idx = i; // capture for closure
|
|
2067
|
-
chip.addEventListener('click', () => {
|
|
2068
|
-
setHighlight(idx);
|
|
2069
|
-
});
|
|
2070
|
-
chips.push(chip);
|
|
2071
|
-
grid.appendChild(chip);
|
|
2072
|
-
}
|
|
2073
|
-
ui.body.appendChild(grid);
|
|
2074
|
-
function doConfirm() {
|
|
2075
|
-
opts.onConfirm(selected);
|
|
2076
|
-
opts.onClose();
|
|
2077
|
-
}
|
|
2078
|
-
// Single full-bleed Confirm; dismissal is the ✕ (top-right). No Cancel button.
|
|
2079
|
-
const confirm = document.createElement('button');
|
|
2080
|
-
confirm.className = 'ge-modal-btn ge-modal-btn--accent';
|
|
2081
|
-
confirm.dataset.ge = 'sheet-confirm';
|
|
2082
|
-
confirm.textContent = opts.confirmLabel;
|
|
2083
|
-
confirm.addEventListener('click', doConfirm);
|
|
2084
|
-
ui.card.appendChild(confirm);
|
|
2085
|
-
function onKey(e) {
|
|
2086
|
-
const last = opts.choices.length - 1;
|
|
2087
|
-
switch (e.code) {
|
|
2088
|
-
case 'ArrowRight':
|
|
2089
|
-
case 'ArrowDown':
|
|
2090
|
-
case 'Equal': // + on most keyboards
|
|
2091
|
-
case 'NumpadAdd':
|
|
2092
|
-
if (focusIndex < last)
|
|
2093
|
-
setHighlight(focusIndex + 1);
|
|
2094
|
-
return true;
|
|
2095
|
-
case 'ArrowLeft':
|
|
2096
|
-
case 'ArrowUp':
|
|
2097
|
-
case 'Minus':
|
|
2098
|
-
case 'NumpadSubtract':
|
|
2099
|
-
if (focusIndex > 0)
|
|
2100
|
-
setHighlight(focusIndex - 1);
|
|
2101
|
-
return true;
|
|
2102
|
-
case 'Enter':
|
|
2103
|
-
case 'Space':
|
|
2104
|
-
doConfirm();
|
|
2105
|
-
return true;
|
|
2106
|
-
case 'Escape':
|
|
2107
|
-
opts.onClose();
|
|
2108
|
-
return true;
|
|
2109
|
-
default:
|
|
2110
|
-
return false;
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
return { root: ui.root, onKey };
|
|
2114
|
-
}
|
|
2115
|
-
/** Bet picker — all available bets as chips (6 per row, 3 on mobile), accent Confirm applies it. */
|
|
2116
|
-
function openBetModal(shell) {
|
|
2117
|
-
return buildSheet({
|
|
2118
|
-
ge: 'bet-modal', title: shell.t('Bet'), columns: { wide: 6, mobile: 3 }, confirmLabel: shell.t('Confirm'),
|
|
2119
|
-
choices: shell.state.availableBets.map((b) => ({ id: String(b), label: formatCurrency(b, shell.config.currency) })),
|
|
2120
|
-
selected: String(shell.state.bet),
|
|
2121
|
-
onClose: () => shell.closeModal(),
|
|
2122
|
-
onConfirm: (id) => {
|
|
2123
|
-
const v = Number(id);
|
|
2124
|
-
if (v !== shell.state.bet) {
|
|
2125
|
-
shell.state.bet = v;
|
|
2126
|
-
shell.emit('betChange', v);
|
|
2127
|
-
}
|
|
2128
|
-
shell.render();
|
|
2129
|
-
},
|
|
2130
|
-
});
|
|
2131
|
-
}
|
|
2132
|
-
const AUTOPLAY_COUNTS = [10, 25, 50, 100, 250, 500, 1000, 2000, Infinity];
|
|
2133
|
-
/** The selectable spin counts, honouring an optional jurisdiction max. With a `maxCount`:
|
|
2134
|
-
* drop ∞, keep presets ≤ max, and append the max itself when it isn't already a preset
|
|
2135
|
-
* (so the cap is always offered). Without one: the default presets including ∞. */
|
|
2136
|
-
function autoplayCounts(maxCount) {
|
|
2137
|
-
if (maxCount == null)
|
|
2138
|
-
return AUTOPLAY_COUNTS;
|
|
2139
|
-
const capped = AUTOPLAY_COUNTS.filter((n) => Number.isFinite(n) && n <= maxCount);
|
|
2140
|
-
if (!capped.includes(maxCount))
|
|
2141
|
-
capped.push(maxCount);
|
|
2142
|
-
return capped;
|
|
2143
|
-
}
|
|
2144
|
-
/** Autoplay picker — spin counts (incl. ∞ unless a maxCount caps them); Confirm starts autoplay. */
|
|
2145
|
-
function openAutoplayModal(shell) {
|
|
2146
|
-
const maxCount = shell.config.features.autoplay?.maxCount;
|
|
2147
|
-
const counts = autoplayCounts(maxCount);
|
|
2148
|
-
return buildSheet({
|
|
2149
|
-
ge: 'autoplay-modal', title: shell.t('Autoplay'), columns: 3, confirmLabel: shell.t('Start'),
|
|
2150
|
-
choices: counts.map((n) => ({ id: String(n), label: Number.isFinite(n) ? String(n) : '∞' })),
|
|
2151
|
-
selected: String(shell.state.autoplay.remaining || counts[0]),
|
|
2152
|
-
onClose: () => shell.closeModal(),
|
|
2153
|
-
onConfirm: (id) => {
|
|
2154
|
-
let remaining = Number(id); // "Infinity" → Infinity
|
|
2155
|
-
if (maxCount != null)
|
|
2156
|
-
remaining = Math.min(remaining, maxCount); // defensive cap
|
|
2157
|
-
shell.state.autoplay = { active: true, remaining };
|
|
2158
|
-
shell.emit('autoplayStart', { active: true, remaining });
|
|
2159
|
-
shell.render();
|
|
2160
|
-
},
|
|
2161
|
-
});
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
/** Build a generic, externally-triggered modal (title + body text + optional action buttons),
|
|
2165
|
-
* on the shared card-modal chrome. Each action runs its `on` then closes; the ✕ (if
|
|
2166
|
-
* `availableClose`) and the actions are the only ways to dismiss. See GameShell.openModal. */
|
|
2167
|
-
function buildModal(opts) {
|
|
2168
|
-
const ui = createCardModal({
|
|
2169
|
-
ge: 'modal',
|
|
2170
|
-
title: opts.title,
|
|
2171
|
-
closable: opts.availableClose,
|
|
2172
|
-
blur: opts.blurLevel,
|
|
2173
|
-
onClose: () => ui.root.remove(),
|
|
2174
|
-
});
|
|
2175
|
-
const text = document.createElement('p');
|
|
2176
|
-
text.className = 'ge-modal-text';
|
|
2177
|
-
text.dataset.ge = 'modal-body';
|
|
2178
|
-
text.textContent = opts.body;
|
|
2179
|
-
ui.body.appendChild(text);
|
|
2180
|
-
if (opts.actions?.length) {
|
|
2181
|
-
const actions = document.createElement('div');
|
|
2182
|
-
actions.className = 'ge-modal-actions';
|
|
2183
|
-
for (const a of opts.actions) {
|
|
2184
|
-
const btn = document.createElement('button');
|
|
2185
|
-
btn.className = 'ge-modal-btn';
|
|
2186
|
-
btn.dataset.ge = 'modal-action';
|
|
2187
|
-
btn.textContent = a.title;
|
|
2188
|
-
if (a.color) {
|
|
2189
|
-
btn.style.background = a.color;
|
|
2190
|
-
btn.style.color = contrastText(a.color);
|
|
2191
|
-
}
|
|
2192
|
-
else
|
|
2193
|
-
btn.classList.add('ge-modal-btn--ghost');
|
|
2194
|
-
btn.addEventListener('click', () => { a.on?.(); ui.root.remove(); });
|
|
2195
|
-
actions.appendChild(btn);
|
|
2196
|
-
}
|
|
2197
|
-
ui.card.appendChild(actions);
|
|
2198
|
-
}
|
|
2199
|
-
return ui.root;
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
/** The replay summary modal — built on the shared card chrome, but NOT dismissable: no ✕,
|
|
2203
|
-
* and the backdrop never closes it. The only way out is START REPLAY, which closes the
|
|
2204
|
-
* modal, runs `onReplay`, then reopens it (whether the handler resolves OR rejects, so a
|
|
2205
|
-
* failed replay can't strand the user). */
|
|
2206
|
-
function buildReplayModal(shell, opts) {
|
|
2207
|
-
const { bonusId, bet, payoutMultiplier } = opts;
|
|
2208
|
-
const fmt = (n) => formatCurrency(n, shell.config.currency);
|
|
2209
|
-
const fmtWin = (n) => formatCurrency(n, shell.config.currency, true); // total win: variable decimals
|
|
2210
|
-
const bonus = Array.isArray(shell.config.features.buyBonus)
|
|
2211
|
-
? shell.config.features.buyBonus.find((b) => b.id === bonusId)
|
|
2212
|
-
: undefined;
|
|
2213
|
-
const mode = bonus?.title ?? bonusId;
|
|
2214
|
-
const costMultiplier = bonus?.priceMultiplier ?? 1;
|
|
2215
|
-
const ui = createCardModal({
|
|
2216
|
-
ge: 'replay-modal',
|
|
2217
|
-
title: shell.t('Replay'),
|
|
2218
|
-
closable: false, // no ✕; the backdrop never dismisses it either
|
|
2219
|
-
onClose: () => { }, // unused — there is no close affordance
|
|
2220
|
-
});
|
|
2221
|
-
const rows = document.createElement('div');
|
|
2222
|
-
rows.className = 'ge-replay-rows';
|
|
2223
|
-
const row = (label, value, total = false) => {
|
|
2224
|
-
const r = document.createElement('div');
|
|
2225
|
-
r.className = `ge-replay-row${total ? ' ge-replay-total' : ''}`;
|
|
2226
|
-
const l = document.createElement('span');
|
|
2227
|
-
l.textContent = shell.t(label);
|
|
2228
|
-
const v = document.createElement('b');
|
|
2229
|
-
v.textContent = value;
|
|
2230
|
-
r.append(l, v);
|
|
2231
|
-
rows.appendChild(r);
|
|
2232
|
-
};
|
|
2233
|
-
row('Mode', mode);
|
|
2234
|
-
row('Base bet', fmt(bet));
|
|
2235
|
-
row('Cost multiplier', `${costMultiplier}×`);
|
|
2236
|
-
row('Total cost', fmt(bet * costMultiplier));
|
|
2237
|
-
row('Win multiplier', `${payoutMultiplier}×`);
|
|
2238
|
-
row('Total win', fmtWin(payoutMultiplier * bet), true);
|
|
2239
|
-
ui.body.appendChild(rows);
|
|
2240
|
-
const actions = document.createElement('div');
|
|
2241
|
-
actions.className = 'ge-modal-actions';
|
|
2242
|
-
const btn = document.createElement('button');
|
|
2243
|
-
btn.className = 'ge-modal-btn ge-modal-btn--accent';
|
|
2244
|
-
btn.dataset.ge = 'replay-start';
|
|
2245
|
-
btn.textContent = shell.t('Start replay');
|
|
2246
|
-
btn.addEventListener('click', () => {
|
|
2247
|
-
ui.root.remove(); // close immediately
|
|
2248
|
-
// Reopen after the handler settles. On rejection we still reopen — this modal is the only
|
|
2249
|
-
// way out of replay mode, so a failed play must not strand the user on an empty screen.
|
|
2250
|
-
const reopen = () => { shell.openReplay(opts); };
|
|
2251
|
-
Promise.resolve(opts.onReplay()).then(reopen, reopen);
|
|
2252
|
-
});
|
|
2253
|
-
actions.appendChild(btn);
|
|
2254
|
-
ui.card.appendChild(actions);
|
|
2255
|
-
return ui.root;
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2258
|
-
/** True when the user (or environment) prefers no motion. Missing matchMedia
|
|
2259
|
-
* — e.g. jsdom / SSR — is treated as reduced so animations never block. */
|
|
2260
|
-
function prefersReducedMotion() {
|
|
2261
|
-
const mm = globalThis.matchMedia;
|
|
2262
|
-
if (typeof mm !== 'function')
|
|
2263
|
-
return true;
|
|
2264
|
-
return mm('(prefers-reduced-motion: reduce)').matches;
|
|
2265
|
-
}
|
|
2266
|
-
/** Animate el's text from `from` to `to` via `fmt`. Skips to final value when
|
|
2267
|
-
* motion is reduced or rAF is unavailable. Returns a canceler that stops the loop —
|
|
2268
|
-
* call it before the target node is detached so the rAF chain can't keep writing to
|
|
2269
|
-
* (and pinning) a dead node, or stack up overlapping loops on rapid updates. */
|
|
2270
|
-
function countUp(el, from, to, fmt, durationMs = 450) {
|
|
2271
|
-
const raf = globalThis.requestAnimationFrame;
|
|
2272
|
-
const caf = globalThis.cancelAnimationFrame;
|
|
2273
|
-
if (prefersReducedMotion() || typeof raf !== 'function') {
|
|
2274
|
-
el.textContent = fmt(to);
|
|
2275
|
-
return () => { };
|
|
2276
|
-
}
|
|
2277
|
-
let handle = 0;
|
|
2278
|
-
let cancelled = false;
|
|
2279
|
-
const origin = globalThis.performance?.now() ?? 0;
|
|
2280
|
-
const tick = (t) => {
|
|
2281
|
-
if (cancelled)
|
|
2282
|
-
return;
|
|
2283
|
-
const start = origin;
|
|
2284
|
-
const p = Math.min(1, (t - start) / durationMs);
|
|
2285
|
-
const eased = 1 - Math.pow(1 - p, 3); // easeOutCubic
|
|
2286
|
-
el.textContent = fmt(from + (to - from) * eased);
|
|
2287
|
-
if (p < 1)
|
|
2288
|
-
handle = raf(tick);
|
|
2289
|
-
else
|
|
2290
|
-
el.textContent = fmt(to);
|
|
2291
|
-
};
|
|
2292
|
-
handle = raf(tick);
|
|
2293
|
-
return () => {
|
|
2294
|
-
cancelled = true;
|
|
2295
|
-
if (typeof caf === 'function')
|
|
2296
|
-
caf(handle);
|
|
2297
|
-
};
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
// MACHINE-TRANSLATED — native QA required before production use.
|
|
2301
|
-
// Keys are the English source strings exactly as they appear at t('…') call sites.
|
|
2302
|
-
// Entries within each language block are sorted alphabetically by key for diff stability.
|
|
2303
|
-
// 'en' is omitted: the resolver returns the source string unchanged for English.
|
|
2304
|
-
const LOCALES = {
|
|
2305
|
-
da: {
|
|
2306
|
-
DISCLAIMER: 'Ansvarsfraskrivelse',
|
|
2307
|
-
Activate: 'Aktivér',
|
|
2308
|
-
Autoplay: 'Autoplay',
|
|
2309
|
-
Balance: 'Saldo',
|
|
2310
|
-
Bet: 'Indsats',
|
|
2311
|
-
'BUY BONUS': 'KØB BONUS',
|
|
2312
|
-
Buy: 'Køb',
|
|
2313
|
-
'Buy bonus': 'Køb bonus',
|
|
2314
|
-
Cancel: 'Annuller',
|
|
2315
|
-
Close: 'Luk',
|
|
2316
|
-
'Cluster pays': 'Klyngeudbetaling',
|
|
2317
|
-
Confirm: 'Bekræft',
|
|
2318
|
-
Controls: 'Kontroller',
|
|
2319
|
-
'Decrease your stake.': 'Reducer din indsats.',
|
|
2320
|
-
DISABLE: 'DEAKTIVER',
|
|
2321
|
-
'Dismiss the current overlay.': 'Luk det aktuelle overlay.',
|
|
2322
|
-
'Free spins': 'Gratisspins',
|
|
2323
|
-
Game: 'Spil',
|
|
2324
|
-
'Game info': 'Spilinfo',
|
|
2325
|
-
Hotkeys: 'Tastaturgenveje',
|
|
2326
|
-
'Increase your stake.': 'Forøg din indsats.',
|
|
2327
|
-
'Lower bet': 'Lavere indsats',
|
|
2328
|
-
'Master volume': 'Hovedvolumen',
|
|
2329
|
-
'Max win': 'Maks gevinst',
|
|
2330
|
-
Menu: 'Menu',
|
|
2331
|
-
'Menu & info': 'Menu og info',
|
|
2332
|
-
Modes: 'Tilstande',
|
|
2333
|
-
Music: 'Musik',
|
|
2334
|
-
Mute: 'Lydløs',
|
|
2335
|
-
'Mute or unmute the game.': 'Slå lyden til/fra.',
|
|
2336
|
-
Navigate: 'Naviger',
|
|
2337
|
-
'Open settings and game info.': 'Åbn indstillinger og spilinfo.',
|
|
2338
|
-
'Open the paytable and rules.': 'Åbn gevinsttabel og regler.',
|
|
2339
|
-
'Pay a fixed cost to enter a bonus feature.': 'Betal en fast pris for at aktivere en bonusfunktion.',
|
|
2340
|
-
Paylines: 'Gevinstlinjer',
|
|
2341
|
-
Paytable: 'Gevinsttabel',
|
|
2342
|
-
'Pays anywhere': 'Udbetaler overalt',
|
|
2343
|
-
Price: 'Pris',
|
|
2344
|
-
'Raise bet': 'Hæv indsats',
|
|
2345
|
-
Replay: 'Genspil',
|
|
2346
|
-
RTP: 'RTP',
|
|
2347
|
-
SFX: 'Lydeffekter',
|
|
2348
|
-
Settings: 'Indstillinger',
|
|
2349
|
-
Sound: 'Lyd',
|
|
2350
|
-
Spin: 'Drej',
|
|
2351
|
-
'Spin automatically a set number of times.': 'Spin automatisk et bestemt antal gange.',
|
|
2352
|
-
'Speed up spin animations.': 'Gør spilanimationer hurtigere.',
|
|
2353
|
-
Start: 'Start',
|
|
2354
|
-
'Start a spin at the current bet.': 'Start et spin med den aktuelle indsats.',
|
|
2355
|
-
'Start replay': 'Start genspil',
|
|
2356
|
-
'Total win': 'Samlet gevinst',
|
|
2357
|
-
Turbo: 'Turbo',
|
|
2358
|
-
'Ways to win': 'Vindermuligheder',
|
|
2359
|
-
Win: 'Gevinst',
|
|
2360
|
-
'Winning shapes': 'Vindende former',
|
|
2361
|
-
},
|
|
2362
|
-
de: {
|
|
2363
|
-
DISCLAIMER: 'Haftungsausschluss',
|
|
2364
|
-
Activate: 'Aktivieren',
|
|
2365
|
-
Autoplay: 'Autoplay',
|
|
2366
|
-
Balance: 'Kontostand',
|
|
2367
|
-
Bet: 'Einsatz',
|
|
2368
|
-
'BUY BONUS': 'BONUS KAUFEN',
|
|
2369
|
-
Buy: 'Kaufen',
|
|
2370
|
-
'Buy bonus': 'Bonus kaufen',
|
|
2371
|
-
Cancel: 'Abbrechen',
|
|
2372
|
-
Close: 'Schließen',
|
|
2373
|
-
'Cluster pays': 'Cluster-Gewinne',
|
|
2374
|
-
Confirm: 'Bestätigen',
|
|
2375
|
-
Controls: 'Steuerung',
|
|
2376
|
-
'Decrease your stake.': 'Einsatz verringern.',
|
|
2377
|
-
DISABLE: 'DEAKTIVIEREN',
|
|
2378
|
-
'Dismiss the current overlay.': 'Aktuelles Overlay schließen.',
|
|
2379
|
-
'Free spins': 'Freispiele',
|
|
2380
|
-
Game: 'Spiel',
|
|
2381
|
-
'Game info': 'Spielinfo',
|
|
2382
|
-
Hotkeys: 'Tastenkürzel',
|
|
2383
|
-
'Increase your stake.': 'Einsatz erhöhen.',
|
|
2384
|
-
'Lower bet': 'Einsatz senken',
|
|
2385
|
-
'Master volume': 'Gesamtlautstärke',
|
|
2386
|
-
'Max win': 'Max. Gewinn',
|
|
2387
|
-
Menu: 'Menü',
|
|
2388
|
-
'Menu & info': 'Menü & Info',
|
|
2389
|
-
Modes: 'Modi',
|
|
2390
|
-
Music: 'Musik',
|
|
2391
|
-
Mute: 'Stummschalten',
|
|
2392
|
-
'Mute or unmute the game.': 'Ton stummschalten oder aktivieren.',
|
|
2393
|
-
Navigate: 'Navigieren',
|
|
2394
|
-
'Open settings and game info.': 'Einstellungen und Spielinfo öffnen.',
|
|
2395
|
-
'Open the paytable and rules.': 'Gewinntabelle und Regeln öffnen.',
|
|
2396
|
-
'Pay a fixed cost to enter a bonus feature.': 'Zahle einen festen Betrag, um ein Bonus-Feature zu aktivieren.',
|
|
2397
|
-
Paylines: 'Gewinnlinien',
|
|
2398
|
-
Paytable: 'Gewinntabelle',
|
|
2399
|
-
'Pays anywhere': 'Zahlt überall',
|
|
2400
|
-
Price: 'Preis',
|
|
2401
|
-
'Raise bet': 'Einsatz erhöhen',
|
|
2402
|
-
Replay: 'Wiederholen',
|
|
2403
|
-
RTP: 'RTP',
|
|
2404
|
-
SFX: 'Soundeffekte',
|
|
2405
|
-
Settings: 'Einstellungen',
|
|
2406
|
-
Sound: 'Ton',
|
|
2407
|
-
Spin: 'Drehen',
|
|
2408
|
-
'Spin automatically a set number of times.': 'Automatisch eine festgelegte Anzahl von Runden spielen.',
|
|
2409
|
-
'Speed up spin animations.': 'Animationen beschleunigen.',
|
|
2410
|
-
Start: 'Start',
|
|
2411
|
-
'Start a spin at the current bet.': 'Mit dem aktuellen Einsatz drehen.',
|
|
2412
|
-
'Start replay': 'Wiederholen',
|
|
2413
|
-
'Total win': 'Gesamtgewinn',
|
|
2414
|
-
Turbo: 'Turbo',
|
|
2415
|
-
'Ways to win': 'Gewinnwege',
|
|
2416
|
-
Win: 'Gewinn',
|
|
2417
|
-
'Winning shapes': 'Gewinnmuster',
|
|
2418
|
-
},
|
|
2419
|
-
es: {
|
|
2420
|
-
DISCLAIMER: 'Aviso legal',
|
|
2421
|
-
Activate: 'Activar',
|
|
2422
|
-
Autoplay: 'Giro automático',
|
|
2423
|
-
Balance: 'Saldo',
|
|
2424
|
-
Bet: 'Apuesta',
|
|
2425
|
-
'BUY BONUS': 'COMPRAR BONO',
|
|
2426
|
-
Buy: 'Comprar',
|
|
2427
|
-
'Buy bonus': 'Comprar bono',
|
|
2428
|
-
Cancel: 'Cancelar',
|
|
2429
|
-
Close: 'Cerrar',
|
|
2430
|
-
'Cluster pays': 'Pago en racimo',
|
|
2431
|
-
Confirm: 'Confirmar',
|
|
2432
|
-
Controls: 'Controles',
|
|
2433
|
-
'Decrease your stake.': 'Reducir apuesta.',
|
|
2434
|
-
DISABLE: 'DESACTIVAR',
|
|
2435
|
-
'Dismiss the current overlay.': 'Cerrar el panel actual.',
|
|
2436
|
-
'Free spins': 'Giros gratis',
|
|
2437
|
-
Game: 'Juego',
|
|
2438
|
-
'Game info': 'Info del juego',
|
|
2439
|
-
Hotkeys: 'Atajos de teclado',
|
|
2440
|
-
'Increase your stake.': 'Aumentar apuesta.',
|
|
2441
|
-
'Lower bet': 'Bajar apuesta',
|
|
2442
|
-
'Master volume': 'Volumen principal',
|
|
2443
|
-
'Max win': 'Ganancia máxima',
|
|
2444
|
-
Menu: 'Menú',
|
|
2445
|
-
'Menu & info': 'Menú e info',
|
|
2446
|
-
Modes: 'Modos',
|
|
2447
|
-
Music: 'Música',
|
|
2448
|
-
Mute: 'Silenciar',
|
|
2449
|
-
'Mute or unmute the game.': 'Activar o silenciar el sonido.',
|
|
2450
|
-
Navigate: 'Navegar',
|
|
2451
|
-
'Open settings and game info.': 'Abrir ajustes e info del juego.',
|
|
2452
|
-
'Open the paytable and rules.': 'Abrir tabla de pagos y reglas.',
|
|
2453
|
-
'Pay a fixed cost to enter a bonus feature.': 'Paga un coste fijo para acceder a una función de bono.',
|
|
2454
|
-
Paylines: 'Líneas de pago',
|
|
2455
|
-
Paytable: 'Tabla de pagos',
|
|
2456
|
-
'Pays anywhere': 'Paga en cualquier lugar',
|
|
2457
|
-
Price: 'Precio',
|
|
2458
|
-
'Raise bet': 'Subir apuesta',
|
|
2459
|
-
Replay: 'Repetir',
|
|
2460
|
-
RTP: 'RTP',
|
|
2461
|
-
SFX: 'Efectos de sonido',
|
|
2462
|
-
Settings: 'Ajustes',
|
|
2463
|
-
Sound: 'Sonido',
|
|
2464
|
-
Spin: 'Girar',
|
|
2465
|
-
'Spin automatically a set number of times.': 'Girar automáticamente un número determinado de veces.',
|
|
2466
|
-
'Speed up spin animations.': 'Acelerar las animaciones de giro.',
|
|
2467
|
-
Start: 'Iniciar',
|
|
2468
|
-
'Start a spin at the current bet.': 'Iniciar un giro con la apuesta actual.',
|
|
2469
|
-
'Start replay': 'Iniciar repetición',
|
|
2470
|
-
'Total win': 'Ganancia total',
|
|
2471
|
-
Turbo: 'Turbo',
|
|
2472
|
-
'Ways to win': 'Formas de ganar',
|
|
2473
|
-
Win: 'Premio',
|
|
2474
|
-
'Winning shapes': 'Figuras ganadoras',
|
|
2475
|
-
},
|
|
2476
|
-
fi: {
|
|
2477
|
-
DISCLAIMER: 'Vastuuvapauslauseke',
|
|
2478
|
-
Activate: 'Aktivoi',
|
|
2479
|
-
Autoplay: 'Automaattipeli',
|
|
2480
|
-
Balance: 'Saldo',
|
|
2481
|
-
Bet: 'Panos',
|
|
2482
|
-
'BUY BONUS': 'OSTA BONUS',
|
|
2483
|
-
Buy: 'Osta',
|
|
2484
|
-
'Buy bonus': 'Osta bonus',
|
|
2485
|
-
Cancel: 'Peruuta',
|
|
2486
|
-
Close: 'Sulje',
|
|
2487
|
-
'Cluster pays': 'Ryhmävoitto',
|
|
2488
|
-
Confirm: 'Vahvista',
|
|
2489
|
-
Controls: 'Ohjaimet',
|
|
2490
|
-
'Decrease your stake.': 'Pienennä panostasi.',
|
|
2491
|
-
DISABLE: 'POISTA KÄYTÖSTÄ',
|
|
2492
|
-
'Dismiss the current overlay.': 'Sulje nykyinen näkymä.',
|
|
2493
|
-
'Free spins': 'Ilmaiskierrokset',
|
|
2494
|
-
Game: 'Peli',
|
|
2495
|
-
'Game info': 'Pelitiedot',
|
|
2496
|
-
Hotkeys: 'Pikanäppäimet',
|
|
2497
|
-
'Increase your stake.': 'Kasvata panostasi.',
|
|
2498
|
-
'Lower bet': 'Pienennä panosta',
|
|
2499
|
-
'Master volume': 'Pääänenvoimakkuus',
|
|
2500
|
-
'Max win': 'Maksimivoitto',
|
|
2501
|
-
Menu: 'Valikko',
|
|
2502
|
-
'Menu & info': 'Valikko ja tiedot',
|
|
2503
|
-
Modes: 'Tilat',
|
|
2504
|
-
Music: 'Musiikki',
|
|
2505
|
-
Mute: 'Mykistä',
|
|
2506
|
-
'Mute or unmute the game.': 'Mykistä tai poista mykistys.',
|
|
2507
|
-
Navigate: 'Navigoi',
|
|
2508
|
-
'Open settings and game info.': 'Avaa asetukset ja pelitiedot.',
|
|
2509
|
-
'Open the paytable and rules.': 'Avaa voittotaulukko ja säännöt.',
|
|
2510
|
-
'Pay a fixed cost to enter a bonus feature.': 'Maksa kiinteä summa päästäksesi bonusominaisuuteen.',
|
|
2511
|
-
Paylines: 'Voittolinjat',
|
|
2512
|
-
Paytable: 'Voittotaulukko',
|
|
2513
|
-
'Pays anywhere': 'Voittaa missä tahansa',
|
|
2514
|
-
Price: 'Hinta',
|
|
2515
|
-
'Raise bet': 'Nosta panosta',
|
|
2516
|
-
Replay: 'Toista uudelleen',
|
|
2517
|
-
RTP: 'RTP',
|
|
2518
|
-
SFX: 'Ääniefektit',
|
|
2519
|
-
Settings: 'Asetukset',
|
|
2520
|
-
Sound: 'Ääni',
|
|
2521
|
-
Spin: 'Pyöräytä',
|
|
2522
|
-
'Spin automatically a set number of times.': 'Pyöräytä automaattisesti määritetty määrä kertoja.',
|
|
2523
|
-
'Speed up spin animations.': 'Nopeuta pyöräytysanimaatioita.',
|
|
2524
|
-
Start: 'Aloita',
|
|
2525
|
-
'Start a spin at the current bet.': 'Aloita pyöräytys nykyisellä panoksella.',
|
|
2526
|
-
'Start replay': 'Aloita uudelleentoisto',
|
|
2527
|
-
'Total win': 'Kokonaisvoitto',
|
|
2528
|
-
Turbo: 'Turbo',
|
|
2529
|
-
'Ways to win': 'Voittotavat',
|
|
2530
|
-
Win: 'Voitto',
|
|
2531
|
-
'Winning shapes': 'Voittokuviot',
|
|
2532
|
-
},
|
|
2533
|
-
fr: {
|
|
2534
|
-
DISCLAIMER: 'Avertissement',
|
|
2535
|
-
Activate: 'Activer',
|
|
2536
|
-
Autoplay: 'Jeu automatique',
|
|
2537
|
-
Balance: 'Solde',
|
|
2538
|
-
Bet: 'Mise',
|
|
2539
|
-
'BUY BONUS': 'ACHETER BONUS',
|
|
2540
|
-
Buy: 'Acheter',
|
|
2541
|
-
'Buy bonus': 'Acheter un bonus',
|
|
2542
|
-
Cancel: 'Annuler',
|
|
2543
|
-
Close: 'Fermer',
|
|
2544
|
-
'Cluster pays': 'Gains en grappe',
|
|
2545
|
-
Confirm: 'Confirmer',
|
|
2546
|
-
Controls: 'Commandes',
|
|
2547
|
-
'Decrease your stake.': 'Diminuer votre mise.',
|
|
2548
|
-
DISABLE: 'DÉSACTIVER',
|
|
2549
|
-
'Dismiss the current overlay.': 'Fermer le panneau actuel.',
|
|
2550
|
-
'Free spins': 'Tours gratuits',
|
|
2551
|
-
Game: 'Jeu',
|
|
2552
|
-
'Game info': 'Infos du jeu',
|
|
2553
|
-
Hotkeys: 'Raccourcis clavier',
|
|
2554
|
-
'Increase your stake.': 'Augmenter votre mise.',
|
|
2555
|
-
'Lower bet': 'Baisser la mise',
|
|
2556
|
-
'Master volume': 'Volume principal',
|
|
2557
|
-
'Max win': 'Gain maximum',
|
|
2558
|
-
Menu: 'Menu',
|
|
2559
|
-
'Menu & info': 'Menu et info',
|
|
2560
|
-
Modes: 'Modes',
|
|
2561
|
-
Music: 'Musique',
|
|
2562
|
-
Mute: 'Couper le son',
|
|
2563
|
-
'Mute or unmute the game.': 'Couper ou rétablir le son.',
|
|
2564
|
-
Navigate: 'Naviguer',
|
|
2565
|
-
'Open settings and game info.': 'Ouvrir les paramètres et infos du jeu.',
|
|
2566
|
-
'Open the paytable and rules.': 'Ouvrir la table des gains et les règles.',
|
|
2567
|
-
'Pay a fixed cost to enter a bonus feature.': 'Payez un coût fixe pour accéder à une fonctionnalité bonus.',
|
|
2568
|
-
Paylines: 'Lignes de paiement',
|
|
2569
|
-
Paytable: 'Table des gains',
|
|
2570
|
-
'Pays anywhere': 'Gains sur toute la grille',
|
|
2571
|
-
Price: 'Prix',
|
|
2572
|
-
'Raise bet': 'Augmenter la mise',
|
|
2573
|
-
Replay: 'Revoir',
|
|
2574
|
-
RTP: 'RTP',
|
|
2575
|
-
SFX: 'Effets sonores',
|
|
2576
|
-
Settings: 'Paramètres',
|
|
2577
|
-
Sound: 'Son',
|
|
2578
|
-
Spin: 'Tourner',
|
|
2579
|
-
'Spin automatically a set number of times.': 'Tourner automatiquement un nombre de fois défini.',
|
|
2580
|
-
'Speed up spin animations.': 'Accélérer les animations de spin.',
|
|
2581
|
-
Start: 'Lancer',
|
|
2582
|
-
'Start a spin at the current bet.': 'Lancer un tour avec la mise actuelle.',
|
|
2583
|
-
'Start replay': 'Lancer le replay',
|
|
2584
|
-
'Total win': 'Gain total',
|
|
2585
|
-
Turbo: 'Turbo',
|
|
2586
|
-
'Ways to win': 'Façons de gagner',
|
|
2587
|
-
Win: 'Gain',
|
|
2588
|
-
'Winning shapes': 'Figures gagnantes',
|
|
2589
|
-
},
|
|
2590
|
-
hi: {
|
|
2591
|
-
DISCLAIMER: 'अस्वीकरण',
|
|
2592
|
-
Activate: 'सक्रिय करें',
|
|
2593
|
-
Autoplay: 'ऑटोप्ले',
|
|
2594
|
-
Balance: 'बैलेंस',
|
|
2595
|
-
Bet: 'दांव',
|
|
2596
|
-
'BUY BONUS': 'बोनस खरीदें',
|
|
2597
|
-
Buy: 'खरीदें',
|
|
2598
|
-
'Buy bonus': 'बोनस खरीदें',
|
|
2599
|
-
Cancel: 'रद्द करें',
|
|
2600
|
-
Close: 'बंद करें',
|
|
2601
|
-
'Cluster pays': 'क्लस्टर भुगतान',
|
|
2602
|
-
Confirm: 'पुष्टि करें',
|
|
2603
|
-
Controls: 'नियंत्रण',
|
|
2604
|
-
'Decrease your stake.': 'अपना दांव कम करें।',
|
|
2605
|
-
DISABLE: 'बंद करें',
|
|
2606
|
-
'Dismiss the current overlay.': 'वर्तमान ओवरले बंद करें।',
|
|
2607
|
-
'Free spins': 'फ्री स्पिन',
|
|
2608
|
-
Game: 'खेल',
|
|
2609
|
-
'Game info': 'गेम जानकारी',
|
|
2610
|
-
Hotkeys: 'कीबोर्ड शॉर्टकट',
|
|
2611
|
-
'Increase your stake.': 'अपना दांव बढ़ाएं।',
|
|
2612
|
-
'Lower bet': 'दांव घटाएं',
|
|
2613
|
-
'Master volume': 'मुख्य वॉल्यूम',
|
|
2614
|
-
'Max win': 'अधिकतम जीत',
|
|
2615
|
-
Menu: 'मेनू',
|
|
2616
|
-
'Menu & info': 'मेनू और जानकारी',
|
|
2617
|
-
Modes: 'मोड',
|
|
2618
|
-
Music: 'संगीत',
|
|
2619
|
-
Mute: 'म्यूट करें',
|
|
2620
|
-
'Mute or unmute the game.': 'गेम को म्यूट या अनम्यूट करें।',
|
|
2621
|
-
Navigate: 'नेविगेट करें',
|
|
2622
|
-
'Open settings and game info.': 'सेटिंग और गेम जानकारी खोलें।',
|
|
2623
|
-
'Open the paytable and rules.': 'पेटेबल और नियम खोलें।',
|
|
2624
|
-
'Pay a fixed cost to enter a bonus feature.': 'बोनस फीचर में प्रवेश के लिए एक निश्चित राशि दें।',
|
|
2625
|
-
Paylines: 'पेलाइन',
|
|
2626
|
-
Paytable: 'पेटेबल',
|
|
2627
|
-
'Pays anywhere': 'कहीं भी जीत',
|
|
2628
|
-
Price: 'मूल्य',
|
|
2629
|
-
'Raise bet': 'दांव बढ़ाएं',
|
|
2630
|
-
Replay: 'दोबारा खेलें',
|
|
2631
|
-
RTP: 'RTP',
|
|
2632
|
-
SFX: 'ध्वनि प्रभाव',
|
|
2633
|
-
Settings: 'सेटिंग',
|
|
2634
|
-
Sound: 'ध्वनि',
|
|
2635
|
-
Spin: 'स्पिन',
|
|
2636
|
-
'Spin automatically a set number of times.': 'एक निश्चित संख्या में स्वचालित रूप से स्पिन करें।',
|
|
2637
|
-
'Speed up spin animations.': 'स्पिन एनिमेशन को तेज़ करें।',
|
|
2638
|
-
Start: 'शुरू करें',
|
|
2639
|
-
'Start a spin at the current bet.': 'वर्तमान दांव पर स्पिन शुरू करें।',
|
|
2640
|
-
'Start replay': 'रीप्ले शुरू करें',
|
|
2641
|
-
'Total win': 'कुल जीत',
|
|
2642
|
-
Turbo: 'टर्बो',
|
|
2643
|
-
'Ways to win': 'जीत के तरीके',
|
|
2644
|
-
Win: 'जीत',
|
|
2645
|
-
'Winning shapes': 'जीत के आकार',
|
|
2646
|
-
},
|
|
2647
|
-
id: {
|
|
2648
|
-
DISCLAIMER: 'Penafian',
|
|
2649
|
-
Activate: 'Aktifkan',
|
|
2650
|
-
Autoplay: 'Putar Otomatis',
|
|
2651
|
-
Balance: 'Saldo',
|
|
2652
|
-
Bet: 'Taruhan',
|
|
2653
|
-
'BUY BONUS': 'BELI BONUS',
|
|
2654
|
-
Buy: 'Beli',
|
|
2655
|
-
'Buy bonus': 'Beli bonus',
|
|
2656
|
-
Cancel: 'Batal',
|
|
2657
|
-
Close: 'Tutup',
|
|
2658
|
-
'Cluster pays': 'Bayar kluster',
|
|
2659
|
-
Confirm: 'Konfirmasi',
|
|
2660
|
-
Controls: 'Kontrol',
|
|
2661
|
-
'Decrease your stake.': 'Kurangi taruhan Anda.',
|
|
2662
|
-
DISABLE: 'NONAKTIFKAN',
|
|
2663
|
-
'Dismiss the current overlay.': 'Tutup overlay saat ini.',
|
|
2664
|
-
'Free spins': 'Putaran gratis',
|
|
2665
|
-
Game: 'Permainan',
|
|
2666
|
-
'Game info': 'Info permainan',
|
|
2667
|
-
Hotkeys: 'Pintasan keyboard',
|
|
2668
|
-
'Increase your stake.': 'Tingkatkan taruhan Anda.',
|
|
2669
|
-
'Lower bet': 'Turunkan taruhan',
|
|
2670
|
-
'Master volume': 'Volume utama',
|
|
2671
|
-
'Max win': 'Kemenangan maks',
|
|
2672
|
-
Menu: 'Menu',
|
|
2673
|
-
'Menu & info': 'Menu & info',
|
|
2674
|
-
Modes: 'Mode',
|
|
2675
|
-
Music: 'Musik',
|
|
2676
|
-
Mute: 'Bisukan',
|
|
2677
|
-
'Mute or unmute the game.': 'Bisukan atau aktifkan suara permainan.',
|
|
2678
|
-
Navigate: 'Navigasi',
|
|
2679
|
-
'Open settings and game info.': 'Buka pengaturan dan info permainan.',
|
|
2680
|
-
'Open the paytable and rules.': 'Buka tabel pembayaran dan aturan.',
|
|
2681
|
-
'Pay a fixed cost to enter a bonus feature.': 'Bayar biaya tetap untuk masuk ke fitur bonus.',
|
|
2682
|
-
Paylines: 'Garis pembayaran',
|
|
2683
|
-
Paytable: 'Tabel pembayaran',
|
|
2684
|
-
'Pays anywhere': 'Menang di mana saja',
|
|
2685
|
-
Price: 'Harga',
|
|
2686
|
-
'Raise bet': 'Naikkan taruhan',
|
|
2687
|
-
Replay: 'Putar ulang',
|
|
2688
|
-
RTP: 'RTP',
|
|
2689
|
-
SFX: 'Efek suara',
|
|
2690
|
-
Settings: 'Pengaturan',
|
|
2691
|
-
Sound: 'Suara',
|
|
2692
|
-
Spin: 'Putar',
|
|
2693
|
-
'Spin automatically a set number of times.': 'Putar otomatis sejumlah kali yang ditentukan.',
|
|
2694
|
-
'Speed up spin animations.': 'Percepat animasi putaran.',
|
|
2695
|
-
Start: 'Mulai',
|
|
2696
|
-
'Start a spin at the current bet.': 'Mulai putaran dengan taruhan saat ini.',
|
|
2697
|
-
'Start replay': 'Mulai putar ulang',
|
|
2698
|
-
'Total win': 'Total kemenangan',
|
|
2699
|
-
Turbo: 'Turbo',
|
|
2700
|
-
'Ways to win': 'Cara menang',
|
|
2701
|
-
Win: 'Menang',
|
|
2702
|
-
'Winning shapes': 'Bentuk kemenangan',
|
|
2703
|
-
},
|
|
2704
|
-
ja: {
|
|
2705
|
-
DISCLAIMER: '免責事項',
|
|
2706
|
-
Activate: '有効化',
|
|
2707
|
-
Autoplay: 'オートプレイ',
|
|
2708
|
-
Balance: '残高',
|
|
2709
|
-
Bet: 'ベット',
|
|
2710
|
-
'BUY BONUS': 'ボーナス購入',
|
|
2711
|
-
Buy: '購入',
|
|
2712
|
-
'Buy bonus': 'ボーナス購入',
|
|
2713
|
-
Cancel: 'キャンセル',
|
|
2714
|
-
Close: '閉じる',
|
|
2715
|
-
'Cluster pays': 'クラスター配当',
|
|
2716
|
-
Confirm: '確認',
|
|
2717
|
-
Controls: '操作方法',
|
|
2718
|
-
'Decrease your stake.': 'ベットを減らす。',
|
|
2719
|
-
DISABLE: '無効',
|
|
2720
|
-
'Dismiss the current overlay.': '現在のオーバーレイを閉じる。',
|
|
2721
|
-
'Free spins': 'フリースピン',
|
|
2722
|
-
Game: 'ゲーム',
|
|
2723
|
-
'Game info': 'ゲーム情報',
|
|
2724
|
-
Hotkeys: 'キーボードショートカット',
|
|
2725
|
-
'Increase your stake.': 'ベットを増やす。',
|
|
2726
|
-
'Lower bet': 'ベットを下げる',
|
|
2727
|
-
'Master volume': 'マスターボリューム',
|
|
2728
|
-
'Max win': '最大当選',
|
|
2729
|
-
Menu: 'メニュー',
|
|
2730
|
-
'Menu & info': 'メニューと情報',
|
|
2731
|
-
Modes: 'モード',
|
|
2732
|
-
Music: 'ミュージック',
|
|
2733
|
-
Mute: 'ミュート',
|
|
2734
|
-
'Mute or unmute the game.': 'ゲームをミュート/ミュート解除する。',
|
|
2735
|
-
Navigate: 'ナビゲート',
|
|
2736
|
-
'Open settings and game info.': '設定とゲーム情報を開く。',
|
|
2737
|
-
'Open the paytable and rules.': '配当表とルールを開く。',
|
|
2738
|
-
'Pay a fixed cost to enter a bonus feature.': 'ボーナス機能に入るために固定料金を支払う。',
|
|
2739
|
-
Paylines: 'ペイライン',
|
|
2740
|
-
Paytable: '配当表',
|
|
2741
|
-
'Pays anywhere': 'どこでも当選',
|
|
2742
|
-
Price: '価格',
|
|
2743
|
-
'Raise bet': 'ベットを上げる',
|
|
2744
|
-
Replay: 'リプレイ',
|
|
2745
|
-
RTP: 'RTP',
|
|
2746
|
-
SFX: '効果音',
|
|
2747
|
-
Settings: '設定',
|
|
2748
|
-
Sound: 'サウンド',
|
|
2749
|
-
Spin: 'スピン',
|
|
2750
|
-
'Spin automatically a set number of times.': '設定した回数だけ自動でスピンする。',
|
|
2751
|
-
'Speed up spin animations.': 'スピンアニメーションを高速化する。',
|
|
2752
|
-
Start: 'スタート',
|
|
2753
|
-
'Start a spin at the current bet.': '現在のベットでスピンを開始する。',
|
|
2754
|
-
'Start replay': 'リプレイ開始',
|
|
2755
|
-
'Total win': '合計当選',
|
|
2756
|
-
Turbo: 'ターボ',
|
|
2757
|
-
'Ways to win': '当選方法',
|
|
2758
|
-
Win: '当選',
|
|
2759
|
-
'Winning shapes': '当選形状',
|
|
2760
|
-
},
|
|
2761
|
-
ko: {
|
|
2762
|
-
DISCLAIMER: '면책 조항',
|
|
2763
|
-
Activate: '활성화',
|
|
2764
|
-
Autoplay: '자동 플레이',
|
|
2765
|
-
Balance: '잔액',
|
|
2766
|
-
Bet: '베팅',
|
|
2767
|
-
'BUY BONUS': '보너스 구매',
|
|
2768
|
-
Buy: '구매',
|
|
2769
|
-
'Buy bonus': '보너스 구매',
|
|
2770
|
-
Cancel: '취소',
|
|
2771
|
-
Close: '닫기',
|
|
2772
|
-
'Cluster pays': '클러스터 페이',
|
|
2773
|
-
Confirm: '확인',
|
|
2774
|
-
Controls: '조작법',
|
|
2775
|
-
'Decrease your stake.': '베팅을 줄이세요.',
|
|
2776
|
-
DISABLE: '비활성화',
|
|
2777
|
-
'Dismiss the current overlay.': '현재 오버레이를 닫습니다.',
|
|
2778
|
-
'Free spins': '무료 스핀',
|
|
2779
|
-
Game: '게임',
|
|
2780
|
-
'Game info': '게임 정보',
|
|
2781
|
-
Hotkeys: '키보드 단축키',
|
|
2782
|
-
'Increase your stake.': '베팅을 늘리세요.',
|
|
2783
|
-
'Lower bet': '베팅 낮추기',
|
|
2784
|
-
'Master volume': '마스터 볼륨',
|
|
2785
|
-
'Max win': '최대 당첨',
|
|
2786
|
-
Menu: '메뉴',
|
|
2787
|
-
'Menu & info': '메뉴 & 정보',
|
|
2788
|
-
Modes: '모드',
|
|
2789
|
-
Music: '음악',
|
|
2790
|
-
Mute: '음소거',
|
|
2791
|
-
'Mute or unmute the game.': '게임 소리를 켜거나 끕니다.',
|
|
2792
|
-
Navigate: '이동',
|
|
2793
|
-
'Open settings and game info.': '설정 및 게임 정보를 엽니다.',
|
|
2794
|
-
'Open the paytable and rules.': '페이테이블 및 규칙을 엽니다.',
|
|
2795
|
-
'Pay a fixed cost to enter a bonus feature.': '고정 비용을 지불하고 보너스 기능에 진입하세요.',
|
|
2796
|
-
Paylines: '페이라인',
|
|
2797
|
-
Paytable: '페이테이블',
|
|
2798
|
-
'Pays anywhere': '어디서나 당첨',
|
|
2799
|
-
Price: '가격',
|
|
2800
|
-
'Raise bet': '베팅 올리기',
|
|
2801
|
-
Replay: '다시보기',
|
|
2802
|
-
RTP: 'RTP',
|
|
2803
|
-
SFX: '효과음',
|
|
2804
|
-
Settings: '설정',
|
|
2805
|
-
Sound: '사운드',
|
|
2806
|
-
Spin: '스핀',
|
|
2807
|
-
'Spin automatically a set number of times.': '정해진 횟수만큼 자동으로 스핀합니다.',
|
|
2808
|
-
'Speed up spin animations.': '스핀 애니메이션을 빠르게 합니다.',
|
|
2809
|
-
Start: '시작',
|
|
2810
|
-
'Start a spin at the current bet.': '현재 베팅으로 스핀을 시작합니다.',
|
|
2811
|
-
'Start replay': '다시보기 시작',
|
|
2812
|
-
'Total win': '총 당첨',
|
|
2813
|
-
Turbo: '터보',
|
|
2814
|
-
'Ways to win': '당첨 방법',
|
|
2815
|
-
Win: '당첨',
|
|
2816
|
-
'Winning shapes': '당첨 패턴',
|
|
2817
|
-
},
|
|
2818
|
-
pl: {
|
|
2819
|
-
DISCLAIMER: 'Zastrzeżenie',
|
|
2820
|
-
Activate: 'Aktywuj',
|
|
2821
|
-
Autoplay: 'Autoplay',
|
|
2822
|
-
Balance: 'Saldo',
|
|
2823
|
-
Bet: 'Zakład',
|
|
2824
|
-
'BUY BONUS': 'KUP BONUS',
|
|
2825
|
-
Buy: 'Kup',
|
|
2826
|
-
'Buy bonus': 'Kup bonus',
|
|
2827
|
-
Cancel: 'Anuluj',
|
|
2828
|
-
Close: 'Zamknij',
|
|
2829
|
-
'Cluster pays': 'Wypłaty klastrowe',
|
|
2830
|
-
Confirm: 'Potwierdź',
|
|
2831
|
-
Controls: 'Sterowanie',
|
|
2832
|
-
'Decrease your stake.': 'Zmniejsz swój zakład.',
|
|
2833
|
-
DISABLE: 'WYŁĄCZ',
|
|
2834
|
-
'Dismiss the current overlay.': 'Zamknij bieżące okno.',
|
|
2835
|
-
'Free spins': 'Darmowe spiny',
|
|
2836
|
-
Game: 'Gra',
|
|
2837
|
-
'Game info': 'Informacje o grze',
|
|
2838
|
-
Hotkeys: 'Skróty klawiaturowe',
|
|
2839
|
-
'Increase your stake.': 'Zwiększ swój zakład.',
|
|
2840
|
-
'Lower bet': 'Obniż zakład',
|
|
2841
|
-
'Master volume': 'Głośność główna',
|
|
2842
|
-
'Max win': 'Maks. wygrana',
|
|
2843
|
-
Menu: 'Menu',
|
|
2844
|
-
'Menu & info': 'Menu i informacje',
|
|
2845
|
-
Modes: 'Tryby',
|
|
2846
|
-
Music: 'Muzyka',
|
|
2847
|
-
Mute: 'Wycisz',
|
|
2848
|
-
'Mute or unmute the game.': 'Wycisz lub odcisz dźwięk gry.',
|
|
2849
|
-
Navigate: 'Nawiguj',
|
|
2850
|
-
'Open settings and game info.': 'Otwórz ustawienia i informacje o grze.',
|
|
2851
|
-
'Open the paytable and rules.': 'Otwórz tabelę wygranych i zasady.',
|
|
2852
|
-
'Pay a fixed cost to enter a bonus feature.': 'Zapłać stałą kwotę, aby wejść do funkcji bonusowej.',
|
|
2853
|
-
Paylines: 'Linie wygrywające',
|
|
2854
|
-
Paytable: 'Tabela wygranych',
|
|
2855
|
-
'Pays anywhere': 'Wypłaca wszędzie',
|
|
2856
|
-
Price: 'Cena',
|
|
2857
|
-
'Raise bet': 'Podnieś zakład',
|
|
2858
|
-
Replay: 'Odtwórz ponownie',
|
|
2859
|
-
RTP: 'RTP',
|
|
2860
|
-
SFX: 'Efekty dźwiękowe',
|
|
2861
|
-
Settings: 'Ustawienia',
|
|
2862
|
-
Sound: 'Dźwięk',
|
|
2863
|
-
Spin: 'Zakręć',
|
|
2864
|
-
'Spin automatically a set number of times.': 'Obracaj automatycznie określoną liczbę razy.',
|
|
2865
|
-
'Speed up spin animations.': 'Przyspiesz animacje obrotów.',
|
|
2866
|
-
Start: 'Start',
|
|
2867
|
-
'Start a spin at the current bet.': 'Rozpocznij obrót przy bieżącym zakładzie.',
|
|
2868
|
-
'Start replay': 'Rozpocznij odtwarzanie',
|
|
2869
|
-
'Total win': 'Łączna wygrana',
|
|
2870
|
-
Turbo: 'Turbo',
|
|
2871
|
-
'Ways to win': 'Sposoby wygrywania',
|
|
2872
|
-
Win: 'Wygrana',
|
|
2873
|
-
'Winning shapes': 'Wzory wygrywające',
|
|
2874
|
-
},
|
|
2875
|
-
pt: {
|
|
2876
|
-
DISCLAIMER: 'Aviso legal',
|
|
2877
|
-
Activate: 'Ativar',
|
|
2878
|
-
Autoplay: 'Giro automático',
|
|
2879
|
-
Balance: 'Saldo',
|
|
2880
|
-
Bet: 'Aposta',
|
|
2881
|
-
'BUY BONUS': 'COMPRAR BÔNUS',
|
|
2882
|
-
Buy: 'Comprar',
|
|
2883
|
-
'Buy bonus': 'Comprar bônus',
|
|
2884
|
-
Cancel: 'Cancelar',
|
|
2885
|
-
Close: 'Fechar',
|
|
2886
|
-
'Cluster pays': 'Pagamento em cluster',
|
|
2887
|
-
Confirm: 'Confirmar',
|
|
2888
|
-
Controls: 'Controles',
|
|
2889
|
-
'Decrease your stake.': 'Diminuir aposta.',
|
|
2890
|
-
DISABLE: 'DESATIVAR',
|
|
2891
|
-
'Dismiss the current overlay.': 'Fechar o painel atual.',
|
|
2892
|
-
'Free spins': 'Giros grátis',
|
|
2893
|
-
Game: 'Jogo',
|
|
2894
|
-
'Game info': 'Info do jogo',
|
|
2895
|
-
Hotkeys: 'Atalhos de teclado',
|
|
2896
|
-
'Increase your stake.': 'Aumentar aposta.',
|
|
2897
|
-
'Lower bet': 'Baixar aposta',
|
|
2898
|
-
'Master volume': 'Volume principal',
|
|
2899
|
-
'Max win': 'Ganho máximo',
|
|
2900
|
-
Menu: 'Menu',
|
|
2901
|
-
'Menu & info': 'Menu e info',
|
|
2902
|
-
Modes: 'Modos',
|
|
2903
|
-
Music: 'Música',
|
|
2904
|
-
Mute: 'Silenciar',
|
|
2905
|
-
'Mute or unmute the game.': 'Ativar ou silenciar o som do jogo.',
|
|
2906
|
-
Navigate: 'Navegar',
|
|
2907
|
-
'Open settings and game info.': 'Abrir configurações e info do jogo.',
|
|
2908
|
-
'Open the paytable and rules.': 'Abrir tabela de pagamentos e regras.',
|
|
2909
|
-
'Pay a fixed cost to enter a bonus feature.': 'Pague um custo fixo para entrar numa funcionalidade de bônus.',
|
|
2910
|
-
Paylines: 'Linhas de pagamento',
|
|
2911
|
-
Paytable: 'Tabela de pagamentos',
|
|
2912
|
-
'Pays anywhere': 'Paga em qualquer posição',
|
|
2913
|
-
Price: 'Preço',
|
|
2914
|
-
'Raise bet': 'Aumentar aposta',
|
|
2915
|
-
Replay: 'Repetir',
|
|
2916
|
-
RTP: 'RTP',
|
|
2917
|
-
SFX: 'Efeitos sonoros',
|
|
2918
|
-
Settings: 'Configurações',
|
|
2919
|
-
Sound: 'Som',
|
|
2920
|
-
Spin: 'Girar',
|
|
2921
|
-
'Spin automatically a set number of times.': 'Girar automaticamente um número definido de vezes.',
|
|
2922
|
-
'Speed up spin animations.': 'Acelerar as animações de giro.',
|
|
2923
|
-
Start: 'Iniciar',
|
|
2924
|
-
'Start a spin at the current bet.': 'Iniciar um giro com a aposta atual.',
|
|
2925
|
-
'Start replay': 'Iniciar repetição',
|
|
2926
|
-
'Total win': 'Ganho total',
|
|
2927
|
-
Turbo: 'Turbo',
|
|
2928
|
-
'Ways to win': 'Formas de ganhar',
|
|
2929
|
-
Win: 'Ganho',
|
|
2930
|
-
'Winning shapes': 'Figuras vencedoras',
|
|
2931
|
-
},
|
|
2932
|
-
ru: {
|
|
2933
|
-
DISCLAIMER: 'Отказ от ответственности',
|
|
2934
|
-
Activate: 'Активировать',
|
|
2935
|
-
Autoplay: 'Автоигра',
|
|
2936
|
-
Balance: 'Баланс',
|
|
2937
|
-
Bet: 'Ставка',
|
|
2938
|
-
'BUY BONUS': 'КУПИТЬ БОНУС',
|
|
2939
|
-
Buy: 'Купить',
|
|
2940
|
-
'Buy bonus': 'Купить бонус',
|
|
2941
|
-
Cancel: 'Отмена',
|
|
2942
|
-
Close: 'Закрыть',
|
|
2943
|
-
'Cluster pays': 'Кластерные выплаты',
|
|
2944
|
-
Confirm: 'Подтвердить',
|
|
2945
|
-
Controls: 'Управление',
|
|
2946
|
-
'Decrease your stake.': 'Уменьшить ставку.',
|
|
2947
|
-
DISABLE: 'ОТКЛЮЧИТЬ',
|
|
2948
|
-
'Dismiss the current overlay.': 'Закрыть текущее окно.',
|
|
2949
|
-
'Free spins': 'Бесплатные вращения',
|
|
2950
|
-
Game: 'Игра',
|
|
2951
|
-
'Game info': 'Информация об игре',
|
|
2952
|
-
Hotkeys: 'Горячие клавиши',
|
|
2953
|
-
'Increase your stake.': 'Увеличить ставку.',
|
|
2954
|
-
'Lower bet': 'Уменьшить ставку',
|
|
2955
|
-
'Master volume': 'Общая громкость',
|
|
2956
|
-
'Max win': 'Макс. выигрыш',
|
|
2957
|
-
Menu: 'Меню',
|
|
2958
|
-
'Menu & info': 'Меню и информация',
|
|
2959
|
-
Modes: 'Режимы',
|
|
2960
|
-
Music: 'Музыка',
|
|
2961
|
-
Mute: 'Отключить звук',
|
|
2962
|
-
'Mute or unmute the game.': 'Включить или отключить звук.',
|
|
2963
|
-
Navigate: 'Навигация',
|
|
2964
|
-
'Open settings and game info.': 'Открыть настройки и информацию об игре.',
|
|
2965
|
-
'Open the paytable and rules.': 'Открыть таблицу выплат и правила.',
|
|
2966
|
-
'Pay a fixed cost to enter a bonus feature.': 'Заплатите фиксированную сумму для входа в бонусный раунд.',
|
|
2967
|
-
Paylines: 'Линии выплат',
|
|
2968
|
-
Paytable: 'Таблица выплат',
|
|
2969
|
-
'Pays anywhere': 'Выплачивает в любом месте',
|
|
2970
|
-
Price: 'Цена',
|
|
2971
|
-
'Raise bet': 'Повысить ставку',
|
|
2972
|
-
Replay: 'Повтор',
|
|
2973
|
-
RTP: 'RTP',
|
|
2974
|
-
SFX: 'Звуковые эффекты',
|
|
2975
|
-
Settings: 'Настройки',
|
|
2976
|
-
Sound: 'Звук',
|
|
2977
|
-
Spin: 'Вращение',
|
|
2978
|
-
'Spin automatically a set number of times.': 'Автоматически вращать заданное количество раз.',
|
|
2979
|
-
'Speed up spin animations.': 'Ускорить анимацию вращений.',
|
|
2980
|
-
Start: 'Начать',
|
|
2981
|
-
'Start a spin at the current bet.': 'Начать вращение с текущей ставкой.',
|
|
2982
|
-
'Start replay': 'Начать повтор',
|
|
2983
|
-
'Total win': 'Общий выигрыш',
|
|
2984
|
-
Turbo: 'Турбо',
|
|
2985
|
-
'Ways to win': 'Способы выигрыша',
|
|
2986
|
-
Win: 'Выигрыш',
|
|
2987
|
-
'Winning shapes': 'Выигрышные комбинации',
|
|
2988
|
-
},
|
|
2989
|
-
tr: {
|
|
2990
|
-
DISCLAIMER: 'Yasal uyarı',
|
|
2991
|
-
Activate: 'Etkinleştir',
|
|
2992
|
-
Autoplay: 'Otomatik Oyun',
|
|
2993
|
-
Balance: 'Bakiye',
|
|
2994
|
-
Bet: 'Bahis',
|
|
2995
|
-
'BUY BONUS': 'BONUS SATIN AL',
|
|
2996
|
-
Buy: 'Satın al',
|
|
2997
|
-
'Buy bonus': 'Bonus satın al',
|
|
2998
|
-
Cancel: 'İptal',
|
|
2999
|
-
Close: 'Kapat',
|
|
3000
|
-
'Cluster pays': 'Küme ödemeleri',
|
|
3001
|
-
Confirm: 'Onayla',
|
|
3002
|
-
Controls: 'Kontroller',
|
|
3003
|
-
'Decrease your stake.': 'Bahsinizi azaltın.',
|
|
3004
|
-
DISABLE: 'DEVRE DIŞI',
|
|
3005
|
-
'Dismiss the current overlay.': 'Mevcut pencereyi kapat.',
|
|
3006
|
-
'Free spins': 'Ücretsiz dönüşler',
|
|
3007
|
-
Game: 'Oyun',
|
|
3008
|
-
'Game info': 'Oyun bilgisi',
|
|
3009
|
-
Hotkeys: 'Klavye kısayolları',
|
|
3010
|
-
'Increase your stake.': 'Bahsinizi artırın.',
|
|
3011
|
-
'Lower bet': 'Bahsi düşür',
|
|
3012
|
-
'Master volume': 'Ana ses',
|
|
3013
|
-
'Max win': 'Maks kazanç',
|
|
3014
|
-
Menu: 'Menü',
|
|
3015
|
-
'Menu & info': 'Menü ve bilgi',
|
|
3016
|
-
Modes: 'Modlar',
|
|
3017
|
-
Music: 'Müzik',
|
|
3018
|
-
Mute: 'Sessiz',
|
|
3019
|
-
'Mute or unmute the game.': 'Oyun sesini aç ya da kapat.',
|
|
3020
|
-
Navigate: 'Gezin',
|
|
3021
|
-
'Open settings and game info.': 'Ayarları ve oyun bilgisini aç.',
|
|
3022
|
-
'Open the paytable and rules.': 'Ödeme tablosunu ve kuralları aç.',
|
|
3023
|
-
'Pay a fixed cost to enter a bonus feature.': 'Bonus özelliğine girmek için sabit bir ücret ödeyin.',
|
|
3024
|
-
Paylines: 'Ödeme çizgileri',
|
|
3025
|
-
Paytable: 'Ödeme tablosu',
|
|
3026
|
-
'Pays anywhere': 'Her yerde kazandırır',
|
|
3027
|
-
Price: 'Fiyat',
|
|
3028
|
-
'Raise bet': 'Bahsi artır',
|
|
3029
|
-
Replay: 'Tekrar oynat',
|
|
3030
|
-
RTP: 'RTP',
|
|
3031
|
-
SFX: 'Ses efektleri',
|
|
3032
|
-
Settings: 'Ayarlar',
|
|
3033
|
-
Sound: 'Ses',
|
|
3034
|
-
Spin: 'Döndür',
|
|
3035
|
-
'Spin automatically a set number of times.': 'Belirlenen sayıda otomatik döndür.',
|
|
3036
|
-
'Speed up spin animations.': 'Döndürme animasyonlarını hızlandır.',
|
|
3037
|
-
Start: 'Başlat',
|
|
3038
|
-
'Start a spin at the current bet.': 'Mevcut bahisle döndürmeyi başlat.',
|
|
3039
|
-
'Start replay': 'Tekrarı başlat',
|
|
3040
|
-
'Total win': 'Toplam kazanç',
|
|
3041
|
-
Turbo: 'Turbo',
|
|
3042
|
-
'Ways to win': 'Kazanma yolları',
|
|
3043
|
-
Win: 'Kazanç',
|
|
3044
|
-
'Winning shapes': 'Kazanan şekiller',
|
|
3045
|
-
},
|
|
3046
|
-
vi: {
|
|
3047
|
-
DISCLAIMER: 'Tuyên bố miễn trừ trách nhiệm',
|
|
3048
|
-
Activate: 'Kích hoạt',
|
|
3049
|
-
Autoplay: 'Tự động quay',
|
|
3050
|
-
Balance: 'Số dư',
|
|
3051
|
-
Bet: 'Cược',
|
|
3052
|
-
'BUY BONUS': 'MUA THƯỞNG',
|
|
3053
|
-
Buy: 'Mua',
|
|
3054
|
-
'Buy bonus': 'Mua thưởng',
|
|
3055
|
-
Cancel: 'Hủy',
|
|
3056
|
-
Close: 'Đóng',
|
|
3057
|
-
'Cluster pays': 'Trả theo cụm',
|
|
3058
|
-
Confirm: 'Xác nhận',
|
|
3059
|
-
Controls: 'Điều khiển',
|
|
3060
|
-
'Decrease your stake.': 'Giảm mức cược.',
|
|
3061
|
-
DISABLE: 'TẮT',
|
|
3062
|
-
'Dismiss the current overlay.': 'Đóng lớp phủ hiện tại.',
|
|
3063
|
-
'Free spins': 'Quay miễn phí',
|
|
3064
|
-
Game: 'Trò chơi',
|
|
3065
|
-
'Game info': 'Thông tin trò chơi',
|
|
3066
|
-
Hotkeys: 'Phím tắt',
|
|
3067
|
-
'Increase your stake.': 'Tăng mức cược.',
|
|
3068
|
-
'Lower bet': 'Giảm cược',
|
|
3069
|
-
'Master volume': 'Âm lượng chính',
|
|
3070
|
-
'Max win': 'Thắng tối đa',
|
|
3071
|
-
Menu: 'Menu',
|
|
3072
|
-
'Menu & info': 'Menu & thông tin',
|
|
3073
|
-
Modes: 'Chế độ',
|
|
3074
|
-
Music: 'Âm nhạc',
|
|
3075
|
-
Mute: 'Tắt âm',
|
|
3076
|
-
'Mute or unmute the game.': 'Tắt hoặc bật âm thanh trò chơi.',
|
|
3077
|
-
Navigate: 'Điều hướng',
|
|
3078
|
-
'Open settings and game info.': 'Mở cài đặt và thông tin trò chơi.',
|
|
3079
|
-
'Open the paytable and rules.': 'Mở bảng trả thưởng và quy tắc.',
|
|
3080
|
-
'Pay a fixed cost to enter a bonus feature.': 'Trả một khoản phí cố định để tham gia tính năng thưởng.',
|
|
3081
|
-
Paylines: 'Đường thắng',
|
|
3082
|
-
Paytable: 'Bảng trả thưởng',
|
|
3083
|
-
'Pays anywhere': 'Trả bất cứ đâu',
|
|
3084
|
-
Price: 'Giá',
|
|
3085
|
-
'Raise bet': 'Tăng cược',
|
|
3086
|
-
Replay: 'Xem lại',
|
|
3087
|
-
RTP: 'RTP',
|
|
3088
|
-
SFX: 'Hiệu ứng âm thanh',
|
|
3089
|
-
Settings: 'Cài đặt',
|
|
3090
|
-
Sound: 'Âm thanh',
|
|
3091
|
-
Spin: 'Quay',
|
|
3092
|
-
'Spin automatically a set number of times.': 'Tự động quay một số lần nhất định.',
|
|
3093
|
-
'Speed up spin animations.': 'Tăng tốc độ hoạt ảnh quay.',
|
|
3094
|
-
Start: 'Bắt đầu',
|
|
3095
|
-
'Start a spin at the current bet.': 'Bắt đầu quay với mức cược hiện tại.',
|
|
3096
|
-
'Start replay': 'Bắt đầu xem lại',
|
|
3097
|
-
'Total win': 'Tổng thắng',
|
|
3098
|
-
Turbo: 'Turbo',
|
|
3099
|
-
'Ways to win': 'Cách thắng',
|
|
3100
|
-
Win: 'Thắng',
|
|
3101
|
-
'Winning shapes': 'Hình thắng',
|
|
3102
|
-
},
|
|
3103
|
-
zh: {
|
|
3104
|
-
DISCLAIMER: '免责声明',
|
|
3105
|
-
Activate: '激活',
|
|
3106
|
-
Autoplay: '自动游戏',
|
|
3107
|
-
Balance: '余额',
|
|
3108
|
-
Bet: '投注',
|
|
3109
|
-
'BUY BONUS': '购买奖励',
|
|
3110
|
-
Buy: '购买',
|
|
3111
|
-
'Buy bonus': '购买奖励',
|
|
3112
|
-
Cancel: '取消',
|
|
3113
|
-
Close: '关闭',
|
|
3114
|
-
'Cluster pays': '集群赔付',
|
|
3115
|
-
Confirm: '确认',
|
|
3116
|
-
Controls: '控制',
|
|
3117
|
-
'Decrease your stake.': '降低投注额。',
|
|
3118
|
-
DISABLE: '禁用',
|
|
3119
|
-
'Dismiss the current overlay.': '关闭当前弹窗。',
|
|
3120
|
-
'Free spins': '免费旋转',
|
|
3121
|
-
Game: '游戏',
|
|
3122
|
-
'Game info': '游戏信息',
|
|
3123
|
-
Hotkeys: '快捷键',
|
|
3124
|
-
'Increase your stake.': '提高投注额。',
|
|
3125
|
-
'Lower bet': '降低投注',
|
|
3126
|
-
'Master volume': '主音量',
|
|
3127
|
-
'Max win': '最大奖金',
|
|
3128
|
-
Menu: '菜单',
|
|
3129
|
-
'Menu & info': '菜单与信息',
|
|
3130
|
-
Modes: '模式',
|
|
3131
|
-
Music: '音乐',
|
|
3132
|
-
Mute: '静音',
|
|
3133
|
-
'Mute or unmute the game.': '静音或取消静音。',
|
|
3134
|
-
Navigate: '导航',
|
|
3135
|
-
'Open settings and game info.': '打开设置和游戏信息。',
|
|
3136
|
-
'Open the paytable and rules.': '打开赔付表和规则。',
|
|
3137
|
-
'Pay a fixed cost to enter a bonus feature.': '支付固定费用以进入奖励功能。',
|
|
3138
|
-
Paylines: '赔付线',
|
|
3139
|
-
Paytable: '赔付表',
|
|
3140
|
-
'Pays anywhere': '任意位置赢',
|
|
3141
|
-
Price: '价格',
|
|
3142
|
-
'Raise bet': '提高投注',
|
|
3143
|
-
Replay: '重播',
|
|
3144
|
-
RTP: 'RTP',
|
|
3145
|
-
SFX: '音效',
|
|
3146
|
-
Settings: '设置',
|
|
3147
|
-
Sound: '声音',
|
|
3148
|
-
Spin: '旋转',
|
|
3149
|
-
'Spin automatically a set number of times.': '自动旋转设定次数。',
|
|
3150
|
-
'Speed up spin animations.': '加速旋转动画。',
|
|
3151
|
-
Start: '开始',
|
|
3152
|
-
'Start a spin at the current bet.': '以当前投注额开始旋转。',
|
|
3153
|
-
'Start replay': '开始重播',
|
|
3154
|
-
'Total win': '总奖金',
|
|
3155
|
-
Turbo: '急速',
|
|
3156
|
-
'Ways to win': '赢法',
|
|
3157
|
-
Win: '奖金',
|
|
3158
|
-
'Winning shapes': '赢利图形',
|
|
3159
|
-
},
|
|
3160
|
-
};
|
|
3161
|
-
|
|
3162
|
-
// Social-casino language. English is the source (and, for now, the only) language; `socialize`
|
|
3163
|
-
// rewrites the restricted gambling vocabulary into social-safe phrasing while preserving case.
|
|
3164
|
-
//
|
|
3165
|
-
// Ordering matters: the longest / most specific phrases are listed first so they win over their
|
|
3166
|
-
// constituent words (e.g. "buy bonus" before "buy", "pay out" before "pay"). The JS alternation
|
|
3167
|
-
// tries entries left-to-right at each position, so a phrase earlier in this list takes priority.
|
|
3168
|
-
//
|
|
3169
|
-
// Conflicting duplicates in the source table are resolved to a single replacement here
|
|
3170
|
-
// (betting→playing, total bet→total play, paid out→won, pays out→win).
|
|
3171
|
-
const RULES = [
|
|
3172
|
-
['be awarded to player’s accounts', 'appear in player’s accounts'],
|
|
3173
|
-
["be awarded to player's accounts", "appear in player's accounts"],
|
|
3174
|
-
['place your bets', 'come and play / join in the game'],
|
|
3175
|
-
['at the cost of', 'for'],
|
|
3176
|
-
['cost of', 'can be played for'],
|
|
3177
|
-
['win feature', 'play feature'],
|
|
3178
|
-
['total bet', 'total play'],
|
|
3179
|
-
['buy bonus', 'get bonus'],
|
|
3180
|
-
['bonus buy', 'bonus / feature'],
|
|
3181
|
-
['pay out', 'win / won'],
|
|
3182
|
-
['paid out', 'won'],
|
|
3183
|
-
['pays out', 'win'],
|
|
3184
|
-
['payout', 'win'], // single word; "pay out" (spaced) is handled above
|
|
3185
|
-
['paytable', 'win table'],
|
|
3186
|
-
['paylines', 'winlines'],
|
|
3187
|
-
['payline', 'winline'],
|
|
3188
|
-
['bet/s', 'play/s'],
|
|
3189
|
-
['betting', 'playing'],
|
|
3190
|
-
['rebet', 'respin'],
|
|
3191
|
-
['stake', 'play amount'],
|
|
3192
|
-
['payer', 'winner'],
|
|
3193
|
-
['bets', 'plays'],
|
|
3194
|
-
['pays', 'wins'],
|
|
3195
|
-
['paid', 'won'],
|
|
3196
|
-
['bought', 'instantly triggered'],
|
|
3197
|
-
['purchase', 'play'],
|
|
3198
|
-
['price', 'play'],
|
|
3199
|
-
['cost', 'play'], // standalone; the "cost of" / "at the cost of" phrases above win first
|
|
3200
|
-
['deposit', 'get coins'],
|
|
3201
|
-
['withdraw', 'redeem'],
|
|
3202
|
-
['currency', 'token'],
|
|
3203
|
-
['gamble', 'play'],
|
|
3204
|
-
['wager', 'play'],
|
|
3205
|
-
['credit', 'balance'],
|
|
3206
|
-
['money', 'coins'],
|
|
3207
|
-
['cash', 'coins'],
|
|
3208
|
-
['fund', 'balance'],
|
|
3209
|
-
['bet', 'play'],
|
|
3210
|
-
['pay', 'win'],
|
|
3211
|
-
['buy', 'play'],
|
|
3212
|
-
];
|
|
3213
|
-
const MAP = new Map(RULES.map(([k, v]) => [k.toLowerCase(), v]));
|
|
3214
|
-
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
|
|
3215
|
-
// Letter-bounded so we only swap whole words/phrases (e.g. "pay" inside "Autoplay" is left alone).
|
|
3216
|
-
const PATTERN = new RegExp(`(?<![A-Za-z])(?:${RULES.map(([k]) => escapeRe(k)).join('|')})(?![A-Za-z])`, 'gi');
|
|
3217
|
-
/** Carry the matched text's capitalisation onto the replacement: ALL CAPS → upper, Capitalised
|
|
3218
|
-
* (first letter) → capitalise the replacement's first letter, otherwise lower-case as written. */
|
|
3219
|
-
function applyCase(match, repl) {
|
|
3220
|
-
const letters = match.replace(/[^A-Za-z]/g, '');
|
|
3221
|
-
if (letters && letters === letters.toUpperCase())
|
|
3222
|
-
return repl.toUpperCase();
|
|
3223
|
-
if (/^[^A-Za-z]*[A-Z]/.test(match))
|
|
3224
|
-
return repl.charAt(0).toUpperCase() + repl.slice(1);
|
|
3225
|
-
return repl;
|
|
3226
|
-
}
|
|
3227
|
-
/** Rewrite restricted gambling terms in `text` to social-safe phrasing, preserving case. */
|
|
3228
|
-
function socialize(text) {
|
|
3229
|
-
return text.replace(PATTERN, (m) => {
|
|
3230
|
-
const repl = MAP.get(m.toLowerCase());
|
|
3231
|
-
return repl == null ? m : applyCase(m, repl);
|
|
3232
|
-
});
|
|
3233
|
-
}
|
|
3234
|
-
const LANGS = ['de', 'en', 'es', 'fi', 'fr', 'hi', 'id', 'ja', 'ko', 'pl', 'pt', 'ru', 'tr', 'vi', 'zh', 'da'];
|
|
3235
|
-
const LANG_SET = new Set(LANGS);
|
|
3236
|
-
function normalizeLang(code) {
|
|
3237
|
-
const base = (code ?? '').toLowerCase().split(/[-_]/)[0];
|
|
3238
|
-
return (LANG_SET.has(base) ? base : 'en');
|
|
3239
|
-
}
|
|
3240
|
-
function createI18n(opts) {
|
|
3241
|
-
const lang = normalizeLang(opts.language);
|
|
3242
|
-
const t = (src) => {
|
|
3243
|
-
if (lang === 'en')
|
|
3244
|
-
return opts.isSocial ? socialize(src) : src;
|
|
3245
|
-
return opts.messages?.[lang]?.[src] ?? LOCALES[lang]?.[src] ?? src;
|
|
3246
|
-
};
|
|
3247
|
-
return { lang, t };
|
|
3248
|
-
}
|
|
3249
|
-
|
|
3250
|
-
const REMOVE_FADE_MS = 300;
|
|
3251
|
-
class GameShell extends EventEmitter {
|
|
3252
|
-
config;
|
|
3253
|
-
state;
|
|
3254
|
-
root;
|
|
3255
|
-
styleEl;
|
|
3256
|
-
barHost = document.createElement('div');
|
|
3257
|
-
modalHost = document.createElement('div');
|
|
3258
|
-
destroyed = false;
|
|
3259
|
-
layout = 'wide';
|
|
3260
|
-
ro = null;
|
|
3261
|
-
prevBalance = 0;
|
|
3262
|
-
prevWin = 0;
|
|
3263
|
-
moneyAnims = [];
|
|
3264
|
-
kbd;
|
|
3265
|
-
i18n;
|
|
3266
|
-
/** onKey handler of the currently open modal/overlay, if any (set in showModal, cleared in closeModal). */
|
|
3267
|
-
modalOnKey = undefined;
|
|
3268
|
-
/** Shared sound on/off state — Settings speaker toggle and the Shift+M hotkey stay in sync. The
|
|
3269
|
-
* game listens to `settingChange({ key: 'sound' })` to (un)mute audio. */
|
|
3270
|
-
soundOn = true;
|
|
3271
|
-
/** Set by the open Settings modal so Shift+M live-updates its speaker icon; cleared on close. */
|
|
3272
|
-
soundRefresh = null;
|
|
3273
|
-
constructor(config) {
|
|
3274
|
-
super();
|
|
3275
|
-
this.config = config;
|
|
3276
|
-
this.i18n = createI18n({ language: config.language, isSocial: config.isSocial });
|
|
3277
|
-
this.state = createInitialState(config);
|
|
3278
|
-
this.styleEl = document.createElement('style');
|
|
3279
|
-
this.styleEl.textContent = SHELL_CSS;
|
|
3280
|
-
this.root = document.createElement('div');
|
|
3281
|
-
this.root.id = SHELL_ROOT_ID;
|
|
3282
|
-
this.root.setAttribute('style', buildThemeVars(config.theme));
|
|
3283
|
-
config.mount.append(this.styleEl, this.root);
|
|
3284
|
-
this.barHost.className = 'ge-shell-barhost';
|
|
3285
|
-
this.root.appendChild(this.barHost);
|
|
3286
|
-
this.modalHost.className = 'ge-shell-modalhost';
|
|
3287
|
-
this.root.appendChild(this.modalHost);
|
|
3288
|
-
this.prevBalance = this.state.balance;
|
|
3289
|
-
this.prevWin = this.state.win;
|
|
3290
|
-
this.observeLayout();
|
|
3291
|
-
if (typeof document !== 'undefined') {
|
|
3292
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
3293
|
-
const shell = this;
|
|
3294
|
-
const host = {
|
|
3295
|
-
get state() { return shell.state; },
|
|
3296
|
-
get hotkeysEnabled() { return shell.config.features.hotkeys !== false; },
|
|
3297
|
-
get spacebarEnabled() { return shell.config.features.spacebar !== false; },
|
|
3298
|
-
get turboLevels() { return shell.config.features.turbo; },
|
|
3299
|
-
get autoplayEnabled() { return shell.config.features.autoplay != null; },
|
|
3300
|
-
get buyBonusEnabled() { return shell.config.features.buyBonus !== false; },
|
|
3301
|
-
hasOpenLayer: () => shell.modalHost.childElementCount > 0,
|
|
3302
|
-
routeToLayer: (e) => shell.modalOnKey?.(e) ?? false,
|
|
3303
|
-
spin: () => shell.emit('spin'),
|
|
3304
|
-
stepBet: (dir) => {
|
|
3305
|
-
const next = stepBet(shell.state, dir);
|
|
3306
|
-
if (next === shell.state.bet)
|
|
3307
|
-
return;
|
|
3308
|
-
shell.state.bet = next;
|
|
3309
|
-
shell.emit('betChange', next);
|
|
3310
|
-
shell.render();
|
|
3311
|
-
},
|
|
3312
|
-
toggleAutoplay: () => {
|
|
3313
|
-
if (shell.state.autoplay.active) {
|
|
3314
|
-
shell.state.autoplay = { active: false, remaining: 0 };
|
|
3315
|
-
shell.emit('autoplayStop');
|
|
3316
|
-
shell.render();
|
|
3317
|
-
}
|
|
3318
|
-
else {
|
|
3319
|
-
shell.openAutoplayPicker();
|
|
3320
|
-
}
|
|
3321
|
-
},
|
|
3322
|
-
cycleTurbo: () => {
|
|
3323
|
-
const next = nextTurbo(shell.state.turbo, shell.config.features.turbo);
|
|
3324
|
-
shell.state.turbo = next;
|
|
3325
|
-
shell.emit('turboChange', next);
|
|
3326
|
-
shell.render();
|
|
3327
|
-
},
|
|
3328
|
-
openBuyBonus: () => shell.openBuyBonus(),
|
|
3329
|
-
openInfo: () => shell.openInfo(),
|
|
3330
|
-
openMenu: () => shell.openMenu(),
|
|
3331
|
-
toggleMute: () => shell.setSound(!shell.soundOn),
|
|
3332
|
-
closeLayer: () => shell.closeModal(),
|
|
3333
|
-
};
|
|
3334
|
-
this.kbd = new KeyboardController(host);
|
|
3335
|
-
this.kbd.attach();
|
|
3336
|
-
// Stake serves the game in an iframe; on first paint focus is on the HOST page, so a `document`
|
|
3337
|
-
// keydown never fires and Space scrolls the parent. Pull window focus into the iframe on the
|
|
3338
|
-
// first pointer interaction so the spacebar shortcut works. Harmless on full-page Energy8.
|
|
3339
|
-
document.addEventListener('pointerdown', this.pullFocus, true);
|
|
3340
|
-
}
|
|
3341
|
-
this.render();
|
|
3342
|
-
// re-fit once the bundled webfont swaps in (text metrics change → row width changes)
|
|
3343
|
-
if (typeof document !== 'undefined' && document.fonts) {
|
|
3344
|
-
document.fonts.ready.then(() => { if (!this.destroyed)
|
|
3345
|
-
this.applyFitScale(); });
|
|
3346
|
-
}
|
|
3347
|
-
}
|
|
3348
|
-
render() {
|
|
3349
|
-
if (this.destroyed)
|
|
3350
|
-
return;
|
|
3351
|
-
this.cancelMoneyAnims(); // stop in-flight count-ups before their nodes are torn down below
|
|
3352
|
-
this.root.classList.toggle('ge-mobile', this.layout === 'mobile');
|
|
3353
|
-
this.barHost.innerHTML = '';
|
|
3354
|
-
this.barHost.appendChild(renderBottomBar(this));
|
|
3355
|
-
this.animateMoney();
|
|
3356
|
-
this.applyFitScale();
|
|
3357
|
-
}
|
|
3358
|
-
cancelMoneyAnims() {
|
|
3359
|
-
for (const cancel of this.moneyAnims)
|
|
3360
|
-
cancel();
|
|
3361
|
-
this.moneyAnims = [];
|
|
3362
|
-
}
|
|
3363
|
-
/** Keep the WIN pill inline between the groups; float it above when it won't fit. */
|
|
3364
|
-
/**
|
|
3365
|
-
* Landscape bar fills the width when it fits. When it overflows, the WIN pill is
|
|
3366
|
-
* lifted above the bar (unscaled, so it stays readable) and the remaining row is
|
|
3367
|
-
* centred and scaled down to fit — keeping the controls as large as possible.
|
|
3368
|
-
*/
|
|
3369
|
-
applyFitScale() {
|
|
3370
|
-
if (this.destroyed)
|
|
3371
|
-
return;
|
|
3372
|
-
const host = this.barHost;
|
|
3373
|
-
const bar = host.querySelector('.ge-shell-bottom');
|
|
3374
|
-
if (!bar)
|
|
3375
|
-
return;
|
|
3376
|
-
// reset to baseline (idempotent — the pill may have been lifted on a prior pass)
|
|
3377
|
-
const pill = host.querySelector('.ge-winpill');
|
|
3378
|
-
if (pill && pill.parentElement === host) { // put a lifted pill back inline
|
|
3379
|
-
const right = bar.querySelector('.ge-zone-right');
|
|
3380
|
-
if (right)
|
|
3381
|
-
bar.insertBefore(pill, right);
|
|
3382
|
-
else
|
|
3383
|
-
bar.appendChild(pill);
|
|
3384
|
-
pill.classList.remove('ge-up');
|
|
3385
|
-
}
|
|
3386
|
-
host.classList.remove('ge-fit');
|
|
3387
|
-
host.style.transform = '';
|
|
3388
|
-
host.style.transformOrigin = '';
|
|
3389
|
-
// clear any per-zone scale/zoom from a prior pass
|
|
3390
|
-
for (const el of host.querySelectorAll('.ge-zone, .ge-winpill')) {
|
|
3391
|
-
el.style.transform = '';
|
|
3392
|
-
el.style.transformOrigin = '';
|
|
3393
|
-
el.style.removeProperty('zoom');
|
|
3394
|
-
}
|
|
3395
|
-
if (this.layout === 'mobile') {
|
|
3396
|
-
// Shrink the whole stack to fit narrow phones (mobile-s, or big balance/win/total-win
|
|
3397
|
-
// numbers in a row). The rows use space-between, so on overflow their content is
|
|
3398
|
-
// left-anchored and spills off the RIGHT edge — scale from the bottom-left corner so
|
|
3399
|
-
// `avail/need` fits it exactly. (The old centre-origin + 0.7 floor left large numbers
|
|
3400
|
-
// running past the screen edge; the 0.4 floor only guards a degenerate near-zero bar.)
|
|
3401
|
-
let need = 0;
|
|
3402
|
-
for (const row of Array.from(bar.children))
|
|
3403
|
-
need = Math.max(need, row.scrollWidth);
|
|
3404
|
-
const avail = bar.clientWidth;
|
|
3405
|
-
if (need > avail + 1 && avail > 0) {
|
|
3406
|
-
host.style.transformOrigin = 'bottom left';
|
|
3407
|
-
host.style.transform = `scale(${Math.max(0.4, avail / need).toFixed(4)})`;
|
|
3408
|
-
}
|
|
3409
|
-
return;
|
|
3410
|
-
}
|
|
3411
|
-
// ONE fit-scale, from the SCREEN SIZE, applied identically in EVERY mode — switching base⇄replay
|
|
3412
|
-
// must not resize the bar. The factor is the frame WIDTH vs the bar's design width, never the
|
|
3413
|
-
// current mode's content width.
|
|
3414
|
-
//
|
|
3415
|
-
// It's applied with `zoom` (not `transform`): zoom shrinks the LAYOUT, so the zones genuinely
|
|
3416
|
-
// take less room and still sit edge-to-edge (menu hard-left, controls hard-right) even when base's
|
|
3417
|
-
// wide row would overflow a merely-visually-scaled bar — so there is no per-mode centred cluster
|
|
3418
|
-
// and no width/mode branching. A wide WIN pill is still lifted above the row first so it can't
|
|
3419
|
-
// shove the controls off-screen. (Mobile, above, keeps its own stacked fit.)
|
|
3420
|
-
if (pill && bar.scrollWidth > bar.clientWidth + 1) {
|
|
3421
|
-
host.insertBefore(pill, bar);
|
|
3422
|
-
pill.classList.add('ge-up');
|
|
3423
|
-
}
|
|
3424
|
-
const zoomBar = (z) => {
|
|
3425
|
-
const v = z < 0.999 ? z.toFixed(4) : '';
|
|
3426
|
-
for (const el of host.querySelectorAll('.ge-zone, .ge-winpill')) {
|
|
3427
|
-
if (v)
|
|
3428
|
-
el.style.setProperty('zoom', v);
|
|
3429
|
-
else
|
|
3430
|
-
el.style.removeProperty('zoom');
|
|
3431
|
-
}
|
|
3432
|
-
};
|
|
3433
|
-
const s = Math.max(GameShell.BAR_MIN_SCALE, Math.min(1, this.root.clientWidth / GameShell.BAR_REF_WIDTH));
|
|
3434
|
-
zoomBar(s);
|
|
3435
|
-
// Safety: a pathologically long balance/win can still overflow the frame at the screen zoom —
|
|
3436
|
-
// nudge the zoom down just enough that the far control (turbo) isn't clipped. Normal content
|
|
3437
|
-
// never triggers this, so base and replay keep the SAME zoom (no size change on mode switch).
|
|
3438
|
-
if (bar.scrollWidth > bar.clientWidth + 1 && bar.scrollWidth > 0) {
|
|
3439
|
-
zoomBar(s * (bar.clientWidth / bar.scrollWidth));
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
/** Pull window focus into the iframe on first pointer interaction so `document` keydown (the
|
|
3443
|
-
* spacebar shortcut) fires. No-op / harmless when already focused or full-page. */
|
|
3444
|
-
pullFocus = () => { try {
|
|
3445
|
-
window.focus();
|
|
3446
|
-
}
|
|
3447
|
-
catch { /* cross-origin / non-browser */ } };
|
|
3448
|
-
setLayout(layout) {
|
|
3449
|
-
if (layout === this.layout)
|
|
3450
|
-
return;
|
|
3451
|
-
this.layout = layout;
|
|
3452
|
-
this.render();
|
|
3453
|
-
}
|
|
3454
|
-
/** Resolve a built-in shell string through the i18n resolver (translation + optional socialize). */
|
|
3455
|
-
t(text) { return this.i18n.t(text); }
|
|
3456
|
-
/** Toggle the social vocabulary at runtime (rebuilds resolver, re-renders bar). */
|
|
3457
|
-
setSocial(isSocial) {
|
|
3458
|
-
this.config.isSocial = isSocial;
|
|
3459
|
-
this.i18n = createI18n({ language: this.config.language, isSocial });
|
|
3460
|
-
this.render();
|
|
3461
|
-
}
|
|
3462
|
-
/** Swap the active language at runtime (rebuilds resolver, re-renders bar). */
|
|
3463
|
-
setLanguage(lang) {
|
|
3464
|
-
this.config.language = lang;
|
|
3465
|
-
this.i18n = createI18n({ language: lang, isSocial: this.config.isSocial });
|
|
3466
|
-
this.render();
|
|
3467
|
-
}
|
|
3468
|
-
/** Recolour the shell at runtime (e.g. switch dark/light scheme). */
|
|
3469
|
-
setTheme(theme) {
|
|
3470
|
-
this.config.theme = theme;
|
|
3471
|
-
this.root.setAttribute('style', buildThemeVars(theme));
|
|
3472
|
-
}
|
|
3473
|
-
observeLayout() {
|
|
3474
|
-
const RO = globalThis.ResizeObserver;
|
|
3475
|
-
if (typeof RO !== 'function')
|
|
3476
|
-
return; // jsdom: stays 'wide'
|
|
3477
|
-
this.ro = new RO((entries) => {
|
|
3478
|
-
const rect = entries[0]?.contentRect;
|
|
3479
|
-
const w = rect?.width ?? 0, h = rect?.height ?? 0;
|
|
3480
|
-
// portrait → stacked mobile; landscape (incl. popouts) → one row, scaled to fit if it overflows
|
|
3481
|
-
this.setLayout(w !== 0 && h > w ? 'mobile' : 'wide');
|
|
3482
|
-
this.applyFitScale();
|
|
3483
|
-
this.fitModals(); // re-scale open card modals when the popout resizes
|
|
3484
|
-
});
|
|
3485
|
-
this.ro.observe(this.root);
|
|
3486
|
-
}
|
|
3487
|
-
animateMoney() {
|
|
3488
|
-
const fmt = (n) => formatCurrency(n, this.config.currency);
|
|
3489
|
-
const fmtWin = (n) => formatCurrency(n, this.config.currency, true); // win: variable decimals
|
|
3490
|
-
const bal = this.barHost.querySelector('[data-ge="balance"]');
|
|
3491
|
-
const win = this.barHost.querySelector('[data-ge="win"]');
|
|
3492
|
-
if (bal && this.state.balance !== this.prevBalance)
|
|
3493
|
-
this.moneyAnims.push(animateReadout(bal, this.prevBalance, this.state.balance, fmt));
|
|
3494
|
-
if (win && this.state.win !== this.prevWin)
|
|
3495
|
-
this.moneyAnims.push(animateReadout(win, this.prevWin, this.state.win, fmtWin));
|
|
3496
|
-
this.prevBalance = this.state.balance;
|
|
3497
|
-
this.prevWin = this.state.win;
|
|
3498
|
-
}
|
|
3499
|
-
setBalance(n) { this.state.balance = n; this.render(); }
|
|
3500
|
-
setWin(n) { this.state.win = n; this.render(); }
|
|
3501
|
-
setBet(n) { this.state.bet = n; this.render(); }
|
|
3502
|
-
setMode(mode) {
|
|
3503
|
-
if (mode === 'replay')
|
|
3504
|
-
this.state.replay = true; // sticky: a replay stays a replay across modes
|
|
3505
|
-
this.state.mode = mode;
|
|
3506
|
-
this.render();
|
|
3507
|
-
}
|
|
3508
|
-
setBusy(busy) { this.state.busy = busy; this.render(); this.kbd?.notifyBusyChanged(busy); }
|
|
3509
|
-
setAutoplay(a) { this.state.autoplay = a; this.render(); }
|
|
3510
|
-
setTurbo(level) { this.state.turbo = level; this.render(); }
|
|
3511
|
-
/** Currency-aware money formatter for WIN amounts (variable decimals: 0.0041 stays 0.0041, not
|
|
3512
|
-
* 0.00). The host hands this to a scene so games format money without knowing the currency. */
|
|
3513
|
-
formatWin(value) { return formatCurrency(value, this.config.currency, true); }
|
|
3514
|
-
setBuyBonusEnabled(enabled) { this.state.buyBonusEnabled = enabled; this.render(); }
|
|
3515
|
-
setFreeSpins(fs) { this.state.freeSpins = fs; this.render(); }
|
|
3516
|
-
showModal(el, onKey) {
|
|
3517
|
-
// The control that opened this overlay (menu/buy/auto) keeps DOM focus. Drop it, or a
|
|
3518
|
-
// stray Space/Enter would natively re-activate that <button> and rebuild the modal — a
|
|
3519
|
-
// visible flicker. Only relinquish focus we own (a shell control), never the host page's.
|
|
3520
|
-
const active = document.activeElement;
|
|
3521
|
-
if (active && this.root.contains(active))
|
|
3522
|
-
active.blur();
|
|
3523
|
-
this.modalHost.innerHTML = '';
|
|
3524
|
-
this.modalHost.appendChild(el);
|
|
3525
|
-
this.modalOnKey = onKey;
|
|
3526
|
-
this.fitModals();
|
|
3527
|
-
}
|
|
3528
|
-
/** Uniformly scale every open centred card modal (`.ge-sheet`) down so it fits a short/narrow
|
|
3529
|
-
* popout — the same idea as the bar's fit-scale. Covers the pickers, generic + replay modals,
|
|
3530
|
-
* AND the buy-bonus confirm (which is hosted inside the overlay, not directly in modalHost).
|
|
3531
|
-
* Full-screen overlays handle their own responsiveness (scroll + vh-clamp). */
|
|
3532
|
-
fitModals() {
|
|
3533
|
-
if (this.destroyed)
|
|
3534
|
-
return;
|
|
3535
|
-
this.modalHost.querySelectorAll('.ge-sheet').forEach((el) => this.fitSheet(el));
|
|
3536
|
-
}
|
|
3537
|
-
/** Fraction of the frame a card modal may occupy; the rest is breathing-room margin. Keeps
|
|
3538
|
-
* modals from filling a small popout edge-to-edge (so even short pickers scale down there). */
|
|
3539
|
-
static MODAL_FIT = 0.86;
|
|
3540
|
-
/** The bar's design width (px). When the frame is narrower, the bar fit-scales DOWN with the
|
|
3541
|
-
* screen — the SAME factor in every mode, so replay/free-spins shrink like base instead of
|
|
3542
|
-
* staying full-size on a popout. */
|
|
3543
|
-
static BAR_REF_WIDTH = 840;
|
|
3544
|
-
/** Lower bound on the bar fit-scale (guards a degenerate near-zero frame). */
|
|
3545
|
-
static BAR_MIN_SCALE = 0.5;
|
|
3546
|
-
fitSheet(root) {
|
|
3547
|
-
const card = root.querySelector('.ge-modal-card');
|
|
3548
|
-
if (!card)
|
|
3549
|
-
return;
|
|
3550
|
-
card.style.transform = ''; // reset before measuring the natural size
|
|
3551
|
-
const availW = root.clientWidth, availH = root.clientHeight;
|
|
3552
|
-
const w = card.offsetWidth, h = card.offsetHeight;
|
|
3553
|
-
if (w <= 0 || h <= 0 || availW <= 0 || availH <= 0)
|
|
3554
|
-
return;
|
|
3555
|
-
const fit = GameShell.MODAL_FIT;
|
|
3556
|
-
const s = Math.min(1, (availW * fit) / w, (availH * fit) / h);
|
|
3557
|
-
if (s < 0.999)
|
|
3558
|
-
card.style.transform = `scale(${s.toFixed(4)})`;
|
|
3559
|
-
}
|
|
3560
|
-
/** Activate a `feature` option (e.g. Ante): the bar shows the effective bet, tinted with
|
|
3561
|
-
* the feature accent, and BUY BONUS becomes DISABLE. */
|
|
3562
|
-
activateFeature(bonus) {
|
|
3563
|
-
this.state.activeFeature = bonus;
|
|
3564
|
-
this.emit('featureActivate', { id: bonus.id });
|
|
3565
|
-
this.render();
|
|
3566
|
-
}
|
|
3567
|
-
/** Clear the active feature — reverts the bet readout and the BUY BONUS button. */
|
|
3568
|
-
deactivateFeature() {
|
|
3569
|
-
const prev = this.state.activeFeature;
|
|
3570
|
-
if (!prev)
|
|
3571
|
-
return;
|
|
3572
|
-
this.state.activeFeature = null;
|
|
3573
|
-
this.emit('featureDeactivate', { id: prev.id });
|
|
3574
|
-
this.render();
|
|
3575
|
-
}
|
|
3576
|
-
openMenu() { this.emit('menuOpen'); this.openSettings(); }
|
|
3577
|
-
openSettings() { this.emit('settingsOpen'); this.showModal(openSettingsModal(this)); }
|
|
3578
|
-
openInfo() { this.emit('infoOpen'); const { root, onKey } = openGameInfoModal(this); this.showModal(root, onKey); }
|
|
3579
|
-
openBuyBonus() {
|
|
3580
|
-
if (this.config.onBonusBuy) {
|
|
3581
|
-
this.config.onBonusBuy();
|
|
3582
|
-
return;
|
|
3583
|
-
} // game handles it (own UI)
|
|
3584
|
-
const result = openBuyBonusOverlay(this);
|
|
3585
|
-
if (result)
|
|
3586
|
-
this.showModal(result.root, result.onKey);
|
|
3587
|
-
}
|
|
3588
|
-
/** Open a generic, externally-driven modal (title + body + optional action buttons).
|
|
3589
|
-
* Each action runs its `on` then closes; the ✕ shows when `availableClose` is true. */
|
|
3590
|
-
openModal(opts) { this.showModal(buildModal(opts), opts.onKey); }
|
|
3591
|
-
/** Programmatically dismiss whatever modal/overlay is currently shown (e.g. auto-close the
|
|
3592
|
-
* reconnect overlay once the link is restored). No-op when nothing is open. */
|
|
3593
|
-
closeModal() { this.modalOnKey = undefined; this.soundRefresh = null; this.modalHost.innerHTML = ''; }
|
|
3594
|
-
/** Flip the shared sound state, notify the game (`settingChange({ key: 'sound' })`), and live-update
|
|
3595
|
-
* the Settings speaker icon if that modal is open. Used by both the Settings toggle and Shift+M. */
|
|
3596
|
-
setSound(on) {
|
|
3597
|
-
this.soundOn = on;
|
|
3598
|
-
this.emit('settingChange', { key: 'sound', value: on });
|
|
3599
|
-
this.soundRefresh?.(on);
|
|
3600
|
-
}
|
|
3601
|
-
/** The Settings modal registers an icon-updater while open (cleared on close). */
|
|
3602
|
-
setSoundRefresh(fn) { this.soundRefresh = fn; }
|
|
3603
|
-
/** Open the non-dismissable replay summary modal (START REPLAY → onReplay → reopen). */
|
|
3604
|
-
openReplay(opts) {
|
|
3605
|
-
if (this.destroyed)
|
|
3606
|
-
return;
|
|
3607
|
-
this.showModal(buildReplayModal(this, opts));
|
|
3608
|
-
}
|
|
3609
|
-
/** Bet picker — list of available bets with an accent Confirm. */
|
|
3610
|
-
openBetPicker() { const { root, onKey } = openBetModal(this); this.showModal(root, onKey); }
|
|
3611
|
-
/** Autoplay picker — spin-count list; Confirm starts autoplay. */
|
|
3612
|
-
openAutoplayPicker() { const { root, onKey } = openAutoplayModal(this); this.showModal(root, onKey); }
|
|
3613
|
-
destroy() {
|
|
3614
|
-
if (this.destroyed)
|
|
3615
|
-
return Promise.resolve();
|
|
3616
|
-
this.destroyed = true;
|
|
3617
|
-
this.ro?.disconnect();
|
|
3618
|
-
this.ro = null;
|
|
3619
|
-
if (typeof document !== 'undefined') {
|
|
3620
|
-
this.kbd?.detach();
|
|
3621
|
-
document.removeEventListener('pointerdown', this.pullFocus, true);
|
|
3622
|
-
}
|
|
3623
|
-
this.cancelMoneyAnims();
|
|
3624
|
-
this.removeAllListeners();
|
|
3625
|
-
this.root.classList.add('ge-shell-hidden');
|
|
3626
|
-
return new Promise((resolve) => {
|
|
3627
|
-
setTimeout(() => {
|
|
3628
|
-
this.root.remove();
|
|
3629
|
-
this.styleEl.remove();
|
|
3630
|
-
resolve();
|
|
3631
|
-
}, REMOVE_FADE_MS);
|
|
3632
|
-
});
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
/** Count-up the trailing text node of a .ge-rd readout (keeps its label span).
|
|
3636
|
-
* Returns the count-up canceler so the shell can stop it before the node is replaced. */
|
|
3637
|
-
function animateReadout(el, from, to, fmt) {
|
|
3638
|
-
const textNode = el.lastChild;
|
|
3639
|
-
if (!textNode || textNode.nodeType !== Node.TEXT_NODE) {
|
|
3640
|
-
el.textContent = fmt(to);
|
|
3641
|
-
return () => { };
|
|
3642
|
-
}
|
|
3643
|
-
const proxy = {
|
|
3644
|
-
set textContent(v) { textNode.data = v; },
|
|
3645
|
-
get textContent() { return textNode.data; },
|
|
3646
|
-
};
|
|
3647
|
-
return countUp(proxy, from, to, fmt);
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
let active = null;
|
|
3651
|
-
function createGameShell(config) {
|
|
3652
|
-
if (active)
|
|
3653
|
-
return active;
|
|
3654
|
-
active = new GameShell(config);
|
|
3655
|
-
return active;
|
|
3656
|
-
}
|
|
3657
|
-
function removeGameShell() {
|
|
3658
|
-
if (!active)
|
|
3659
|
-
return Promise.resolve();
|
|
3660
|
-
const shell = active;
|
|
3661
|
-
active = null;
|
|
3662
|
-
return shell.destroy();
|
|
3663
|
-
}
|
|
3664
|
-
|
|
3665
|
-
exports.GameShell = GameShell;
|
|
3666
|
-
exports.createGameShell = createGameShell;
|
|
3667
|
-
exports.createI18n = createI18n;
|
|
3668
|
-
exports.normalizeLang = normalizeLang;
|
|
3669
|
-
exports.removeGameShell = removeGameShell;
|
|
3670
|
-
exports.socialize = socialize;
|
|
3671
|
-
//# sourceMappingURL=shell.cjs.js.map
|