@energy8platform/platform-core 0.22.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs.js +60 -46
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.esm.js +60 -46
- package/dist/index.esm.js.map +1 -1
- package/dist/shell.cjs.js +60 -46
- package/dist/shell.cjs.js.map +1 -1
- package/dist/shell.d.ts +0 -1
- package/dist/shell.esm.js +60 -46
- package/dist/shell.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/shell/components/BottomBar.ts +20 -15
- package/src/shell/components/GameInfo.ts +7 -7
- package/src/shell/i18n.ts +1 -0
- package/src/shell/shell.css.ts +31 -22
- package/src/shell/state.ts +1 -1
- package/src/shell/types.ts +0 -1
|
@@ -43,10 +43,13 @@ export function renderBottomBar(shell: GameShell): HTMLElement {
|
|
|
43
43
|
// menu icon button (always)
|
|
44
44
|
const menu = iconBtn('menu', 'menu', () => shell.openMenu());
|
|
45
45
|
|
|
46
|
-
// All three modes share the base plaque layout. FS/replay hide the controls that
|
|
47
|
-
//
|
|
46
|
+
// All three modes share the base plaque layout. FS/replay hide the controls that don't apply
|
|
47
|
+
// and add Free Spins + Total Win blocks on the left; the per-spin WIN uses the base pill.
|
|
48
48
|
const isBase = state.mode === 'base';
|
|
49
49
|
const isFS = state.mode === 'freeSpins';
|
|
50
|
+
// FS always shows the spins counter + accumulated Total Win (even €0); a replay shows them
|
|
51
|
+
// only when it's a free-spins replay (freeSpins.total > 0).
|
|
52
|
+
const showFsBlocks = isFS || (state.mode === 'replay' && state.freeSpins.total > 0);
|
|
50
53
|
|
|
51
54
|
const balance = readout('balance', shell.t('Balance'), fmt(state.balance));
|
|
52
55
|
// With a feature active (e.g. Ante) the BET readout shows the effective stake, tinted with
|
|
@@ -72,22 +75,25 @@ export function renderBottomBar(shell: GameShell): HTMLElement {
|
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
const winEl = state.win > 0 ? readout('win', shell.t('Win'), fmt(state.win)) : null;
|
|
75
|
-
// FS
|
|
76
|
-
const fsCounter =
|
|
77
|
-
const fsTotalWin =
|
|
78
|
-
const fsLastWin = isFS ? readout('fs-lastwin', shell.t('Last win'), fmt(state.freeSpins.lastWin)) : null;
|
|
78
|
+
// FS/replay left blocks: spins counter + accumulated Total Win (shown even at €0).
|
|
79
|
+
const fsCounter = showFsBlocks ? readout('fs-counter', shell.t('Free spins'), `${state.freeSpins.current} / ${state.freeSpins.total}`) : null;
|
|
80
|
+
const fsTotalWin = showFsBlocks ? readout('fs-totalwin', shell.t('Total win'), fmt(state.freeSpins.totalWin)) : null;
|
|
79
81
|
|
|
80
82
|
if (mobile) {
|
|
81
|
-
// rows: [balance · win
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// rows: [balance · win] · [menu · auto · spin · FS counter · Total Win · turbo · buy] · [− bet +]
|
|
84
|
+
// FS counter + Total Win live in the controls row (alongside menu/turbo), not the top readouts.
|
|
85
|
+
bar.appendChild(plaque('ge-m-top ge-pl ge-pl-glass', compact([balance, winEl])));
|
|
86
|
+
const center = isBase ? spin : null;
|
|
87
|
+
bar.appendChild(plaque('ge-m-controls ge-pl-dark', compact([menu, auto, center, fsCounter, fsTotalWin, turbo, buy])));
|
|
85
88
|
bar.appendChild(plaque('ge-m-bet ge-pl ge-pl-dark', compact([betDown, betValue, betUp])));
|
|
86
89
|
} else {
|
|
87
|
-
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance]
|
|
90
|
+
// LEFT: [menu] ⊐ BUY BONUS coin ⊏ [balance] · [Free Spins] · [Total Win]
|
|
91
|
+
// (the last two only render in FS / a free-spins replay)
|
|
88
92
|
const menuPlaque = plaque('ge-pl ge-pl-dark ge-pl-menu', [menu]);
|
|
89
93
|
const balPlaque = plaque('ge-pl ge-pl-glass ge-pl-bal', [balance]);
|
|
90
|
-
const
|
|
94
|
+
const fsPlaque = fsCounter ? plaque('ge-pl ge-pl-glass ge-pl-fs', [fsCounter]) : null;
|
|
95
|
+
const totalWinPlaque = fsTotalWin ? plaque('ge-pl ge-pl-glass ge-pl-totalwin', [fsTotalWin]) : null;
|
|
96
|
+
const left = zone('ge-zone-left ge-zone-plaques', ...compact([menuPlaque, buy, balPlaque, fsPlaque, totalWinPlaque]));
|
|
91
97
|
|
|
92
98
|
// RIGHT: [bet (+ step)] · |divider| · [auto · SPIN · turbo]
|
|
93
99
|
const betKids: HTMLElement[] = [betValue];
|
|
@@ -101,10 +107,9 @@ export function renderBottomBar(shell: GameShell): HTMLElement {
|
|
|
101
107
|
spinWrap.append(...compact([auto, spin, turbo]));
|
|
102
108
|
const right = zone('ge-zone-right ge-zone-plaques', betPlaque, divider, spinWrap);
|
|
103
109
|
|
|
104
|
-
// MIDDLE:
|
|
110
|
+
// MIDDLE: per-spin WIN pill in every mode — lifts above the bar on overflow.
|
|
105
111
|
let middle: HTMLElement | null = null;
|
|
106
|
-
if (
|
|
107
|
-
else if (winEl) { winEl.classList.add('ge-winpill'); middle = winEl; }
|
|
112
|
+
if (winEl) { winEl.classList.add('ge-winpill'); middle = winEl; }
|
|
108
113
|
bar.append(...compact([left, middle, right]));
|
|
109
114
|
}
|
|
110
115
|
|
|
@@ -28,7 +28,7 @@ export function openGameInfoModal(shell: GameShell): HTMLElement {
|
|
|
28
28
|
|
|
29
29
|
function renderSection(shell: GameShell, s: GameInfoSection): HTMLElement {
|
|
30
30
|
switch (s.type) {
|
|
31
|
-
case 'modes': return sectionModes(s.modes, sec('info-modes', s.title, shell.t('Modes')));
|
|
31
|
+
case 'modes': return sectionModes(shell, s.modes, sec('info-modes', s.title, shell.t('Modes')));
|
|
32
32
|
case 'controls': return sectionControls(shell, sec('info-controls', s.title, shell.t('Controls')));
|
|
33
33
|
case 'paytable': return sectionPaytable(s.rows, sec('info-paytable', s.title, shell.t('Paytable')));
|
|
34
34
|
case 'wins': return sectionWins(s, sec('info-wins', s.title, shell.t(winFallbackTitle(s.kind))));
|
|
@@ -46,20 +46,20 @@ function sec(ge: string, title: string | undefined, fallback: string): HTMLEleme
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// ── modes (rows — varying description lengths read better than fixed cards) ────
|
|
49
|
-
function sectionModes(modes: GameMode[], el: HTMLElement): HTMLElement {
|
|
49
|
+
function sectionModes(shell: GameShell, modes: GameMode[], el: HTMLElement): HTMLElement {
|
|
50
50
|
const list = document.createElement('div'); list.className = 'ge-gi-modes';
|
|
51
|
-
for (const m of modes) list.appendChild(modeRow(m));
|
|
51
|
+
for (const m of modes) list.appendChild(modeRow(shell, m));
|
|
52
52
|
el.appendChild(list);
|
|
53
53
|
return el;
|
|
54
54
|
}
|
|
55
|
-
function modeRow(m: GameMode): HTMLElement {
|
|
55
|
+
function modeRow(shell: GameShell, m: GameMode): HTMLElement {
|
|
56
56
|
const row = document.createElement('div'); row.className = 'ge-gi-mode';
|
|
57
57
|
const stat = (label: string, val: string) =>
|
|
58
58
|
`<span class="ge-gi-mode-st"><span>${label}</span><b>${val}</b></span>`;
|
|
59
59
|
let stats = '';
|
|
60
|
-
if (m.price != null) stats += stat('Price', m.price);
|
|
61
|
-
if (typeof m.rtp === 'number') stats += stat('RTP', `${m.rtp}%`);
|
|
62
|
-
if (m.maxWin != null) stats += stat('Max win', m.maxWin);
|
|
60
|
+
if (m.price != null) stats += stat(shell.t('Price'), m.price);
|
|
61
|
+
if (typeof m.rtp === 'number') stats += stat(shell.t('RTP'), `${m.rtp}%`);
|
|
62
|
+
if (m.maxWin != null) stats += stat(shell.t('Max win'), m.maxWin);
|
|
63
63
|
row.innerHTML =
|
|
64
64
|
`<div class="ge-gi-mode-top"><span class="ge-gi-mode-h">${m.title}</span>` +
|
|
65
65
|
(stats ? `<span class="ge-gi-mode-stats">${stats}</span>` : '') + '</div>' +
|
package/src/shell/i18n.ts
CHANGED
package/src/shell/shell.css.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const SHELL_ROOT_ID = '__ge-game-shell__';
|
|
|
7
7
|
export const SHELL_CSS = SHELL_FONT_CSS + `
|
|
8
8
|
#${SHELL_ROOT_ID} {
|
|
9
9
|
position: absolute; inset: 0;
|
|
10
|
+
container-type: size; /* query container → centred modals size in cq units (responsive on every screen) */
|
|
10
11
|
pointer-events: none; z-index: 9000;
|
|
11
12
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
12
13
|
color: var(--shell-fg);
|
|
@@ -218,16 +219,18 @@ export const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
218
219
|
/* the buy-bonus scroll area is a SIZE CONTAINER, so the cards' cqh units measure the overlay
|
|
219
220
|
(the popout frame) and not the browser window — cards fit without any vertical scroll. */
|
|
220
221
|
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-scroll { container-type:size; }
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
/* buy-bonus uses the FULL overlay width (no 800px centre cap) so the card row isn't cropped at
|
|
223
|
+
the sides; small horizontal padding keeps the cards off the screen edges. */
|
|
224
|
+
#${SHELL_ROOT_ID} [data-ge="buybonus-overlay"] .ge-ov-body { max-width:none; padding:clamp(8px,3cqh,16px) clamp(12px,3vw,28px); }
|
|
225
|
+
#${SHELL_ROOT_ID} .ge-bb-grid { display:flex; gap:14px; justify-content:safe center; overflow-x:auto; overflow-y:hidden; padding-bottom:6px;
|
|
223
226
|
scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }
|
|
224
227
|
/* the one knob that scales the whole card — cqh measures the overlay (popout frame), not the
|
|
225
228
|
browser window, so cards shrink to fit the real container height. */
|
|
226
|
-
#${SHELL_ROOT_ID} .ge-bb-grid .ge-bonus-card { flex:0 0
|
|
227
|
-
font-size:clamp(7px,
|
|
229
|
+
#${SHELL_ROOT_ID} .ge-bb-grid .ge-bonus-card { flex:0 0 18em; scroll-snap-align:start;
|
|
230
|
+
font-size:clamp(7px, 3.6cqh, 12px); }
|
|
228
231
|
/* mobile: vertical stack at a fixed, readable size — scroll the list, don't shrink the cards */
|
|
229
232
|
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid { display:flex; flex-direction:column; gap:14px; overflow:visible; }
|
|
230
|
-
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:
|
|
233
|
+
#${SHELL_ROOT_ID}.ge-mobile .ge-bb-grid .ge-bonus-card { flex:0 0 auto; font-size:12px; }
|
|
231
234
|
#${SHELL_ROOT_ID} .ge-bonus-card { display:flex; flex-direction:column; border-radius:1.4em; overflow:hidden;
|
|
232
235
|
background:var(--shell-plaque-glass); border:1px solid var(--shell-plaque-line); color:#fff; text-align:center;
|
|
233
236
|
pointer-events:auto; cursor:pointer; transition:box-shadow .12s ease, background .12s ease; }
|
|
@@ -282,8 +285,10 @@ export const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
282
285
|
border-radius:16px; padding:0 20px; gap:18px; }
|
|
283
286
|
#${SHELL_ROOT_ID} .ge-pl-dark { background:var(--shell-plaque-dark); }
|
|
284
287
|
#${SHELL_ROOT_ID} .ge-pl-glass { background:var(--shell-plaque-glass); }
|
|
285
|
-
/* FS
|
|
286
|
-
|
|
288
|
+
/* FS/replay left blocks — Free Spins counter (compact) + Total Win, standalone glass plaques
|
|
289
|
+
sitting just right of the balance pill */
|
|
290
|
+
#${SHELL_ROOT_ID} .ge-pl-fs, #${SHELL_ROOT_ID} .ge-pl-totalwin { margin-left:8px; }
|
|
291
|
+
#${SHELL_ROOT_ID} .ge-pl-fs { padding:0 16px; }
|
|
287
292
|
#${SHELL_ROOT_ID} .ge-pl .ge-rd { color:#fff; text-shadow:none; }
|
|
288
293
|
#${SHELL_ROOT_ID} .ge-pl .ge-rd .ge-lbl { color:var(--shell-plaque-label); }
|
|
289
294
|
#${SHELL_ROOT_ID} .ge-pl .ge-iconbtn { color:#fff; }
|
|
@@ -332,40 +337,44 @@ export const SHELL_CSS = SHELL_FONT_CSS + `
|
|
|
332
337
|
align-items:center; justify-content:center; padding:clamp(10px,4vh,24px); box-sizing:border-box;
|
|
333
338
|
background:rgba(12,17,28,.5); backdrop-filter:blur(var(--ge-sheet-blur,20px)) saturate(120%);
|
|
334
339
|
-webkit-backdrop-filter:blur(var(--ge-sheet-blur,20px)) saturate(120%); animation:ge-ov-in .16s ease-out; }
|
|
335
|
-
/*
|
|
336
|
-
|
|
337
|
-
|
|
340
|
+
/* Card sizes in cq units of the shell root → responsive on EVERY screen, not just popouts. The
|
|
341
|
+
card's font-size is the one knob (clamped for readability); everything inside is em-relative so
|
|
342
|
+
the whole card scales as a unit. GameShell.fitModal() still transform-scales it down as a
|
|
343
|
+
backstop for very short popouts. */
|
|
344
|
+
#${SHELL_ROOT_ID} .ge-modal-card { font-size:clamp(11px, 2cqmin, 15px); width:100%; max-width:28em; box-sizing:border-box;
|
|
345
|
+
overflow:hidden; transform-origin:center center; background:var(--shell-plaque-solid); border-radius:1.3em;
|
|
346
|
+
display:flex; flex-direction:column; }
|
|
338
347
|
/* ✕ pinned to the overlay corner (the screen), not the card */
|
|
339
348
|
#${SHELL_ROOT_ID} .ge-modal-close { position:absolute; top:12px; right:12px; z-index:2; width:36px; height:36px;
|
|
340
349
|
border:none; border-radius:50%; cursor:pointer; pointer-events:auto; background:var(--shell-plaque-dark); color:#fff;
|
|
341
350
|
display:flex; align-items:center; justify-content:center; font-size:20px; transition:background .12s ease, color .12s ease; }
|
|
342
351
|
#${SHELL_ROOT_ID} .ge-modal-close:hover { background:var(--shell-plaque-glass); color:var(--shell-accent); }
|
|
343
|
-
#${SHELL_ROOT_ID} .ge-modal-body { padding:
|
|
352
|
+
#${SHELL_ROOT_ID} .ge-modal-body { padding:1.2em; display:flex; flex-direction:column; gap:1.05em; }
|
|
344
353
|
#${SHELL_ROOT_ID} .ge-modal-title { margin:0; text-align:center; color:var(--card-acc, var(--shell-accent));
|
|
345
|
-
font-weight:800; letter-spacing:.04em; text-transform:uppercase; font-size:
|
|
346
|
-
#${SHELL_ROOT_ID} .ge-modal-text { margin:0; text-align:center; color:rgba(255,255,255,.85); font-size
|
|
347
|
-
#${SHELL_ROOT_ID} .ge-sheet-grid { display:grid; gap
|
|
354
|
+
font-weight:800; letter-spacing:.04em; text-transform:uppercase; font-size:1.2em; }
|
|
355
|
+
#${SHELL_ROOT_ID} .ge-modal-text { margin:0; text-align:center; color:rgba(255,255,255,.85); font-size:.93em; line-height:1.5; }
|
|
356
|
+
#${SHELL_ROOT_ID} .ge-sheet-grid { display:grid; gap:.65em; }
|
|
348
357
|
#${SHELL_ROOT_ID} .ge-chip { pointer-events:auto; cursor:pointer; border:1px solid var(--shell-plaque-line);
|
|
349
|
-
border-radius
|
|
350
|
-
font-variant-numeric:tabular-nums; padding
|
|
358
|
+
border-radius:.8em; background:rgba(255,255,255,.04); color:#fff; font-size:1em; font-weight:700;
|
|
359
|
+
font-variant-numeric:tabular-nums; padding:.8em .55em; transition:background .12s ease, border-color .12s ease; }
|
|
351
360
|
#${SHELL_ROOT_ID} .ge-chip:hover { background:var(--shell-plaque-glass-hover); }
|
|
352
361
|
#${SHELL_ROOT_ID} .ge-chip.ge-on { border-color:var(--shell-accent); background:var(--shell-accent); color:#fff; }
|
|
353
362
|
/* full-bleed footer button(s), flush to the card's bottom edge (card clips the corners) */
|
|
354
363
|
#${SHELL_ROOT_ID} .ge-modal-actions { display:flex; }
|
|
355
364
|
#${SHELL_ROOT_ID} .ge-modal-actions > * { flex:1; }
|
|
356
|
-
#${SHELL_ROOT_ID} .ge-modal-btn { width:100%; border:none; padding:
|
|
365
|
+
#${SHELL_ROOT_ID} .ge-modal-btn { width:100%; border:none; padding:1.05em; font-size:1em; font-weight:800;
|
|
357
366
|
letter-spacing:.04em; text-transform:uppercase; cursor:pointer; pointer-events:auto; transition:filter .12s ease; }
|
|
358
367
|
#${SHELL_ROOT_ID} .ge-modal-btn:hover:not([disabled]) { filter:brightness(1.08); }
|
|
359
368
|
#${SHELL_ROOT_ID} .ge-modal-btn--accent { background:var(--card-acc, var(--shell-accent)); color:#fff; }
|
|
360
369
|
#${SHELL_ROOT_ID} .ge-modal-btn--ghost { background:var(--shell-plaque-glass-hover); color:#fff; }
|
|
361
370
|
/* replay summary — label/value rows, accented total-win row */
|
|
362
371
|
#${SHELL_ROOT_ID} .ge-replay-rows { display:flex; flex-direction:column; }
|
|
363
|
-
#${SHELL_ROOT_ID} .ge-replay-row { display:flex; justify-content:space-between; align-items:baseline; gap:
|
|
372
|
+
#${SHELL_ROOT_ID} .ge-replay-row { display:flex; justify-content:space-between; align-items:baseline; gap:1.05em; padding:.73em .13em; }
|
|
364
373
|
#${SHELL_ROOT_ID} .ge-replay-row + .ge-replay-row { border-top:1px solid var(--shell-plaque-line); }
|
|
365
|
-
#${SHELL_ROOT_ID} .ge-replay-row span { color:var(--shell-plaque-label); text-transform:uppercase; letter-spacing:.07em; font-size
|
|
366
|
-
#${SHELL_ROOT_ID} .ge-replay-row b { color:#fff; font-weight:800; font-size:
|
|
367
|
-
#${SHELL_ROOT_ID} .ge-replay-total span { color:#fff; font-size
|
|
368
|
-
#${SHELL_ROOT_ID} .ge-replay-total b { color:var(--shell-accent); font-size:
|
|
374
|
+
#${SHELL_ROOT_ID} .ge-replay-row span { color:var(--shell-plaque-label); text-transform:uppercase; letter-spacing:.07em; font-size:.73em; font-weight:700; }
|
|
375
|
+
#${SHELL_ROOT_ID} .ge-replay-row b { color:#fff; font-weight:800; font-size:1em; font-variant-numeric:tabular-nums; }
|
|
376
|
+
#${SHELL_ROOT_ID} .ge-replay-total span { color:#fff; font-size:.8em; }
|
|
377
|
+
#${SHELL_ROOT_ID} .ge-replay-total b { color:var(--shell-accent); font-size:1.27em; }
|
|
369
378
|
|
|
370
379
|
#${SHELL_ROOT_ID}.ge-shell-hidden { opacity:0; pointer-events:none; transition:opacity .25s ease; }
|
|
371
380
|
`;
|
package/src/shell/state.ts
CHANGED
|
@@ -11,7 +11,7 @@ export function createInitialState(config: ShellConfig): ShellState {
|
|
|
11
11
|
autoplay: { active: false, remaining: 0 },
|
|
12
12
|
turbo: 0,
|
|
13
13
|
buyBonusEnabled: true,
|
|
14
|
-
freeSpins: { current: 0, total: 0, totalWin: 0
|
|
14
|
+
freeSpins: { current: 0, total: 0, totalWin: 0 },
|
|
15
15
|
activeFeature: null,
|
|
16
16
|
};
|
|
17
17
|
}
|
package/src/shell/types.ts
CHANGED