@open-slot-ui/core 0.8.2 → 0.9.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 +438 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +266 -4
- package/dist/index.d.ts +266 -4
- package/dist/index.js +427 -93
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -435,12 +435,136 @@ function valueFitMaxWidth(digits, columnWidth) {
|
|
|
435
435
|
return clampDigits(digits) * columnWidth + 64;
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
// src/format/currency.ts
|
|
439
|
+
var CURRENCY_TABLE = Object.freeze({
|
|
440
|
+
// ── fiat — symbol BEFORE the amount ──────────────────────────────────────────
|
|
441
|
+
USD: { symbol: "$", decimals: 2 },
|
|
442
|
+
CAD: { symbol: "CA$", decimals: 2 },
|
|
443
|
+
JPY: { symbol: "\xA5", decimals: 0 },
|
|
444
|
+
EUR: { symbol: "\u20AC", decimals: 2 },
|
|
445
|
+
RUB: { symbol: "\u20BD", decimals: 2 },
|
|
446
|
+
CNY: { symbol: "CN\xA5", decimals: 2 },
|
|
447
|
+
PHP: { symbol: "\u20B1", decimals: 2 },
|
|
448
|
+
INR: { symbol: "\u20B9", decimals: 2 },
|
|
449
|
+
IDR: { symbol: "Rp", decimals: 0 },
|
|
450
|
+
KRW: { symbol: "\u20A9", decimals: 0 },
|
|
451
|
+
BRL: { symbol: "R$", decimals: 2 },
|
|
452
|
+
MXN: { symbol: "MX$", decimals: 2 },
|
|
453
|
+
TRY: { symbol: "\u20BA", decimals: 2 },
|
|
454
|
+
GBP: { symbol: "\xA3", decimals: 2 },
|
|
455
|
+
NGN: { symbol: "\u20A6", decimals: 2 },
|
|
456
|
+
TWD: { symbol: "NT$", decimals: 2 },
|
|
457
|
+
SGD: { symbol: "SG$", decimals: 2 },
|
|
458
|
+
MYR: { symbol: "RM", decimals: 2 },
|
|
459
|
+
CRC: { symbol: "\u20A1", decimals: 2 },
|
|
460
|
+
// three-decimal dinars/rials (1 unit = 1000 fils) — the >2-decimal path must format these
|
|
461
|
+
KWD: { symbol: "KD", decimals: 3 },
|
|
462
|
+
JOD: { symbol: "JD", decimals: 3 },
|
|
463
|
+
BHD: { symbol: "BD", decimals: 3 },
|
|
464
|
+
OMR: { symbol: "OMR", decimals: 3 },
|
|
465
|
+
TND: { symbol: "DT", decimals: 3 },
|
|
466
|
+
LYD: { symbol: "LD", decimals: 3 },
|
|
467
|
+
IQD: { symbol: "IQD", decimals: 3 },
|
|
468
|
+
// ── fiat — symbol AFTER the amount ───────────────────────────────────────────
|
|
469
|
+
DKK: { symbol: "kr", decimals: 2, symbolAfter: true },
|
|
470
|
+
NOK: { symbol: "kr", decimals: 2, symbolAfter: true },
|
|
471
|
+
PLN: { symbol: "z\u0142", decimals: 2, symbolAfter: true },
|
|
472
|
+
VND: { symbol: "\u20AB", decimals: 0, symbolAfter: true },
|
|
473
|
+
CLP: { symbol: "CLP", decimals: 0, symbolAfter: true },
|
|
474
|
+
ARS: { symbol: "ARS", decimals: 2, symbolAfter: true },
|
|
475
|
+
PEN: { symbol: "S/", decimals: 2, symbolAfter: true },
|
|
476
|
+
// ── crypto (8-dp, so sub-cent + high precision format correctly) ─────────────
|
|
477
|
+
BTC: { symbol: "\u20BF", decimals: 8 },
|
|
478
|
+
ETH: { symbol: "\u039E", decimals: 8 },
|
|
479
|
+
LTC: { symbol: "\u0141", decimals: 8 },
|
|
480
|
+
BCH: { symbol: "BCH", decimals: 8 },
|
|
481
|
+
DOGE: { symbol: "\xD0", decimals: 8 },
|
|
482
|
+
USDT: { symbol: "\u20AE", decimals: 2 },
|
|
483
|
+
USDC: { symbol: "USDC", decimals: 2 },
|
|
484
|
+
// ── Stake social coins — Gold Coins / Stake Cash (shown as GC / SC) ───────────
|
|
485
|
+
XGC: { symbol: "GC", decimals: 2, social: true },
|
|
486
|
+
XSC: { symbol: "SC", decimals: 2, social: true },
|
|
487
|
+
GC: { symbol: "GC", decimals: 2, social: true },
|
|
488
|
+
SC: { symbol: "SC", decimals: 2, social: true }
|
|
489
|
+
});
|
|
490
|
+
var upper = (code) => typeof code === "string" ? code.toUpperCase() : "";
|
|
491
|
+
function isSocialCurrency(code) {
|
|
492
|
+
return CURRENCY_TABLE[upper(code)]?.social === true;
|
|
493
|
+
}
|
|
494
|
+
function resolveCurrency(code, overrides = {}) {
|
|
495
|
+
const info = CURRENCY_TABLE[upper(code)];
|
|
496
|
+
if (!info) {
|
|
497
|
+
return {
|
|
498
|
+
code: overrides.code ?? code,
|
|
499
|
+
decimals: clampDecimals(overrides.decimals ?? 2),
|
|
500
|
+
position: overrides.position ?? "suffix",
|
|
501
|
+
separator: overrides.separator ?? ",",
|
|
502
|
+
decimalChar: overrides.decimalChar ?? ".",
|
|
503
|
+
...overrides.symbol ? { symbol: overrides.symbol, display: overrides.display ?? "symbol" } : {}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
// Social coins carry their DISPLAY code (XGC → "GC", XSC → "SC"); fiat/crypto keep
|
|
508
|
+
// their ISO code (the symbol is what's actually rendered under `display:'symbol'`).
|
|
509
|
+
code: overrides.code ?? (info.social ? info.symbol : code),
|
|
510
|
+
symbol: overrides.symbol ?? info.symbol,
|
|
511
|
+
display: overrides.display ?? "symbol",
|
|
512
|
+
position: overrides.position ?? (info.symbolAfter ? "suffix" : "prefix"),
|
|
513
|
+
decimals: clampDecimals(overrides.decimals ?? info.decimals),
|
|
514
|
+
separator: overrides.separator ?? ",",
|
|
515
|
+
decimalChar: overrides.decimalChar ?? "."
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function neededDecimals(value, base) {
|
|
519
|
+
const b = clampDecimals(base);
|
|
520
|
+
if (!Number.isFinite(value) || value === 0) return b;
|
|
521
|
+
let scaled = Math.round(Math.abs(value) * 1e8);
|
|
522
|
+
let d = 8;
|
|
523
|
+
while (d > b && scaled % 10 === 0) {
|
|
524
|
+
scaled /= 10;
|
|
525
|
+
d--;
|
|
526
|
+
}
|
|
527
|
+
return d;
|
|
528
|
+
}
|
|
529
|
+
function formatAmountPrecise(value, spec, opts = {}) {
|
|
530
|
+
const base = clampDecimals(spec.decimals ?? 2);
|
|
531
|
+
const d = neededDecimals(value, base);
|
|
532
|
+
return d === base ? formatAmount(value, spec, opts) : formatAmount(value, { ...spec, decimals: d }, opts);
|
|
533
|
+
}
|
|
534
|
+
function formatAmount(value, spec, opts = {}) {
|
|
535
|
+
const decimals = clampDecimals(spec.decimals);
|
|
536
|
+
const sep = spec.separator ?? ",";
|
|
537
|
+
const dot = spec.decimalChar ?? ".";
|
|
538
|
+
const n = Number.isFinite(value) ? value : 0;
|
|
539
|
+
const fixed = Math.abs(n).toFixed(decimals);
|
|
540
|
+
const [int = "0", frac] = fixed.split(".");
|
|
541
|
+
const grouped = int.replace(/\B(?=(\d{3})+(?!\d))/g, sep);
|
|
542
|
+
const body = frac ? `${grouped}${dot}${frac}` : grouped;
|
|
543
|
+
const sign = n < 0 ? "-" : opts.signed ? "+" : "";
|
|
544
|
+
const number = `${sign}${body}`;
|
|
545
|
+
const symbolMode = spec.display === "symbol" && !!spec.symbol;
|
|
546
|
+
const affix = symbolMode ? spec.symbol : spec.code;
|
|
547
|
+
const prefix = (spec.position ?? "suffix") === "prefix";
|
|
548
|
+
const gap = symbolMode && prefix ? "" : " ";
|
|
549
|
+
return prefix ? `${affix}${gap}${number}` : `${number}${gap}${affix}`;
|
|
550
|
+
}
|
|
551
|
+
|
|
438
552
|
// src/controls/ValueDisplay.ts
|
|
439
553
|
var ValueDisplay = class extends Control {
|
|
440
554
|
states = { idle: { interactable: false } };
|
|
441
555
|
value = new Signal(0);
|
|
442
556
|
currency;
|
|
443
557
|
label;
|
|
558
|
+
/**
|
|
559
|
+
* Show a value at the precision it ACTUALLY carries (default on): when the value has
|
|
560
|
+
* more fraction digits than the currency's native precision — a sub-unit win like a
|
|
561
|
+
* USD 0.01 bet × a ×0.2 face = 0.002, or the 999.982 balance it leaves behind — the
|
|
562
|
+
* display decimals bump just enough to reveal them ($0.002), and drop back to the
|
|
563
|
+
* native look (5.00) once the value is clean again. Set `false` for hard-fixed decimals.
|
|
564
|
+
*/
|
|
565
|
+
autoPrecision = true;
|
|
566
|
+
/** The currency's NATIVE precision — what `autoPrecision` returns to on clean values. */
|
|
567
|
+
baseDecimals;
|
|
444
568
|
/** Minimum odometer column width (0 = auto: sized tightly to the value, grows as it
|
|
445
569
|
* does). Read by the renderer at build time, so set it before mount (or declaratively
|
|
446
570
|
* via `controls.{id}.digits`). */
|
|
@@ -451,9 +575,11 @@ var ValueDisplay = class extends Control {
|
|
|
451
575
|
this.currency = new Signal(
|
|
452
576
|
c && typeof c.code === "string" ? { ...c, decimals: clampDecimals(c.decimals) } : { code: "USD", decimals: 2 }
|
|
453
577
|
);
|
|
578
|
+
this.baseDecimals = this.currency.get().decimals;
|
|
454
579
|
this.label = opts.label;
|
|
455
580
|
this.digits = clampMinDigits(opts.digits ?? 0);
|
|
456
581
|
if (opts.initial != null) this.value.set(opts.initial);
|
|
582
|
+
this.applyPrecision();
|
|
457
583
|
}
|
|
458
584
|
/** Set the minimum odometer column width (clamped 0..18; 0 = auto). Read by the
|
|
459
585
|
* renderer at build time, so set it before mount (or via `controls.{id}.digits`). */
|
|
@@ -464,14 +590,27 @@ var ValueDisplay = class extends Control {
|
|
|
464
590
|
* good value — malformed host data never throws into the render loop (P11). */
|
|
465
591
|
set(amount) {
|
|
466
592
|
this.value.set(safeAmount(amount, this.value.get()));
|
|
593
|
+
this.applyPrecision();
|
|
467
594
|
}
|
|
468
595
|
get() {
|
|
469
596
|
return this.value.get();
|
|
470
597
|
}
|
|
471
|
-
/** Keep the prior spec on malformed input; clamp decimals to 0..8 (P11).
|
|
598
|
+
/** Keep the prior spec on malformed input; clamp decimals to 0..8 (P11). The given
|
|
599
|
+
* decimals become the NATIVE precision (`autoPrecision` bumps above it as needed). */
|
|
472
600
|
setCurrency(spec) {
|
|
473
601
|
if (!spec || typeof spec.code !== "string") return;
|
|
474
|
-
this.
|
|
602
|
+
this.baseDecimals = clampDecimals(spec.decimals);
|
|
603
|
+
this.currency.set({ ...spec, decimals: this.baseDecimals });
|
|
604
|
+
this.applyPrecision();
|
|
605
|
+
}
|
|
606
|
+
/** Bump the display decimals to what the current value needs — never below the
|
|
607
|
+
* currency's native precision — and drop back once the value is clean again.
|
|
608
|
+
* No-op (and no signal churn) when the shown precision is already right. */
|
|
609
|
+
applyPrecision() {
|
|
610
|
+
if (!this.autoPrecision) return;
|
|
611
|
+
const want = neededDecimals(this.value.get(), this.baseDecimals);
|
|
612
|
+
const cur = this.currency.get();
|
|
613
|
+
if (cur.decimals !== want) this.currency.set({ ...cur, decimals: want });
|
|
475
614
|
}
|
|
476
615
|
/** Accent emphasis: the renderer tints the value in the theme accent while on.
|
|
477
616
|
* For "this number is modified" moments — e.g. a bet display showing the
|
|
@@ -1130,6 +1269,7 @@ var BLOCK_KINDS = [
|
|
|
1130
1269
|
"subheading",
|
|
1131
1270
|
"callout",
|
|
1132
1271
|
"stat-grid",
|
|
1272
|
+
"mode-stats",
|
|
1133
1273
|
"steps",
|
|
1134
1274
|
"table",
|
|
1135
1275
|
"paytable",
|
|
@@ -1282,6 +1422,9 @@ function validateSpec(spec) {
|
|
|
1282
1422
|
if (b.kind === "stat-grid" && (!b.items || b.items.length === 0)) {
|
|
1283
1423
|
add("warn", `${p}.items`, "empty-grid", "a stat-grid block has no items");
|
|
1284
1424
|
}
|
|
1425
|
+
if (b.kind === "mode-stats" && !spec.facts?.modes?.length) {
|
|
1426
|
+
add("warn", `${p}`, "mode-stats-no-facts", "a mode-stats block renders from spec.facts.modes, but no modes are declared");
|
|
1427
|
+
}
|
|
1285
1428
|
if (b.kind === "steps" && (!b.items || b.items.length === 0)) {
|
|
1286
1429
|
add("warn", `${p}.items`, "empty-steps", "a steps block has no items");
|
|
1287
1430
|
}
|
|
@@ -1477,6 +1620,10 @@ var openuiDefaults = {
|
|
|
1477
1620
|
// reality check (RTS 13) — {{minutes}} is interpolated by open-ui's scheduler
|
|
1478
1621
|
"openui.realityCheck.title": "Reality check",
|
|
1479
1622
|
"openui.realityCheck.message": "You've been playing for {{minutes}} minutes. Take a moment before continuing.",
|
|
1623
|
+
// Rules-completeness audit (the info menu's "forgotten declarations" card)
|
|
1624
|
+
"openui.rulesAudit.title": "Rules incomplete",
|
|
1625
|
+
"openui.rulesAudit.required": "Missing required information:",
|
|
1626
|
+
"openui.rulesAudit.recommended": "Highly recommended:",
|
|
1480
1627
|
// RGS error defaults (override via your messages dict or per-call)
|
|
1481
1628
|
"openui.err.generic.title": "Something went wrong",
|
|
1482
1629
|
"openui.err.generic.message": "Sorry, something went wrong. Please try again.",
|
|
@@ -1515,11 +1662,13 @@ var openuiSocialDefaults = {
|
|
|
1515
1662
|
"openui.replay.costMultiplier": "Feature Multiplier",
|
|
1516
1663
|
"openui.replay.payoutMultiplier": "Final Multiplier",
|
|
1517
1664
|
"openui.err.insufficient.message": "You don't have enough balance for this play.",
|
|
1518
|
-
"openui.err.limit.message": "A play limit has been reached. Please try again later."
|
|
1665
|
+
"openui.err.limit.message": "A play limit has been reached. Please try again later.",
|
|
1666
|
+
// The auto mode-stats grid's label words (used as their own keys at render time).
|
|
1667
|
+
"Max win": "Max prize"
|
|
1519
1668
|
};
|
|
1520
1669
|
function interpolate(template, vars) {
|
|
1521
1670
|
if (!vars) return template;
|
|
1522
|
-
return template.replace(/\{\{(\w+)\}\}/g, (_m, k) => String(vars[k] ?? `{{${k}}}`));
|
|
1671
|
+
return template.replace(/\{\{([\w.-]+)\}\}/g, (_m, k) => String(vars[k] ?? `{{${k}}}`));
|
|
1523
1672
|
}
|
|
1524
1673
|
var DictionaryTranslator = class {
|
|
1525
1674
|
constructor(messages, locale, socialMessages = {}) {
|
|
@@ -1559,98 +1708,207 @@ function dictionary(messages, locale, socialMessages) {
|
|
|
1559
1708
|
return new DictionaryTranslator(messages, locale, socialMessages);
|
|
1560
1709
|
}
|
|
1561
1710
|
|
|
1562
|
-
// src/
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
IDR: { symbol: "Rp", decimals: 0 },
|
|
1574
|
-
KRW: { symbol: "\u20A9", decimals: 0 },
|
|
1575
|
-
BRL: { symbol: "R$", decimals: 2 },
|
|
1576
|
-
MXN: { symbol: "MX$", decimals: 2 },
|
|
1577
|
-
TRY: { symbol: "\u20BA", decimals: 2 },
|
|
1578
|
-
GBP: { symbol: "\xA3", decimals: 2 },
|
|
1579
|
-
NGN: { symbol: "\u20A6", decimals: 2 },
|
|
1580
|
-
TWD: { symbol: "NT$", decimals: 2 },
|
|
1581
|
-
SGD: { symbol: "SG$", decimals: 2 },
|
|
1582
|
-
MYR: { symbol: "RM", decimals: 2 },
|
|
1583
|
-
CRC: { symbol: "\u20A1", decimals: 2 },
|
|
1584
|
-
// three-decimal dinars (1 unit = 1000 fils) — the >2-decimal path must format these
|
|
1585
|
-
KWD: { symbol: "KD", decimals: 3 },
|
|
1586
|
-
JOD: { symbol: "JD", decimals: 3 },
|
|
1587
|
-
BHD: { symbol: "BD", decimals: 3 },
|
|
1588
|
-
// ── fiat — symbol AFTER the amount ───────────────────────────────────────────
|
|
1589
|
-
DKK: { symbol: "kr", decimals: 2, symbolAfter: true },
|
|
1590
|
-
NOK: { symbol: "kr", decimals: 2, symbolAfter: true },
|
|
1591
|
-
PLN: { symbol: "z\u0142", decimals: 2, symbolAfter: true },
|
|
1592
|
-
VND: { symbol: "\u20AB", decimals: 0, symbolAfter: true },
|
|
1593
|
-
CLP: { symbol: "CLP", decimals: 0, symbolAfter: true },
|
|
1594
|
-
ARS: { symbol: "ARS", decimals: 2, symbolAfter: true },
|
|
1595
|
-
PEN: { symbol: "S/", decimals: 2, symbolAfter: true },
|
|
1596
|
-
// ── crypto (8-dp, so sub-cent + high precision format correctly) ─────────────
|
|
1597
|
-
BTC: { symbol: "\u20BF", decimals: 8 },
|
|
1598
|
-
ETH: { symbol: "\u039E", decimals: 8 },
|
|
1599
|
-
LTC: { symbol: "\u0141", decimals: 8 },
|
|
1600
|
-
BCH: { symbol: "BCH", decimals: 8 },
|
|
1601
|
-
DOGE: { symbol: "\xD0", decimals: 8 },
|
|
1602
|
-
USDT: { symbol: "\u20AE", decimals: 2 },
|
|
1603
|
-
USDC: { symbol: "USDC", decimals: 2 },
|
|
1604
|
-
// ── Stake social coins — Gold Coins / Stake Cash (shown as GC / SC) ───────────
|
|
1605
|
-
XGC: { symbol: "GC", decimals: 2, social: true },
|
|
1606
|
-
XSC: { symbol: "SC", decimals: 2, social: true },
|
|
1607
|
-
GC: { symbol: "GC", decimals: 2, social: true },
|
|
1608
|
-
SC: { symbol: "SC", decimals: 2, social: true }
|
|
1609
|
-
});
|
|
1610
|
-
var upper = (code) => typeof code === "string" ? code.toUpperCase() : "";
|
|
1611
|
-
function isSocialCurrency(code) {
|
|
1612
|
-
return CURRENCY_TABLE[upper(code)]?.social === true;
|
|
1613
|
-
}
|
|
1614
|
-
function resolveCurrency(code, overrides = {}) {
|
|
1615
|
-
const info = CURRENCY_TABLE[upper(code)];
|
|
1616
|
-
if (!info) {
|
|
1617
|
-
return {
|
|
1618
|
-
code: overrides.code ?? code,
|
|
1619
|
-
decimals: clampDecimals(overrides.decimals ?? 2),
|
|
1620
|
-
position: overrides.position ?? "suffix",
|
|
1621
|
-
separator: overrides.separator ?? ",",
|
|
1622
|
-
decimalChar: overrides.decimalChar ?? ".",
|
|
1623
|
-
...overrides.symbol ? { symbol: overrides.symbol, display: overrides.display ?? "symbol" } : {}
|
|
1624
|
-
};
|
|
1711
|
+
// src/spec/facts.ts
|
|
1712
|
+
function mergeFacts(base, add) {
|
|
1713
|
+
const modes = [...base.modes ?? []];
|
|
1714
|
+
for (const m of add.modes ?? []) {
|
|
1715
|
+
const i = modes.findIndex((x) => x.id === m.id);
|
|
1716
|
+
if (i < 0) modes.push({ ...m });
|
|
1717
|
+
else {
|
|
1718
|
+
const merged = { ...modes[i] };
|
|
1719
|
+
for (const [k, v] of Object.entries(m)) if (v !== void 0) merged[k] = v;
|
|
1720
|
+
modes[i] = merged;
|
|
1721
|
+
}
|
|
1625
1722
|
}
|
|
1626
1723
|
return {
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
display: overrides.display ?? "symbol",
|
|
1632
|
-
position: overrides.position ?? (info.symbolAfter ? "suffix" : "prefix"),
|
|
1633
|
-
decimals: clampDecimals(overrides.decimals ?? info.decimals),
|
|
1634
|
-
separator: overrides.separator ?? ",",
|
|
1635
|
-
decimalChar: overrides.decimalChar ?? "."
|
|
1724
|
+
...modes.length ? { modes } : {},
|
|
1725
|
+
freeSpins: add.freeSpins !== void 0 ? add.freeSpins : base.freeSpins,
|
|
1726
|
+
volatility: add.volatility ?? base.volatility,
|
|
1727
|
+
maxWinCapX: add.maxWinCapX ?? base.maxWinCapX
|
|
1636
1728
|
};
|
|
1637
1729
|
}
|
|
1638
|
-
function
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
const
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1730
|
+
function formatRtp(rtp) {
|
|
1731
|
+
return `${rtp.toFixed(2)}%`;
|
|
1732
|
+
}
|
|
1733
|
+
function formatTimes(x) {
|
|
1734
|
+
return `${x.toLocaleString("en-US", { maximumFractionDigits: 2 })}\xD7`;
|
|
1735
|
+
}
|
|
1736
|
+
function modeStatsItems(facts, tr = (s) => s) {
|
|
1737
|
+
const out = [];
|
|
1738
|
+
for (const m of facts?.modes ?? []) {
|
|
1739
|
+
if (m.rtp != null) out.push({ label: `${tr("RTP")} \xB7 ${tr(m.name)}`, value: formatRtp(m.rtp) });
|
|
1740
|
+
if (m.maxWinX != null) out.push({ label: `${tr("Max win")} \xB7 ${tr(m.name)}`, value: formatTimes(m.maxWinX) });
|
|
1741
|
+
}
|
|
1742
|
+
if (facts?.volatility) out.push({ label: tr("Volatility"), value: tr(facts.volatility) });
|
|
1743
|
+
if (facts?.maxWinCapX != null) out.push({ label: tr("Round cap"), value: formatTimes(facts.maxWinCapX) });
|
|
1744
|
+
return out;
|
|
1745
|
+
}
|
|
1746
|
+
function factsVars(facts, extra) {
|
|
1747
|
+
const vars = {};
|
|
1748
|
+
for (const m of facts?.modes ?? []) {
|
|
1749
|
+
if (!m.id) continue;
|
|
1750
|
+
vars[`name.${m.id}`] = m.name;
|
|
1751
|
+
if (m.rtp != null) vars[`rtp.${m.id}`] = formatRtp(m.rtp);
|
|
1752
|
+
if (m.maxWinX != null) vars[`maxWin.${m.id}`] = formatTimes(m.maxWinX);
|
|
1753
|
+
if (m.cost != null) vars[`cost.${m.id}`] = formatTimes(m.cost);
|
|
1754
|
+
}
|
|
1755
|
+
const fs = facts?.freeSpins;
|
|
1756
|
+
if (fs) {
|
|
1757
|
+
if (fs.count != null) vars["freeSpins.count"] = fs.count;
|
|
1758
|
+
if (fs.retrigger != null) vars["freeSpins.retrigger"] = fs.retrigger ? "can" : "cannot";
|
|
1759
|
+
}
|
|
1760
|
+
if (facts?.volatility) vars["volatility"] = facts.volatility;
|
|
1761
|
+
if (facts?.maxWinCapX != null) vars["maxWinCap"] = formatTimes(facts.maxWinCapX);
|
|
1762
|
+
return extra ? { ...vars, ...extra } : vars;
|
|
1763
|
+
}
|
|
1764
|
+
function scanBlocks(blocks, resolve = (s) => s) {
|
|
1765
|
+
const scan = { text: "", headings: "", covers: /* @__PURE__ */ new Set(), hasModeStats: false, hasLegal: false };
|
|
1766
|
+
const addText = (t) => {
|
|
1767
|
+
if (typeof t === "string" && t) scan.text += `
|
|
1768
|
+
${resolve(t)}`;
|
|
1769
|
+
};
|
|
1770
|
+
const walk = (list) => {
|
|
1771
|
+
for (const b of list ?? []) {
|
|
1772
|
+
for (const c of b.covers ?? []) scan.covers.add(c);
|
|
1773
|
+
const anyB = b;
|
|
1774
|
+
switch (b.kind) {
|
|
1775
|
+
case "mode-stats":
|
|
1776
|
+
scan.hasModeStats = true;
|
|
1777
|
+
(b.extras ?? []).forEach((it) => {
|
|
1778
|
+
addText(it.label);
|
|
1779
|
+
addText(it.value);
|
|
1780
|
+
});
|
|
1781
|
+
break;
|
|
1782
|
+
case "legal":
|
|
1783
|
+
scan.hasLegal = true;
|
|
1784
|
+
addText(b.text);
|
|
1785
|
+
break;
|
|
1786
|
+
case "heading":
|
|
1787
|
+
case "subheading":
|
|
1788
|
+
scan.headings += `
|
|
1789
|
+
${resolve(b.text)}`;
|
|
1790
|
+
addText(b.text);
|
|
1791
|
+
break;
|
|
1792
|
+
case "text":
|
|
1793
|
+
addText(b.text);
|
|
1794
|
+
break;
|
|
1795
|
+
case "callout":
|
|
1796
|
+
addText(b.title);
|
|
1797
|
+
addText(b.text);
|
|
1798
|
+
break;
|
|
1799
|
+
case "media":
|
|
1800
|
+
addText(b.title);
|
|
1801
|
+
addText(b.text);
|
|
1802
|
+
break;
|
|
1803
|
+
case "steps":
|
|
1804
|
+
b.items.forEach(addText);
|
|
1805
|
+
break;
|
|
1806
|
+
case "table":
|
|
1807
|
+
(b.columns ?? []).forEach(addText);
|
|
1808
|
+
b.rows.forEach((r) => r.forEach(addText));
|
|
1809
|
+
break;
|
|
1810
|
+
case "paytable":
|
|
1811
|
+
b.rows.forEach((r) => {
|
|
1812
|
+
addText(r.symbol);
|
|
1813
|
+
addText(r.payouts);
|
|
1814
|
+
});
|
|
1815
|
+
break;
|
|
1816
|
+
case "cards":
|
|
1817
|
+
b.items.forEach((it) => {
|
|
1818
|
+
addText(it.title);
|
|
1819
|
+
addText(it.text);
|
|
1820
|
+
});
|
|
1821
|
+
break;
|
|
1822
|
+
case "stat-grid":
|
|
1823
|
+
b.items.forEach((it) => {
|
|
1824
|
+
addText(it.label);
|
|
1825
|
+
addText(it.value);
|
|
1826
|
+
});
|
|
1827
|
+
break;
|
|
1828
|
+
case "group":
|
|
1829
|
+
addText(b.title);
|
|
1830
|
+
walk(b.children);
|
|
1831
|
+
break;
|
|
1832
|
+
default:
|
|
1833
|
+
addText(anyB.label);
|
|
1834
|
+
addText(anyB.hint);
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
walk(blocks);
|
|
1840
|
+
return scan;
|
|
1841
|
+
}
|
|
1842
|
+
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1843
|
+
function mentionsRtp(text, rtp) {
|
|
1844
|
+
const s = String(rtp);
|
|
1845
|
+
const [int = "", frac = ""] = s.split(".");
|
|
1846
|
+
const body = frac ? `${escapeRe(int)}[.,]${escapeRe(frac)}0*` : `${escapeRe(int)}(?:[.,]0{1,2})?`;
|
|
1847
|
+
return new RegExp(`\\b${body}\\s*%`).test(text);
|
|
1848
|
+
}
|
|
1849
|
+
function mentionsTimes(text, x) {
|
|
1850
|
+
const digits = String(Math.trunc(Math.abs(x)));
|
|
1851
|
+
let grouped = "";
|
|
1852
|
+
for (let i = 0; i < digits.length; i++) {
|
|
1853
|
+
if (i > 0 && (digits.length - i) % 3 === 0) grouped += "[,.\\s\\u00a0]?";
|
|
1854
|
+
grouped += digits[i];
|
|
1855
|
+
}
|
|
1856
|
+
const frac = Math.abs(x) % 1 !== 0 ? `[.,]\\d+` : `(?:[.,]\\d+)?`;
|
|
1857
|
+
return new RegExp(`\\b${grouped}${frac}\\s*[x\xD7]`, "i").test(text);
|
|
1858
|
+
}
|
|
1859
|
+
var mentionsName = (text, name) => text.toLowerCase().includes(name.toLowerCase());
|
|
1860
|
+
function auditRules(facts, rules, opts = {}) {
|
|
1861
|
+
const out = [];
|
|
1862
|
+
try {
|
|
1863
|
+
const scan = scanBlocks(rules, opts.resolve);
|
|
1864
|
+
const add = (level, code, topic, message) => {
|
|
1865
|
+
out.push({ level, code, topic, message });
|
|
1866
|
+
};
|
|
1867
|
+
const covered = (topic) => scan.covers.has(topic);
|
|
1868
|
+
for (const m of facts?.modes ?? []) {
|
|
1869
|
+
const nameIn = mentionsName(scan.text, m.name);
|
|
1870
|
+
if (m.rtp == null) {
|
|
1871
|
+
add("required", "facts-missing-rtp", `rtp:${m.id}`, `\u201C${m.name}\u201D declares no RTP \u2014 set facts.modes[].rtp so the rules can state it.`);
|
|
1872
|
+
} else if (!(scan.hasModeStats || covered(`rtp:${m.id}`) || nameIn && mentionsRtp(scan.text, m.rtp))) {
|
|
1873
|
+
add("required", "rules-missing-rtp", `rtp:${m.id}`, `The RTP of \u201C${m.name}\u201D (${formatRtp(m.rtp)}) is not stated in the rules.`);
|
|
1874
|
+
}
|
|
1875
|
+
if (m.maxWinX == null) {
|
|
1876
|
+
add("required", "facts-missing-maxwin", `maxwin:${m.id}`, `\u201C${m.name}\u201D declares no max win \u2014 set facts.modes[].maxWinX so the rules can state it.`);
|
|
1877
|
+
} else if (!(scan.hasModeStats || covered(`maxwin:${m.id}`) || nameIn && mentionsTimes(scan.text, m.maxWinX))) {
|
|
1878
|
+
add("required", "rules-missing-maxwin", `maxwin:${m.id}`, `The max win of \u201C${m.name}\u201D (${formatTimes(m.maxWinX)}) is not stated in the rules.`);
|
|
1879
|
+
}
|
|
1880
|
+
if (m.kind && m.kind !== "base" && !(covered(`feature:${m.id}`) || nameIn)) {
|
|
1881
|
+
add("required", "rules-missing-feature", `feature:${m.id}`, `The configured feature \u201C${m.name}\u201D is not described in the rules.`);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
const fs = facts?.freeSpins;
|
|
1885
|
+
if (fs === void 0) {
|
|
1886
|
+
if (facts?.modes?.length) {
|
|
1887
|
+
add("recommended", "facts-missing-freespins", "freespins", "Free-spins info is highly recommended: declare facts.freeSpins { count, retrigger } \u2014 or freeSpins: false if the game has none.");
|
|
1888
|
+
}
|
|
1889
|
+
} else if (fs !== false) {
|
|
1890
|
+
const fsIn = covered("freespins") || /free\s*spins?/i.test(scan.text);
|
|
1891
|
+
if (!fsIn) {
|
|
1892
|
+
add("recommended", "rules-missing-freespins", "freespins", "The rules never mention the free spins this game declares.");
|
|
1893
|
+
} else {
|
|
1894
|
+
if (fs.count != null && !covered("freespins:count") && !new RegExp(`\\b${fs.count}\\b[\\s\\S]{0,60}?spins?|spins?[\\s\\S]{0,60}?\\b${fs.count}\\b`, "i").test(scan.text)) {
|
|
1895
|
+
add("recommended", "rules-missing-fs-count", "freespins:count", `State that the bonus awards exactly ${fs.count} free spins.`);
|
|
1896
|
+
}
|
|
1897
|
+
if (fs.retrigger != null && !covered("freespins:retrigger") && !/re-?trigger/i.test(scan.text)) {
|
|
1898
|
+
add("recommended", "rules-missing-fs-retrigger", "freespins:retrigger", `State that free spins ${fs.retrigger ? "CAN" : "CANNOT"} be retriggered.`);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
if (rules?.length && !scan.hasLegal && !/malfunction/i.test(scan.text)) {
|
|
1903
|
+
add("recommended", "rules-missing-legal", "legal", "Add the platform legal disclaimer (a `legal` block).");
|
|
1904
|
+
}
|
|
1905
|
+
if (rules?.length && !covered("controls") && !/\bcontrols?\b/i.test(scan.headings)) {
|
|
1906
|
+
add("recommended", "rules-missing-controls", "controls", 'Describe every interactive control (a \u201CControls\u201D section or covers: ["controls"]).');
|
|
1907
|
+
}
|
|
1908
|
+
out.sort((a, b) => a.level === b.level ? 0 : a.level === "required" ? -1 : 1);
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1911
|
+
return out;
|
|
1654
1912
|
}
|
|
1655
1913
|
|
|
1656
1914
|
// src/OpenUI.ts
|
|
@@ -1702,6 +1960,11 @@ var OpenUI = class {
|
|
|
1702
1960
|
replay = new Signal(false);
|
|
1703
1961
|
/** Game name + version (shown in the menu footer; for support / certification). */
|
|
1704
1962
|
gameInfo = {};
|
|
1963
|
+
/** Declared GAME FACTS (modes + RTP/max win, free spins, volatility, cap) — seeded
|
|
1964
|
+
* from `spec.facts`, extended at runtime via {@link declareFacts} (the buy-feature
|
|
1965
|
+
* modal declares its configured features automatically). Drives the `mode-stats`
|
|
1966
|
+
* rules block + the rules-completeness audit in the info menu. */
|
|
1967
|
+
facts = new Signal({});
|
|
1705
1968
|
/** Minimum round duration (ms) from jurisdiction — stored for the GAME to enforce. */
|
|
1706
1969
|
minimumRoundDuration = 0;
|
|
1707
1970
|
/** Buy-feature confirm threshold, as a multiple of the base bet. A play (buy or
|
|
@@ -1709,6 +1972,13 @@ var OpenUI = class {
|
|
|
1709
1972
|
* — Stake requires that bet modes above 2× never activate on a single click. `0`
|
|
1710
1973
|
* (the default) = off. Set via `spec.buyFeature.confirmAboveCost` or a jurisdiction. */
|
|
1711
1974
|
confirmBuyAboveCost = 0;
|
|
1975
|
+
/** When autoplay stops while the balance can no longer cover the next round —
|
|
1976
|
+
* whether the built-in RG enforcement stopped it, the game's own pre-check did, or
|
|
1977
|
+
* the player pressed Stop already broke — pop the SAME insufficient-funds modal a
|
|
1978
|
+
* manual spin shows (ERR_IPB) instead of ending silently. A normal stop with funds
|
|
1979
|
+
* in hand shows nothing. Default on; `spec.autoplay.insufficientFundsNotice: false`
|
|
1980
|
+
* opts out. */
|
|
1981
|
+
autoplayInsufficientNotice = true;
|
|
1712
1982
|
sessionNet = 0;
|
|
1713
1983
|
prevVolumes = null;
|
|
1714
1984
|
/** The frozen UISpec `createUI` built this from, if any — authored = run = tested. */
|
|
@@ -1848,6 +2118,13 @@ var OpenUI = class {
|
|
|
1848
2118
|
this.disposers.push(this.betStepper.index.subscribe(syncStepperButtons));
|
|
1849
2119
|
this.bet.set(this.betStepper.value);
|
|
1850
2120
|
syncStepperButtons();
|
|
2121
|
+
this.disposers.push(
|
|
2122
|
+
this.bus.on("autoplayStopped", () => {
|
|
2123
|
+
if (!this.autoplayInsufficientNotice || this.replay.get()) return;
|
|
2124
|
+
if (this.noticePanel.isOpen) return;
|
|
2125
|
+
if (this.balance.get() < this.bet.get()) this.showRgsError("ERR_IPB");
|
|
2126
|
+
})
|
|
2127
|
+
);
|
|
1851
2128
|
this.disposers.push(
|
|
1852
2129
|
this.noticePanel.state.subscribe(() => {
|
|
1853
2130
|
if (this.noticePanel.isOpen) return;
|
|
@@ -1938,6 +2215,12 @@ var OpenUI = class {
|
|
|
1938
2215
|
if (this.autoplay.isActive && this.balance.get() < this.bet.get()) this.autoplay.stop();
|
|
1939
2216
|
}
|
|
1940
2217
|
}
|
|
2218
|
+
/** Merge more game facts into {@link facts} (modes merge by id; defined incoming
|
|
2219
|
+
* fields win). The info menu re-audits + re-renders its rules automatically. */
|
|
2220
|
+
declareFacts(add) {
|
|
2221
|
+
if (!add || typeof add !== "object") return;
|
|
2222
|
+
this.facts.set(mergeFacts(this.facts.get(), add));
|
|
2223
|
+
}
|
|
1941
2224
|
/** Reset the running session net + aggregates + timer (e.g. on a fresh session). */
|
|
1942
2225
|
resetSession() {
|
|
1943
2226
|
this.sessionNet = 0;
|
|
@@ -2271,6 +2554,12 @@ function walkBlock(b, src, out) {
|
|
|
2271
2554
|
add(it.value);
|
|
2272
2555
|
});
|
|
2273
2556
|
break;
|
|
2557
|
+
case "mode-stats":
|
|
2558
|
+
(b.extras ?? []).forEach((it) => {
|
|
2559
|
+
add(it.label);
|
|
2560
|
+
add(it.value);
|
|
2561
|
+
});
|
|
2562
|
+
break;
|
|
2274
2563
|
case "group":
|
|
2275
2564
|
add(b.title);
|
|
2276
2565
|
b.children.forEach((c, i) => walkBlock(c, `${src}.children[${i}]`, out));
|
|
@@ -2364,6 +2653,7 @@ function createUI(spec = {}, hooks = {}) {
|
|
|
2364
2653
|
}
|
|
2365
2654
|
if (typeof spec.rtp === "number") ui.rtp.set(spec.rtp);
|
|
2366
2655
|
if (spec.game) ui.gameInfo = { name: spec.game.name, version: spec.game.version };
|
|
2656
|
+
if (spec.facts) ui.declareFacts(spec.facts);
|
|
2367
2657
|
if (spec.betLadder?.levels?.length) {
|
|
2368
2658
|
ui.betStepper.setLevels(spec.betLadder.levels, spec.betLadder.index ?? 0);
|
|
2369
2659
|
ui.bet.set(ui.betStepper.value);
|
|
@@ -2376,6 +2666,7 @@ function createUI(spec = {}, hooks = {}) {
|
|
|
2376
2666
|
}
|
|
2377
2667
|
if (spec.autoplay?.lossLimits) ui.autoplay.setLossLimitOptions(spec.autoplay.lossLimits);
|
|
2378
2668
|
if (spec.autoplay?.winLimits) ui.autoplay.setWinLimitOptions(spec.autoplay.winLimits);
|
|
2669
|
+
if (spec.autoplay?.insufficientFundsNotice === false) ui.autoplayInsufficientNotice = false;
|
|
2379
2670
|
if (spec.turbo) {
|
|
2380
2671
|
ui.turbo.setModes(resolveTurboModes(spec.turbo.modes));
|
|
2381
2672
|
if (spec.turbo.index != null) ui.turbo.setIndex(spec.turbo.index);
|
|
@@ -2491,6 +2782,43 @@ function composeMenu(menu, opts = {}) {
|
|
|
2491
2782
|
return out;
|
|
2492
2783
|
}
|
|
2493
2784
|
|
|
2785
|
+
// src/spec/rulesDoc.ts
|
|
2786
|
+
function isRulesDoc(doc) {
|
|
2787
|
+
if (!doc || typeof doc !== "object") return false;
|
|
2788
|
+
const d = doc;
|
|
2789
|
+
return d.version === 1 && Array.isArray(d.blocks);
|
|
2790
|
+
}
|
|
2791
|
+
var mergeLocaleMaps = (under, over) => {
|
|
2792
|
+
const out = {};
|
|
2793
|
+
for (const src of [under, over]) {
|
|
2794
|
+
for (const [loc, map] of Object.entries(src ?? {})) out[loc] = { ...out[loc], ...map };
|
|
2795
|
+
}
|
|
2796
|
+
return out;
|
|
2797
|
+
};
|
|
2798
|
+
function applyRulesDoc(spec, doc) {
|
|
2799
|
+
if (!isRulesDoc(doc)) return spec;
|
|
2800
|
+
const messages = mergeLocaleMaps(doc.messages, spec.locale?.messages);
|
|
2801
|
+
const socialMessages = mergeLocaleMaps(doc.socialMessages, spec.locale?.socialMessages);
|
|
2802
|
+
return {
|
|
2803
|
+
...spec,
|
|
2804
|
+
menu: { ...spec.menu, rules: doc.blocks },
|
|
2805
|
+
locale: {
|
|
2806
|
+
locale: spec.locale?.locale ?? "en",
|
|
2807
|
+
messages: Object.keys(messages).length ? messages : { en: {} },
|
|
2808
|
+
...Object.keys(socialMessages).length ? { socialMessages } : {}
|
|
2809
|
+
},
|
|
2810
|
+
...doc.facts ? { facts: mergeFacts(doc.facts, spec.facts ?? {}) } : {}
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
function auditRulesDoc(doc, opts = {}) {
|
|
2814
|
+
if (!isRulesDoc(doc)) return [];
|
|
2815
|
+
const facts = opts.facts ? mergeFacts(doc.facts ?? {}, opts.facts) : doc.facts;
|
|
2816
|
+
const dict = doc.messages?.[opts.locale ?? "en"] ?? {};
|
|
2817
|
+
const vars = factsVars(facts);
|
|
2818
|
+
const resolve = (s) => (dict[s] ?? s).replace(/\{\{([\w.-]+)\}\}/g, (_m, k) => String(vars[k] ?? `{{${k}}}`));
|
|
2819
|
+
return auditRules(facts, doc.blocks, { resolve });
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2494
2822
|
// src/spec/defaultHudSpec.ts
|
|
2495
2823
|
var defaultHudSpec = Object.freeze({
|
|
2496
2824
|
meta: { id: "open-ui-reference-hud", version: 1 },
|
|
@@ -2580,6 +2908,12 @@ function buildBetLadder(cfg, divisor = API_AMOUNT_DIVISOR) {
|
|
|
2580
2908
|
const found = levels.findIndex((v) => v >= want);
|
|
2581
2909
|
return { levels, index: found < 0 ? 0 : found };
|
|
2582
2910
|
}
|
|
2911
|
+
function resolveBetLadder(availableBets, defaultBet, fallback = [0.2, 0.5, 1, 2, 5, 10]) {
|
|
2912
|
+
const levels = availableBets?.length ? availableBets.slice() : fallback.slice();
|
|
2913
|
+
const exact = levels.indexOf(defaultBet);
|
|
2914
|
+
const index = exact >= 0 ? exact : Math.max(0, levels.findIndex((b) => b >= defaultBet));
|
|
2915
|
+
return { levels, index };
|
|
2916
|
+
}
|
|
2583
2917
|
function clampBet(amount, cfg, divisor = API_AMOUNT_DIVISOR) {
|
|
2584
2918
|
const min = (cfg.minBet ?? 0) / divisor;
|
|
2585
2919
|
const max = (cfg.maxBet ?? Infinity) / divisor;
|
|
@@ -2589,6 +2923,6 @@ function clampBet(amount, cfg, divisor = API_AMOUNT_DIVISOR) {
|
|
|
2589
2923
|
return Math.min(max, Math.max(min, v));
|
|
2590
2924
|
}
|
|
2591
2925
|
|
|
2592
|
-
export { API_AMOUNT_DIVISOR, AutoplayControl, BLOCK_KINDS, ButtonControl, CURRENCY_TABLE, CardControl, Control, ControlRegistry, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, DictionaryTranslator, EventBus, EventLog, Fade, LOCALE_LABELS, OpenUI, PanelControl, Parallel, Pulse, RGS_ERROR_KEYS, ReadoutControl, SelectControl, Sequence, Signal, SliderControl, SpinControl, Squish, StepperControl, ToggleControl, TurboControl, Turn, ValueDisplay, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, checkSocialPhrases, clampBet, clampDecimals, clampDigits, clampMinDigits, collectSocialCopy, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, findForbiddenPhrases, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
|
|
2926
|
+
export { API_AMOUNT_DIVISOR, AutoplayControl, BLOCK_KINDS, ButtonControl, CURRENCY_TABLE, CardControl, Control, ControlRegistry, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, DictionaryTranslator, EventBus, EventLog, Fade, LOCALE_LABELS, OpenUI, PanelControl, Parallel, Pulse, RGS_ERROR_KEYS, ReadoutControl, SelectControl, Sequence, Signal, SliderControl, SpinControl, Squish, StepperControl, ToggleControl, TurboControl, Turn, ValueDisplay, applyJurisdiction, applyRulesDoc, auditRules, auditRulesDoc, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, checkSocialPhrases, clampBet, clampDecimals, clampDigits, clampMinDigits, collectSocialCopy, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, factsVars, findForbiddenPhrases, formatAmount, formatAmountPrecise, formatRtp, formatTimes, installResponsive, integerDigits, isRulesDoc, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, mergeFacts, modeStatsItems, neededDecimals, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveBetLadder, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
|
|
2593
2927
|
//# sourceMappingURL=index.js.map
|
|
2594
2928
|
//# sourceMappingURL=index.js.map
|