@energy8platform/shell 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html.cjs.js +185 -65
- package/dist/html.cjs.js.map +1 -1
- package/dist/html.d.ts +31 -4
- package/dist/html.esm.js +185 -65
- package/dist/html.esm.js.map +1 -1
- package/dist/index.cjs.js +148 -42
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +31 -4
- package/dist/index.esm.js +148 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/pixi.cjs.js +339 -80
- package/dist/pixi.cjs.js.map +1 -1
- package/dist/pixi.d.ts +31 -4
- package/dist/pixi.esm.js +339 -80
- package/dist/pixi.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ShellController.ts +183 -43
- package/src/core/state.ts +1 -0
- package/src/core/types.ts +40 -4
- package/src/core/version.ts +1 -1
- package/src/ui/html/components/BottomBar.ts +71 -32
- package/src/ui/pixi/components/BottomBar.ts +280 -64
|
@@ -16,26 +16,30 @@ import {
|
|
|
16
16
|
import { IconView, makeRingedIcon } from '../pixi-icon';
|
|
17
17
|
|
|
18
18
|
// ── design constants (mirror the DOM `.ge-bar-panel` / mobile rules) ──────────
|
|
19
|
-
const BAR_H = 68;
|
|
20
|
-
const SPIN = 84;
|
|
19
|
+
const BAR_H = 68; // continuous dark panel height
|
|
20
|
+
const SPIN = 84; // hero disc — pops above/below the bar
|
|
21
21
|
const SPIN_POP = (SPIN - BAR_H) / 2; // 8 — how far the disc sticks out top/bottom
|
|
22
|
-
const MAX_BAR_W = 850;
|
|
23
|
-
const OUTER_PAD = 14;
|
|
24
|
-
const PANEL_PAD = 14;
|
|
25
|
-
const ZONE_GAP = 12;
|
|
26
|
-
const ROW_GAP = 10;
|
|
27
|
-
const MID_GAP = 24;
|
|
28
|
-
const BAR_REF_W = 840;
|
|
22
|
+
const MAX_BAR_W = 850; // .ge-shell-bottom max-width
|
|
23
|
+
const OUTER_PAD = 14; // .ge-shell-bottom padding
|
|
24
|
+
const PANEL_PAD = 14; // .ge-bar-panel padding
|
|
25
|
+
const ZONE_GAP = 12; // .ge-zone gap
|
|
26
|
+
const ROW_GAP = 10; // BUY BONUS ↔ panel gap
|
|
27
|
+
const MID_GAP = 24; // minimum gap between the left info group and the right controls
|
|
28
|
+
const BAR_REF_W = 840; // zoom reference width
|
|
29
29
|
const BAR_MIN_SCALE = 0.5;
|
|
30
30
|
const WIDE_PAD_BOTTOM = 8;
|
|
31
31
|
export const WIDE_BAR_H = WIDE_PAD_BOTTOM + SPIN; // ≈92
|
|
32
32
|
|
|
33
|
-
const BUY_W = 62;
|
|
34
|
-
const DISC = 38;
|
|
35
|
-
const FS_BOX = 90;
|
|
33
|
+
const BUY_W = 62; // buy-bonus disc on desktop
|
|
34
|
+
const DISC = 38; // white-disc icon button box (auto/turbo)
|
|
35
|
+
const FS_BOX = 90; // fixed bet-value box width
|
|
36
36
|
|
|
37
37
|
// mobile
|
|
38
|
-
const M_CTRL_H = 62,
|
|
38
|
+
const M_CTRL_H = 62,
|
|
39
|
+
M_INFO_H = 40,
|
|
40
|
+
M_GAP = 10,
|
|
41
|
+
M_PAD_BOTTOM = 8,
|
|
42
|
+
M_SIDE = 10;
|
|
39
43
|
const M_BUY = 50;
|
|
40
44
|
export const MOBILE_BAR_H = M_PAD_BOTTOM + SPIN + M_GAP + M_INFO_H; // spin pops above the controls
|
|
41
45
|
|
|
@@ -53,7 +57,15 @@ class TurboButton extends Container {
|
|
|
53
57
|
private level: number;
|
|
54
58
|
private accent: string;
|
|
55
59
|
|
|
56
|
-
constructor(opts: {
|
|
60
|
+
constructor(opts: {
|
|
61
|
+
size: number;
|
|
62
|
+
glyph: number;
|
|
63
|
+
discFill: string;
|
|
64
|
+
discBorder?: number;
|
|
65
|
+
accent: string;
|
|
66
|
+
level: number;
|
|
67
|
+
onTap: () => void;
|
|
68
|
+
}) {
|
|
57
69
|
super();
|
|
58
70
|
this.box = opts.size;
|
|
59
71
|
this.accent = opts.accent;
|
|
@@ -70,7 +82,11 @@ class TurboButton extends Container {
|
|
|
70
82
|
this.cursor = 'pointer';
|
|
71
83
|
this.hitArea = new Rectangle(0, 0, this.box, this.box);
|
|
72
84
|
this._paint(false);
|
|
73
|
-
attachHover(
|
|
85
|
+
attachHover(
|
|
86
|
+
this,
|
|
87
|
+
() => this._paint(true),
|
|
88
|
+
() => this._paint(false),
|
|
89
|
+
);
|
|
74
90
|
attachPress(this, 0.92, () => opts.onTap());
|
|
75
91
|
}
|
|
76
92
|
|
|
@@ -99,12 +115,28 @@ class TurboButton extends Container {
|
|
|
99
115
|
this._paint(false);
|
|
100
116
|
}
|
|
101
117
|
|
|
102
|
-
measureSize(): { w: number; h: number } {
|
|
103
|
-
|
|
118
|
+
measureSize(): { w: number; h: number } {
|
|
119
|
+
return { w: this.box, h: this.box };
|
|
120
|
+
}
|
|
121
|
+
setLayoutSize(): void {
|
|
122
|
+
/* fixed */
|
|
123
|
+
}
|
|
104
124
|
}
|
|
105
125
|
|
|
106
126
|
/** A readout in the bar (white Oswald value, plaque-label caption, no shadow). */
|
|
107
|
-
function readout(
|
|
127
|
+
function readout(
|
|
128
|
+
host: PixiComponentContext,
|
|
129
|
+
label: string,
|
|
130
|
+
value: string,
|
|
131
|
+
opts: {
|
|
132
|
+
valueSize?: number;
|
|
133
|
+
align?: 'left' | 'center' | 'right';
|
|
134
|
+
fixedWidth?: number;
|
|
135
|
+
maxWidth?: number;
|
|
136
|
+
color?: string;
|
|
137
|
+
muted?: string;
|
|
138
|
+
} = {},
|
|
139
|
+
): Readout {
|
|
108
140
|
return new Readout({
|
|
109
141
|
label: host.t(label),
|
|
110
142
|
value,
|
|
@@ -151,21 +183,30 @@ export class BottomBar extends Container {
|
|
|
151
183
|
private buildWide(): void {
|
|
152
184
|
const { state, tokens } = this.host;
|
|
153
185
|
const isBase = state.mode === 'base';
|
|
154
|
-
const isFS = state.mode === 'freeSpins';
|
|
186
|
+
const isFS = state.mode === 'freeSpins' || state.mode === 'bonus';
|
|
155
187
|
const showFsBlocks = isFS || (state.mode === 'replay' && state.freeSpins.total > 0);
|
|
156
188
|
|
|
157
189
|
// BUY BONUS — floats outside-left of the panel (base only)
|
|
158
|
-
this.buy = isBase ? this.buildBuy(BUY_W, 13, 3) ?? undefined : undefined;
|
|
190
|
+
this.buy = isBase ? (this.buildBuy(BUY_W, 13, 3) ?? undefined) : undefined;
|
|
159
191
|
|
|
160
192
|
// LEFT info group: menu · balance · (Total win) · (Win)
|
|
161
193
|
const left = new FlexBox({ direction: 'row', align: 'center', gap: ZONE_GAP });
|
|
162
|
-
left.add(
|
|
194
|
+
left.add(
|
|
195
|
+
new IconButton('menu', {
|
|
196
|
+
size: 36,
|
|
197
|
+
glyph: 30,
|
|
198
|
+
color: '#ffffff',
|
|
199
|
+
hover: tokens.accent,
|
|
200
|
+
onTap: () => this.host.actions.openMenu(),
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
163
203
|
if (!state.replay) {
|
|
164
204
|
const bal = readout(this.host, 'Balance', this.host.fmt(state.balance));
|
|
165
205
|
this.balanceValue = bal.valueText;
|
|
166
206
|
left.add(bal);
|
|
167
207
|
}
|
|
168
|
-
if (showFsBlocks)
|
|
208
|
+
if (showFsBlocks)
|
|
209
|
+
left.add(readout(this.host, 'Total win', this.host.fmtWin(state.freeSpins.totalWin)));
|
|
169
210
|
if (state.win > 0) {
|
|
170
211
|
const win = readout(this.host, 'Win', this.host.fmtWin(state.win));
|
|
171
212
|
this.winValue = win.valueText;
|
|
@@ -199,7 +240,9 @@ export class BottomBar extends Container {
|
|
|
199
240
|
if (isBase) {
|
|
200
241
|
bet.eventMode = 'static';
|
|
201
242
|
bet.cursor = 'pointer';
|
|
202
|
-
bet.on('pointertap', () => {
|
|
243
|
+
bet.on('pointertap', () => {
|
|
244
|
+
if (!this.betLocked()) this.host.actions.openBetPicker();
|
|
245
|
+
});
|
|
203
246
|
}
|
|
204
247
|
|
|
205
248
|
const betGroup = new FlexBox({ direction: 'row', align: 'center', gap: 8 });
|
|
@@ -207,8 +250,20 @@ export class BottomBar extends Container {
|
|
|
207
250
|
if (isBase) {
|
|
208
251
|
// plain (borderless) +/- icons, bolder — stacked
|
|
209
252
|
const step = new FlexBox({ direction: 'column', align: 'center', gap: 2 });
|
|
210
|
-
this.betUp = new IconButton('plus', {
|
|
211
|
-
|
|
253
|
+
this.betUp = new IconButton('plus', {
|
|
254
|
+
size: 24,
|
|
255
|
+
glyph: 22,
|
|
256
|
+
color: '#ffffff',
|
|
257
|
+
hover: tokens.accent,
|
|
258
|
+
onTap: () => this.onBet(1),
|
|
259
|
+
});
|
|
260
|
+
this.betDown = new IconButton('minus', {
|
|
261
|
+
size: 24,
|
|
262
|
+
glyph: 22,
|
|
263
|
+
color: '#ffffff',
|
|
264
|
+
hover: tokens.accent,
|
|
265
|
+
onTap: () => this.onBet(-1),
|
|
266
|
+
});
|
|
212
267
|
step.add(this.betUp);
|
|
213
268
|
step.add(this.betDown);
|
|
214
269
|
betGroup.add(step);
|
|
@@ -217,26 +272,52 @@ export class BottomBar extends Container {
|
|
|
217
272
|
const spinWrap = new FlexBox({ direction: 'row', align: 'center', gap: 8 });
|
|
218
273
|
if (isBase && config.features.autoplay) {
|
|
219
274
|
this.autoBtn = new IconButton('autoplay', {
|
|
220
|
-
size: DISC,
|
|
221
|
-
|
|
275
|
+
size: DISC,
|
|
276
|
+
glyph: 25,
|
|
277
|
+
disc: tokens.btn,
|
|
278
|
+
color: tokens.btnInk,
|
|
279
|
+
hover: tokens.accent,
|
|
280
|
+
activeColor: tokens.accent,
|
|
281
|
+
active: state.autoplay.active,
|
|
282
|
+
onTap: () => this.onAutoplay(),
|
|
222
283
|
});
|
|
223
284
|
if (state.autoplay.active) this.autoBtn.setGlow(true);
|
|
224
285
|
spinWrap.add(this.autoBtn);
|
|
225
286
|
}
|
|
226
287
|
if (isBase) {
|
|
227
|
-
this.spin = new SpinDisc({
|
|
288
|
+
this.spin = new SpinDisc({
|
|
289
|
+
size: SPIN,
|
|
290
|
+
glyph: 65,
|
|
291
|
+
tokens,
|
|
292
|
+
ticker: this.host.ticker,
|
|
293
|
+
onSpin: () => this.host.actions.spin(),
|
|
294
|
+
onStop: () => this.stopAutoplay(),
|
|
295
|
+
});
|
|
228
296
|
if (state.autoplay.active) this.spin.setAutoplay(true, state.autoplay.remaining);
|
|
229
297
|
if (state.busy) this.spin.setBusy(true);
|
|
230
298
|
spinWrap.add(this.spin);
|
|
231
299
|
} else if (showFsBlocks) {
|
|
232
300
|
const fs = state.freeSpins;
|
|
233
|
-
const fsText =
|
|
234
|
-
|
|
301
|
+
const fsText =
|
|
302
|
+
state.bonus?.value ?? (fs.current == null ? `${fs.total}` : `${fs.current} / ${fs.total}`);
|
|
303
|
+
spinWrap.add(
|
|
304
|
+
new FsHero({
|
|
305
|
+
label: this.host.t(state.bonus?.label ?? 'Free spins'),
|
|
306
|
+
value: fsText,
|
|
307
|
+
tokens,
|
|
308
|
+
height: SPIN,
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
235
311
|
}
|
|
236
312
|
if (config.features.turbo > 0) {
|
|
237
313
|
this.turboBtn = new TurboButton({
|
|
238
|
-
size: DISC,
|
|
239
|
-
|
|
314
|
+
size: DISC,
|
|
315
|
+
glyph: 19,
|
|
316
|
+
discFill: tokens.btn,
|
|
317
|
+
discBorder: 2,
|
|
318
|
+
accent: tokens.accent,
|
|
319
|
+
level: state.turbo,
|
|
320
|
+
onTap: () => this.onTurbo(),
|
|
240
321
|
});
|
|
241
322
|
spinWrap.add(this.turboBtn);
|
|
242
323
|
}
|
|
@@ -255,57 +336,124 @@ export class BottomBar extends Container {
|
|
|
255
336
|
const feature = state.activeFeature;
|
|
256
337
|
if (feature) {
|
|
257
338
|
const accent = effectiveAccent(feature);
|
|
258
|
-
return new BuyBonusBadge({
|
|
339
|
+
return new BuyBonusBadge({
|
|
340
|
+
size,
|
|
341
|
+
fontSize,
|
|
342
|
+
border,
|
|
343
|
+
bg: accent,
|
|
344
|
+
fg: contrastText(accent),
|
|
345
|
+
label: this.host.t('DISABLE'),
|
|
346
|
+
tokens,
|
|
347
|
+
ticker: this.host.ticker,
|
|
348
|
+
onTap: () => this.host.actions.deactivateFeature(),
|
|
349
|
+
});
|
|
259
350
|
}
|
|
260
|
-
return new BuyBonusBadge({
|
|
351
|
+
return new BuyBonusBadge({
|
|
352
|
+
size,
|
|
353
|
+
border,
|
|
354
|
+
bg: tokens.accent,
|
|
355
|
+
icon: 'ticket',
|
|
356
|
+
iconSize: size * 0.55,
|
|
357
|
+
iconColor: tokens.btnInk,
|
|
358
|
+
label: '',
|
|
359
|
+
tokens,
|
|
360
|
+
ticker: this.host.ticker,
|
|
361
|
+
onTap: () => this.host.actions.openBuyBonus(),
|
|
362
|
+
});
|
|
261
363
|
}
|
|
262
364
|
|
|
263
365
|
// ── mobile / portrait ─────────────────────────────────────────────────────
|
|
264
366
|
private buildMobile(): void {
|
|
265
367
|
const { state, config, tokens } = this.host;
|
|
266
368
|
const isBase = state.mode === 'base';
|
|
267
|
-
const isFS = state.mode === 'freeSpins';
|
|
369
|
+
const isFS = state.mode === 'freeSpins' || state.mode === 'bonus';
|
|
268
370
|
const showFsBlocks = isFS || (state.mode === 'replay' && state.freeSpins.total > 0);
|
|
269
371
|
const W = this.host.screenW - 2 * M_SIDE;
|
|
270
372
|
|
|
271
373
|
// hero (centre): SPIN in base, FS counter in free spins
|
|
272
374
|
let hero: Container | undefined;
|
|
273
375
|
if (isBase) {
|
|
274
|
-
this.spin = new SpinDisc({
|
|
376
|
+
this.spin = new SpinDisc({
|
|
377
|
+
size: SPIN,
|
|
378
|
+
glyph: 65,
|
|
379
|
+
tokens,
|
|
380
|
+
ticker: this.host.ticker,
|
|
381
|
+
onSpin: () => this.host.actions.spin(),
|
|
382
|
+
onStop: () => this.stopAutoplay(),
|
|
383
|
+
});
|
|
275
384
|
if (state.autoplay.active) this.spin.setAutoplay(true, state.autoplay.remaining);
|
|
276
385
|
if (state.busy) this.spin.setBusy(true);
|
|
277
386
|
hero = this.spin;
|
|
278
387
|
} else if (showFsBlocks) {
|
|
279
388
|
const fs = state.freeSpins;
|
|
280
|
-
const fsText =
|
|
281
|
-
|
|
389
|
+
const fsText =
|
|
390
|
+
state.bonus?.value ?? (fs.current == null ? `${fs.total}` : `${fs.current} / ${fs.total}`);
|
|
391
|
+
hero = new FsHero({
|
|
392
|
+
label: this.host.t(state.bonus?.label ?? 'Free spins'),
|
|
393
|
+
value: fsText,
|
|
394
|
+
tokens,
|
|
395
|
+
height: SPIN,
|
|
396
|
+
});
|
|
282
397
|
}
|
|
283
398
|
|
|
284
|
-
const menu = new IconButton('menu', {
|
|
399
|
+
const menu = new IconButton('menu', {
|
|
400
|
+
size: 40,
|
|
401
|
+
glyph: 26,
|
|
402
|
+
color: '#ffffff',
|
|
403
|
+
hover: tokens.accent,
|
|
404
|
+
onTap: () => this.host.actions.openMenu(),
|
|
405
|
+
});
|
|
285
406
|
// autoplay is a base-mode control only — hidden in free spins / replay (matches the DOM bar)
|
|
286
407
|
if (isBase && config.features.autoplay) {
|
|
287
|
-
this.autoBtn = new IconButton('autoplay', {
|
|
408
|
+
this.autoBtn = new IconButton('autoplay', {
|
|
409
|
+
size: 40,
|
|
410
|
+
glyph: 26,
|
|
411
|
+
color: '#ffffff',
|
|
412
|
+
hover: tokens.accent,
|
|
413
|
+
activeColor: tokens.accent,
|
|
414
|
+
active: state.autoplay.active,
|
|
415
|
+
onTap: () => this.onAutoplay(),
|
|
416
|
+
});
|
|
288
417
|
if (state.autoplay.active) this.autoBtn.setGlow(true);
|
|
289
418
|
}
|
|
290
419
|
if (config.features.turbo > 0) {
|
|
291
420
|
this.turboBtn = new TurboButton({
|
|
292
|
-
size: 40,
|
|
293
|
-
|
|
421
|
+
size: 40,
|
|
422
|
+
glyph: 22,
|
|
423
|
+
discFill: tokens.btn,
|
|
424
|
+
discBorder: 2,
|
|
425
|
+
accent: tokens.accent,
|
|
426
|
+
level: state.turbo,
|
|
427
|
+
onTap: () => this.onTurbo(),
|
|
294
428
|
});
|
|
295
429
|
}
|
|
296
|
-
const buy = isBase ? this.buildBuy(M_BUY, 9, 2) ?? undefined : undefined;
|
|
430
|
+
const buy = isBase ? (this.buildBuy(M_BUY, 9, 2) ?? undefined) : undefined;
|
|
297
431
|
|
|
298
432
|
// level 1 — controls bar (dark)
|
|
299
|
-
const controls = new FlexBox({
|
|
433
|
+
const controls = new FlexBox({
|
|
434
|
+
direction: 'row',
|
|
435
|
+
align: 'center',
|
|
436
|
+
justify: 'space-between',
|
|
437
|
+
gap: 0,
|
|
438
|
+
width: W,
|
|
439
|
+
height: M_CTRL_H,
|
|
440
|
+
padding: { left: 18, right: 18 },
|
|
441
|
+
background: { fill: tokens.bar, radius: 16 },
|
|
442
|
+
});
|
|
300
443
|
if (isBase && hero) {
|
|
301
444
|
const SIDE = 12;
|
|
302
445
|
const lz = new FlexBox({ direction: 'row', align: 'center', gap: SIDE, justify: 'start' });
|
|
303
|
-
lz.add(menu);
|
|
446
|
+
lz.add(menu);
|
|
447
|
+
if (this.autoBtn) lz.add(this.autoBtn);
|
|
304
448
|
const rz = new FlexBox({ direction: 'row', align: 'center', gap: SIDE, justify: 'end' });
|
|
305
|
-
if (this.turboBtn) rz.add(this.turboBtn);
|
|
449
|
+
if (this.turboBtn) rz.add(this.turboBtn);
|
|
450
|
+
if (buy) rz.add(buy);
|
|
306
451
|
const zw = Math.max(lz.measureSize().w, rz.measureSize().w);
|
|
307
|
-
lz.setLayoutSize(zw, undefined);
|
|
308
|
-
|
|
452
|
+
lz.setLayoutSize(zw, undefined);
|
|
453
|
+
rz.setLayoutSize(zw, undefined);
|
|
454
|
+
controls.add(lz);
|
|
455
|
+
controls.add(hero);
|
|
456
|
+
controls.add(rz);
|
|
309
457
|
} else {
|
|
310
458
|
controls.add(menu);
|
|
311
459
|
if (this.autoBtn) controls.add(this.autoBtn);
|
|
@@ -316,12 +464,22 @@ export class BottomBar extends Container {
|
|
|
316
464
|
// (centred post-scale) instead of widening the row and forcing a whole-bar down-scale. Mirrors
|
|
317
465
|
// the DOM's shrinkable total-win slot. The floor keeps it readable; below that applyFitMobile
|
|
318
466
|
// scales the stack as a last resort.
|
|
319
|
-
const ICON = 40,
|
|
467
|
+
const ICON = 40,
|
|
468
|
+
PAD = 18,
|
|
469
|
+
GAPS = 24;
|
|
320
470
|
const heroSized = hero as unknown as { measureSize?: () => { w: number; h: number } };
|
|
321
471
|
const heroW = hero ? (heroSized.measureSize?.().w ?? hero.getLocalBounds().width) : 0;
|
|
322
|
-
const fixed =
|
|
472
|
+
const fixed =
|
|
473
|
+
ICON /* menu */ + (this.autoBtn ? ICON : 0) + heroW + (this.turboBtn ? ICON : 0);
|
|
323
474
|
const twSlot = Math.max(60, W - 2 * PAD - fixed - GAPS);
|
|
324
|
-
controls.add(
|
|
475
|
+
controls.add(
|
|
476
|
+
readout(this.host, 'Total win', this.host.fmtWin(state.freeSpins.totalWin), {
|
|
477
|
+
color: '#ffffff',
|
|
478
|
+
muted: '#ffffff',
|
|
479
|
+
align: 'center',
|
|
480
|
+
maxWidth: twSlot,
|
|
481
|
+
}),
|
|
482
|
+
);
|
|
325
483
|
}
|
|
326
484
|
if (this.turboBtn) controls.add(this.turboBtn);
|
|
327
485
|
}
|
|
@@ -332,29 +490,76 @@ export class BottomBar extends Container {
|
|
|
332
490
|
const feature = state.activeFeature;
|
|
333
491
|
const betShown = feature ? state.bet * feature.priceMultiplier : state.bet;
|
|
334
492
|
if (isBase) {
|
|
335
|
-
this.betDown = new IconButton('minus', {
|
|
493
|
+
this.betDown = new IconButton('minus', {
|
|
494
|
+
size: 26,
|
|
495
|
+
glyph: 18,
|
|
496
|
+
color: '#ffffff',
|
|
497
|
+
hover: tokens.accent,
|
|
498
|
+
onTap: () => this.onBet(-1),
|
|
499
|
+
});
|
|
336
500
|
betGroup.add(this.betDown);
|
|
337
501
|
}
|
|
338
|
-
const bet = new Readout({
|
|
502
|
+
const bet = new Readout({
|
|
503
|
+
label: this.host.t('Bet'),
|
|
504
|
+
value: this.host.fmt(betShown),
|
|
505
|
+
muted: feature ? effectiveAccent(feature) : tokens.plaqueLabel,
|
|
506
|
+
fg: feature ? effectiveAccent(feature) : '#ffffff',
|
|
507
|
+
valueSize: 11,
|
|
508
|
+
align: 'center',
|
|
509
|
+
fixedWidth: 76,
|
|
510
|
+
shadow: false,
|
|
511
|
+
});
|
|
339
512
|
this.betReadout = bet;
|
|
340
|
-
if (isBase) {
|
|
513
|
+
if (isBase) {
|
|
514
|
+
bet.eventMode = 'static';
|
|
515
|
+
bet.cursor = 'pointer';
|
|
516
|
+
bet.on('pointertap', () => {
|
|
517
|
+
if (!this.betLocked()) this.host.actions.openBetPicker();
|
|
518
|
+
});
|
|
519
|
+
}
|
|
341
520
|
betGroup.add(bet);
|
|
342
|
-
if (isBase) {
|
|
521
|
+
if (isBase) {
|
|
522
|
+
this.betUp = new IconButton('plus', {
|
|
523
|
+
size: 26,
|
|
524
|
+
glyph: 18,
|
|
525
|
+
color: '#ffffff',
|
|
526
|
+
hover: tokens.accent,
|
|
527
|
+
onTap: () => this.onBet(1),
|
|
528
|
+
});
|
|
529
|
+
betGroup.add(this.betUp);
|
|
530
|
+
}
|
|
343
531
|
betGroup.layout();
|
|
344
532
|
|
|
345
533
|
const innerW = W - 28; // pad 14 each side
|
|
346
534
|
const slot = Math.max(40, (innerW - betGroup.outerWidth - 20) / 2);
|
|
347
|
-
const info = new FlexBox({
|
|
535
|
+
const info = new FlexBox({
|
|
536
|
+
direction: 'row',
|
|
537
|
+
align: 'center',
|
|
538
|
+
justify: 'space-between',
|
|
539
|
+
width: W,
|
|
540
|
+
height: M_INFO_H,
|
|
541
|
+
padding: { left: 14, right: 14 },
|
|
542
|
+
gap: 10,
|
|
543
|
+
background: { fill: tokens.plaqueGlass, radius: 12 },
|
|
544
|
+
});
|
|
348
545
|
// replay is a read-only historical round — no real balance to show (sticky `replay`, so it stays
|
|
349
546
|
// hidden through a replay's free-spins phase too); matches the DOM bar.
|
|
350
547
|
if (!state.replay) {
|
|
351
|
-
const bal = readout(this.host, 'Balance', this.host.fmt(state.balance), {
|
|
548
|
+
const bal = readout(this.host, 'Balance', this.host.fmt(state.balance), {
|
|
549
|
+
valueSize: 11,
|
|
550
|
+
align: 'left',
|
|
551
|
+
fixedWidth: slot,
|
|
552
|
+
});
|
|
352
553
|
this.balanceValue = bal.valueText;
|
|
353
554
|
info.add(bal);
|
|
354
555
|
}
|
|
355
556
|
info.add(betGroup);
|
|
356
557
|
// WIN always present (no jiggle on win↔0)
|
|
357
|
-
const win = readout(this.host, 'Win', this.host.fmtWin(state.win), {
|
|
558
|
+
const win = readout(this.host, 'Win', this.host.fmtWin(state.win), {
|
|
559
|
+
valueSize: 11,
|
|
560
|
+
align: 'right',
|
|
561
|
+
fixedWidth: slot,
|
|
562
|
+
});
|
|
358
563
|
this.winValue = win.valueText;
|
|
359
564
|
info.add(win);
|
|
360
565
|
info.layout();
|
|
@@ -399,7 +604,10 @@ export class BottomBar extends Container {
|
|
|
399
604
|
if (this.spin) this.spin.disabled = state.busy && !auto;
|
|
400
605
|
if (this.autoBtn) this.autoBtn.disabled = state.busy && !auto;
|
|
401
606
|
if (this.buy) this.buy.disabled = state.busy || auto || !state.buyBonusEnabled;
|
|
402
|
-
if (this.betReadout && lockBet) {
|
|
607
|
+
if (this.betReadout && lockBet) {
|
|
608
|
+
this.betReadout.eventMode = 'none';
|
|
609
|
+
this.betReadout.cursor = 'default';
|
|
610
|
+
}
|
|
403
611
|
}
|
|
404
612
|
|
|
405
613
|
// ── height / fit ──────────────────────────────────────────────────────────
|
|
@@ -414,9 +622,13 @@ export class BottomBar extends Container {
|
|
|
414
622
|
}
|
|
415
623
|
|
|
416
624
|
applyFit(): void {
|
|
417
|
-
if (this.host.layout === 'mobile') {
|
|
625
|
+
if (this.host.layout === 'mobile') {
|
|
626
|
+
this.applyFitMobile();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
418
629
|
const { screenW: W, screenH: H, tokens } = this.host;
|
|
419
|
-
const left = this.leftZone!,
|
|
630
|
+
const left = this.leftZone!,
|
|
631
|
+
right = this.rightZone!;
|
|
420
632
|
const buyW = this.buy ? BUY_W + ROW_GAP : 0;
|
|
421
633
|
|
|
422
634
|
// content area = buy + panel; panel holds left (hard-left) and right (hard-right) with ≥MID_GAP.
|
|
@@ -436,16 +648,20 @@ export class BottomBar extends Container {
|
|
|
436
648
|
|
|
437
649
|
if (this.buy) this.buy.position.set(OUTER_PAD, panelCenterY - BUY_W / 2);
|
|
438
650
|
left.position.set(panelX + PANEL_PAD, panelCenterY - left.outerHeight / 2);
|
|
439
|
-
right.position.set(
|
|
651
|
+
right.position.set(
|
|
652
|
+
panelRight - PANEL_PAD - right.outerWidth,
|
|
653
|
+
panelCenterY - right.outerHeight / 2,
|
|
654
|
+
);
|
|
440
655
|
|
|
441
656
|
this.inner.scale.set(s);
|
|
442
|
-
this.inner.position.set((W - barW * s) / 2,
|
|
657
|
+
this.inner.position.set((W - barW * s) / 2, H - WIDE_PAD_BOTTOM - SPIN * s);
|
|
443
658
|
this.measureFootprint();
|
|
444
659
|
}
|
|
445
660
|
|
|
446
661
|
private applyFitMobile(): void {
|
|
447
662
|
const { screenW: W, screenH: H } = this.host;
|
|
448
|
-
const controls = this.mobileControls!,
|
|
663
|
+
const controls = this.mobileControls!,
|
|
664
|
+
info = this.mobileInfo!;
|
|
449
665
|
const pop = this.mobileHeroPop; // how far the hero sticks above the controls bar
|
|
450
666
|
// vertical stack: [controls (hero pops `pop` above)] gap [info]
|
|
451
667
|
const topPad = pop;
|