@open-slot-ui/core 0.4.2 → 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/index.cjs +247 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -19
- package/dist/index.d.ts +121 -19
- package/dist/index.js +245 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -99,7 +99,9 @@ var defaultLayoutConfig = {
|
|
|
99
99
|
refLandscape: [1920, 1080],
|
|
100
100
|
refPortrait: [1080, 2337],
|
|
101
101
|
portraitBelowAspect: 0.85,
|
|
102
|
-
breakpoints: { mobile: 480, tablet: 840 }
|
|
102
|
+
breakpoints: { mobile: 480, tablet: 840 },
|
|
103
|
+
// Bottom bar hugs the screen bottom; extra height opens above (reel area).
|
|
104
|
+
stageAnchor: [0.5, 1]
|
|
103
105
|
};
|
|
104
106
|
function breakpointFor(width, height, cfg) {
|
|
105
107
|
const short = Math.min(Math.max(width, 1), Math.max(height, 1));
|
|
@@ -113,7 +115,11 @@ function computeScreen(width, height, cfg) {
|
|
|
113
115
|
const orientation = w / h < cfg.portraitBelowAspect ? "portrait" : "landscape";
|
|
114
116
|
const [rw, rh] = orientation === "portrait" ? cfg.refPortrait : cfg.refLandscape;
|
|
115
117
|
const scale = Math.min(w / rw, h / rh);
|
|
116
|
-
|
|
118
|
+
const sw = rw * scale;
|
|
119
|
+
const sh = rh * scale;
|
|
120
|
+
const [ax, ay] = cfg.stageAnchor ?? [0.5, 1];
|
|
121
|
+
const stage = { x: (w - sw) * ax, y: (h - sh) * ay, width: sw, height: sh };
|
|
122
|
+
return { width: w, height: h, orientation, breakpoint: breakpointFor(w, h, cfg), scale, stage };
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
// src/layout/anchor.ts
|
|
@@ -125,9 +131,10 @@ function anchorFactors(anchor) {
|
|
|
125
131
|
function resolvePlacement(spec, screen) {
|
|
126
132
|
const [ax, ay] = anchorFactors(spec.anchor);
|
|
127
133
|
const [ox, oy] = spec.offset ?? [0, 0];
|
|
134
|
+
const st = screen.stage;
|
|
128
135
|
return {
|
|
129
|
-
x: ax *
|
|
130
|
-
y: ay *
|
|
136
|
+
x: st.x + ax * st.width + ox * screen.scale,
|
|
137
|
+
y: st.y + ay * st.height + oy * screen.scale,
|
|
131
138
|
scale: screen.scale * (spec.scale ?? 1),
|
|
132
139
|
rotation: (spec.rotation ?? 0) * Math.PI / 180
|
|
133
140
|
};
|
|
@@ -135,14 +142,21 @@ function resolvePlacement(spec, screen) {
|
|
|
135
142
|
|
|
136
143
|
// src/layout/defaultLayouts.ts
|
|
137
144
|
var landscapeDefaultLayouts = Object.freeze({
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
// Measured 1:1 from the Figma "Desk DEF" frame (1920×1080): spin ~0.95 of the base
|
|
146
|
+
// 220px coin, dropped to −124, autoplay/turbo flanking at ±204. (The homogeneous
|
|
147
|
+
// stage keeps this off the reels, so it no longer needs shrinking.)
|
|
148
|
+
spin: { anchor: "bottom-center", offset: [0, -124], scale: 0.95 },
|
|
149
|
+
autoplay: { anchor: "bottom-center", offset: [-204, -100] },
|
|
150
|
+
turbo: { anchor: "bottom-center", offset: [204, -100] },
|
|
141
151
|
balance: { anchor: "bottom-left", offset: [135, -104], rotation: -5 },
|
|
142
152
|
bet: { anchor: "bottom-right", offset: [-186, -104], rotation: 5 },
|
|
143
153
|
"bet-plus": { anchor: "bottom-right", offset: [-120, -188], rotation: 5 },
|
|
144
154
|
"bet-minus": { anchor: "bottom-right", offset: [-120, -104], rotation: 5 },
|
|
145
155
|
bonus: { anchor: "bottom-center", offset: [740, -320] },
|
|
156
|
+
// Bonus "total win" readout — occupies the buy-feature corner; the renderer swaps
|
|
157
|
+
// the two so only one shows at a time (buy in base play, total-win in a bonus round).
|
|
158
|
+
// Right-anchored so a long localized caption grows LEFT and never clips the edge.
|
|
159
|
+
"total-win": { anchor: "bottom-right", offset: [-150, -300] },
|
|
146
160
|
settings: { anchor: "bottom-center", offset: [-740, -320], rotation: -5 },
|
|
147
161
|
mute: { anchor: "top-right", offset: [-122, 46] },
|
|
148
162
|
fullscreen: { anchor: "top-right", offset: [-46, 46] },
|
|
@@ -157,6 +171,9 @@ var portraitDefaultLayouts = Object.freeze({
|
|
|
157
171
|
autoplay: { anchor: "bottom-center", offset: [-258, -510], scale: 1.5 },
|
|
158
172
|
turbo: { anchor: "bottom-center", offset: [258, -510], scale: 1.5 },
|
|
159
173
|
bonus: { anchor: "bottom-center", offset: [-438, -480], scale: 1.3 },
|
|
174
|
+
// Figma "Mobile FS": the total-win sits on the LEFT at the spin's level (where the
|
|
175
|
+
// buy coin lives in base play), left-aligned + tilted −10° — NOT centred over spin.
|
|
176
|
+
"total-win": { anchor: "bottom-left", offset: [30, -520], rotation: -10 },
|
|
160
177
|
settings: { anchor: "bottom-center", offset: [438, -480], scale: 1.5, rotation: -5 },
|
|
161
178
|
"bet-minus": { anchor: "bottom-center", offset: [-105, -267], scale: 1.5 },
|
|
162
179
|
"bet-plus": { anchor: "bottom-center", offset: [105, -267], scale: 1.5 },
|
|
@@ -862,6 +879,10 @@ var ReadoutControl = class extends Control {
|
|
|
862
879
|
value = new Signal(0);
|
|
863
880
|
/** Whether a `'duration'` readout is currently advancing. */
|
|
864
881
|
running = new Signal(false);
|
|
882
|
+
/** Accent emphasis: the renderer tints the value in the theme accent while on — for
|
|
883
|
+
* a MODIFIED readout (e.g. net position while a bet boost is active). Mirrors
|
|
884
|
+
* {@link ValueDisplay.emphasized}. */
|
|
885
|
+
emphasized = new Signal(false);
|
|
865
886
|
kind;
|
|
866
887
|
label;
|
|
867
888
|
decimals;
|
|
@@ -881,6 +902,10 @@ var ReadoutControl = class extends Control {
|
|
|
881
902
|
set(v) {
|
|
882
903
|
this.value.set(safeAmount(v, this.value.get()));
|
|
883
904
|
}
|
|
905
|
+
/** Accent-tint the value ("this readout is modified"). Strict boolean. */
|
|
906
|
+
setEmphasis(on) {
|
|
907
|
+
this.emphasized.set(on === true);
|
|
908
|
+
}
|
|
884
909
|
get() {
|
|
885
910
|
return this.value.get();
|
|
886
911
|
}
|
|
@@ -1407,6 +1432,7 @@ var openuiDefaults = {
|
|
|
1407
1432
|
"openui.bet": "Bet",
|
|
1408
1433
|
"openui.balance": "Balance",
|
|
1409
1434
|
"openui.win": "Win",
|
|
1435
|
+
"openui.totalWin": "Total Win",
|
|
1410
1436
|
"openui.menu": "Menu",
|
|
1411
1437
|
"openui.rules": "Rules",
|
|
1412
1438
|
"openui.paytable": "Paytable",
|
|
@@ -1456,6 +1482,13 @@ var openuiSocialDefaults = {
|
|
|
1456
1482
|
"openui.bet": "Play",
|
|
1457
1483
|
"openui.balance": "Balance",
|
|
1458
1484
|
"openui.win": "Prize",
|
|
1485
|
+
"openui.totalWin": "Total Prize",
|
|
1486
|
+
// "Paytable" (contains "pay") and "Insufficient funds" ("funds") are restricted in
|
|
1487
|
+
// social mode → their compliant equivalents. The literal "Paytable" is overridden too
|
|
1488
|
+
// because the menu SECTION heading uses it as its key (DEFAULT_MENU_TITLES).
|
|
1489
|
+
"openui.paytable": "Prizes",
|
|
1490
|
+
Paytable: "Prizes",
|
|
1491
|
+
"openui.err.insufficient.title": "Insufficient balance",
|
|
1459
1492
|
"openui.buyFeature.title": "Play bonus",
|
|
1460
1493
|
"openui.buyFeature.message": "Play this bonus now?",
|
|
1461
1494
|
"openui.err.insufficient.message": "You don't have enough balance for this play.",
|
|
@@ -1504,27 +1537,67 @@ function dictionary(messages, locale, socialMessages) {
|
|
|
1504
1537
|
}
|
|
1505
1538
|
|
|
1506
1539
|
// src/format/currency.ts
|
|
1507
|
-
var ZERO_DECIMAL = ["JPY", "KRW", "VND", "CLP", "ISK", "HUF", "TWD", "UGX", "XAF", "XOF", "PYG", "RWF"];
|
|
1508
|
-
var CRYPTO = { BTC: 8, ETH: 8, LTC: 8, BCH: 8, DOGE: 8, XRP: 6, SOL: 6, TRX: 6, USDT: 2, USDC: 2 };
|
|
1509
1540
|
var CURRENCY_TABLE = Object.freeze({
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1541
|
+
// ── fiat — symbol BEFORE the amount ──────────────────────────────────────────
|
|
1542
|
+
USD: { symbol: "$", decimals: 2 },
|
|
1543
|
+
CAD: { symbol: "CA$", decimals: 2 },
|
|
1544
|
+
JPY: { symbol: "\xA5", decimals: 0 },
|
|
1545
|
+
EUR: { symbol: "\u20AC", decimals: 2 },
|
|
1546
|
+
RUB: { symbol: "\u20BD", decimals: 2 },
|
|
1547
|
+
CNY: { symbol: "CN\xA5", decimals: 2 },
|
|
1548
|
+
PHP: { symbol: "\u20B1", decimals: 2 },
|
|
1549
|
+
INR: { symbol: "\u20B9", decimals: 2 },
|
|
1550
|
+
IDR: { symbol: "Rp", decimals: 0 },
|
|
1551
|
+
KRW: { symbol: "\u20A9", decimals: 0 },
|
|
1552
|
+
BRL: { symbol: "R$", decimals: 2 },
|
|
1553
|
+
MXN: { symbol: "MX$", decimals: 2 },
|
|
1554
|
+
TRY: { symbol: "\u20BA", decimals: 2 },
|
|
1555
|
+
GBP: { symbol: "\xA3", decimals: 2 },
|
|
1556
|
+
// ── fiat — symbol AFTER the amount ───────────────────────────────────────────
|
|
1557
|
+
DKK: { symbol: "kr", decimals: 2, symbolAfter: true },
|
|
1558
|
+
PLN: { symbol: "z\u0142", decimals: 2, symbolAfter: true },
|
|
1559
|
+
VND: { symbol: "\u20AB", decimals: 0, symbolAfter: true },
|
|
1560
|
+
CLP: { symbol: "CLP", decimals: 0, symbolAfter: true },
|
|
1561
|
+
ARS: { symbol: "ARS", decimals: 2, symbolAfter: true },
|
|
1562
|
+
PEN: { symbol: "S/", decimals: 2, symbolAfter: true },
|
|
1563
|
+
// ── crypto (8-dp, so sub-cent + high precision format correctly) ─────────────
|
|
1564
|
+
BTC: { symbol: "\u20BF", decimals: 8 },
|
|
1565
|
+
ETH: { symbol: "\u039E", decimals: 8 },
|
|
1566
|
+
LTC: { symbol: "\u0141", decimals: 8 },
|
|
1567
|
+
BCH: { symbol: "BCH", decimals: 8 },
|
|
1568
|
+
DOGE: { symbol: "\xD0", decimals: 8 },
|
|
1569
|
+
USDT: { symbol: "\u20AE", decimals: 2 },
|
|
1570
|
+
USDC: { symbol: "USDC", decimals: 2 },
|
|
1571
|
+
// ── Stake social coins — Gold Coins / Stake Cash (shown as GC / SC) ───────────
|
|
1572
|
+
XGC: { symbol: "GC", decimals: 2, social: true },
|
|
1573
|
+
XSC: { symbol: "SC", decimals: 2, social: true },
|
|
1574
|
+
GC: { symbol: "GC", decimals: 2, social: true },
|
|
1575
|
+
SC: { symbol: "SC", decimals: 2, social: true }
|
|
1517
1576
|
});
|
|
1518
1577
|
var upper = (code) => typeof code === "string" ? code.toUpperCase() : "";
|
|
1519
1578
|
function isSocialCurrency(code) {
|
|
1520
1579
|
return CURRENCY_TABLE[upper(code)]?.social === true;
|
|
1521
1580
|
}
|
|
1522
1581
|
function resolveCurrency(code, overrides = {}) {
|
|
1523
|
-
const info = CURRENCY_TABLE[upper(code)]
|
|
1582
|
+
const info = CURRENCY_TABLE[upper(code)];
|
|
1583
|
+
if (!info) {
|
|
1584
|
+
return {
|
|
1585
|
+
code: overrides.code ?? code,
|
|
1586
|
+
decimals: clampDecimals(overrides.decimals ?? 2),
|
|
1587
|
+
position: overrides.position ?? "suffix",
|
|
1588
|
+
separator: overrides.separator ?? ",",
|
|
1589
|
+
decimalChar: overrides.decimalChar ?? ".",
|
|
1590
|
+
...overrides.symbol ? { symbol: overrides.symbol, display: overrides.display ?? "symbol" } : {}
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1524
1593
|
return {
|
|
1525
|
-
|
|
1594
|
+
// Social coins carry their DISPLAY code (XGC → "GC", XSC → "SC"); fiat/crypto keep
|
|
1595
|
+
// their ISO code (the symbol is what's actually rendered under `display:'symbol'`).
|
|
1596
|
+
code: overrides.code ?? (info.social ? info.symbol : code),
|
|
1597
|
+
symbol: overrides.symbol ?? info.symbol,
|
|
1598
|
+
display: overrides.display ?? "symbol",
|
|
1599
|
+
position: overrides.position ?? (info.symbolAfter ? "suffix" : "prefix"),
|
|
1526
1600
|
decimals: clampDecimals(overrides.decimals ?? info.decimals),
|
|
1527
|
-
position: overrides.position ?? "suffix",
|
|
1528
1601
|
separator: overrides.separator ?? ",",
|
|
1529
1602
|
decimalChar: overrides.decimalChar ?? "."
|
|
1530
1603
|
};
|
|
@@ -1540,7 +1613,11 @@ function formatAmount(value, spec, opts = {}) {
|
|
|
1540
1613
|
const body = frac ? `${grouped}${dot}${frac}` : grouped;
|
|
1541
1614
|
const sign = n < 0 ? "-" : opts.signed ? "+" : "";
|
|
1542
1615
|
const number = `${sign}${body}`;
|
|
1543
|
-
|
|
1616
|
+
const symbolMode = spec.display === "symbol" && !!spec.symbol;
|
|
1617
|
+
const affix = symbolMode ? spec.symbol : spec.code;
|
|
1618
|
+
const prefix = (spec.position ?? "suffix") === "prefix";
|
|
1619
|
+
const gap = symbolMode && prefix ? "" : " ";
|
|
1620
|
+
return prefix ? `${affix}${gap}${number}` : `${number}${gap}${affix}`;
|
|
1544
1621
|
}
|
|
1545
1622
|
|
|
1546
1623
|
// src/OpenUI.ts
|
|
@@ -1602,6 +1679,9 @@ var OpenUI = class {
|
|
|
1602
1679
|
spin;
|
|
1603
1680
|
balance;
|
|
1604
1681
|
bet;
|
|
1682
|
+
/** Bonus "total win" readout — shown in the buy-feature slot during a bonus/free-
|
|
1683
|
+
* spins round (the renderer swaps it for the buy button). Same money logic as `bet`. */
|
|
1684
|
+
totalWin;
|
|
1605
1685
|
settingsButton;
|
|
1606
1686
|
settingsPanel;
|
|
1607
1687
|
musicSlider;
|
|
@@ -1650,6 +1730,13 @@ var OpenUI = class {
|
|
|
1650
1730
|
currency: { code: "USD", decimals: 2 },
|
|
1651
1731
|
initial: 1
|
|
1652
1732
|
});
|
|
1733
|
+
this.totalWin = new ValueDisplay({
|
|
1734
|
+
id: "total-win",
|
|
1735
|
+
label: "openui.totalWin",
|
|
1736
|
+
layout: defaultLayout("total-win"),
|
|
1737
|
+
currency: { code: "USD", decimals: 2 },
|
|
1738
|
+
initial: 0
|
|
1739
|
+
});
|
|
1653
1740
|
this.settingsButton = new ButtonControl(
|
|
1654
1741
|
{ id: "settings", layout: defaultLayout("settings") },
|
|
1655
1742
|
this.bus
|
|
@@ -1679,6 +1766,7 @@ var OpenUI = class {
|
|
|
1679
1766
|
this.spin,
|
|
1680
1767
|
this.balance,
|
|
1681
1768
|
this.bet,
|
|
1769
|
+
this.totalWin,
|
|
1682
1770
|
this.settingsButton,
|
|
1683
1771
|
this.settingsPanel,
|
|
1684
1772
|
this.musicSlider,
|
|
@@ -2055,6 +2143,127 @@ function installResponsive(ui, responsive) {
|
|
|
2055
2143
|
};
|
|
2056
2144
|
}
|
|
2057
2145
|
|
|
2146
|
+
// src/spec/socialPhrases.ts
|
|
2147
|
+
var FORBIDDEN = [
|
|
2148
|
+
{ re: /\bpay\w*/i, term: "pay*", replacement: 'prize / award (e.g. "paytable" \u2192 "prizes")' },
|
|
2149
|
+
{ re: /\bpayout\w*/i, term: "payout", replacement: "award" },
|
|
2150
|
+
{ re: /\bbets?\b|\bbetting\b/i, term: "bet", replacement: "play" },
|
|
2151
|
+
{ re: /\bwager\w*/i, term: "wager", replacement: "play" },
|
|
2152
|
+
{ re: /\bgambl\w*/i, term: "gamble", replacement: "play" },
|
|
2153
|
+
{ re: /\bcash\w*/i, term: "cash", replacement: "coins" },
|
|
2154
|
+
{ re: /\bfunds?\b/i, term: "funds", replacement: "coins / balance" },
|
|
2155
|
+
{ re: /\bmoney\b/i, term: "money", replacement: "coins" },
|
|
2156
|
+
{ re: /\bdeposit\w*/i, term: "deposit", replacement: "purchase" },
|
|
2157
|
+
{ re: /\bwithdraw\w*/i, term: "withdraw", replacement: "redeem" },
|
|
2158
|
+
{ re: /\bcredits?\b/i, term: "credit", replacement: "coins / balance" },
|
|
2159
|
+
{ re: /\bwinnings?\b/i, term: "winnings", replacement: "prizes" },
|
|
2160
|
+
{ re: /\bjackpots?\b/i, term: "jackpot", replacement: "grand prize" },
|
|
2161
|
+
{ re: /\breal[-\s]?money\b/i, term: "real money", replacement: "coins" }
|
|
2162
|
+
];
|
|
2163
|
+
function findForbiddenPhrases(text) {
|
|
2164
|
+
if (typeof text !== "string" || !text) return [];
|
|
2165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2166
|
+
const out = [];
|
|
2167
|
+
for (const r of FORBIDDEN) {
|
|
2168
|
+
if (r.re.test(text) && !seen.has(r.term)) {
|
|
2169
|
+
seen.add(r.term);
|
|
2170
|
+
out.push({ term: r.term, replacement: r.replacement });
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
return out;
|
|
2174
|
+
}
|
|
2175
|
+
function walkBlock(b, src, out) {
|
|
2176
|
+
const add = (t, s = src) => {
|
|
2177
|
+
if (typeof t === "string" && t) out.push({ source: s, text: t });
|
|
2178
|
+
};
|
|
2179
|
+
const anyB = b;
|
|
2180
|
+
switch (b.kind) {
|
|
2181
|
+
case "text":
|
|
2182
|
+
case "heading":
|
|
2183
|
+
case "subheading":
|
|
2184
|
+
case "legal":
|
|
2185
|
+
add(b.text);
|
|
2186
|
+
break;
|
|
2187
|
+
case "callout":
|
|
2188
|
+
add(b.title);
|
|
2189
|
+
add(b.text);
|
|
2190
|
+
break;
|
|
2191
|
+
case "media":
|
|
2192
|
+
add(b.title);
|
|
2193
|
+
add(b.text);
|
|
2194
|
+
break;
|
|
2195
|
+
case "slider":
|
|
2196
|
+
case "toggle":
|
|
2197
|
+
case "button":
|
|
2198
|
+
case "select":
|
|
2199
|
+
case "stepper":
|
|
2200
|
+
case "value":
|
|
2201
|
+
add(anyB.label);
|
|
2202
|
+
add(anyB.hint);
|
|
2203
|
+
break;
|
|
2204
|
+
case "steps":
|
|
2205
|
+
b.items.forEach((s, i) => add(s, `${src}.items[${i}]`));
|
|
2206
|
+
break;
|
|
2207
|
+
case "table":
|
|
2208
|
+
(b.columns ?? []).forEach((c) => add(c));
|
|
2209
|
+
b.rows.forEach((row) => row.forEach((c) => add(c)));
|
|
2210
|
+
break;
|
|
2211
|
+
case "paytable":
|
|
2212
|
+
b.rows.forEach((r) => {
|
|
2213
|
+
add(r.symbol);
|
|
2214
|
+
add(r.payouts);
|
|
2215
|
+
});
|
|
2216
|
+
break;
|
|
2217
|
+
case "cards":
|
|
2218
|
+
b.items.forEach((it) => {
|
|
2219
|
+
add(it.title);
|
|
2220
|
+
add(it.text);
|
|
2221
|
+
});
|
|
2222
|
+
break;
|
|
2223
|
+
case "stat-grid":
|
|
2224
|
+
b.items.forEach((it) => {
|
|
2225
|
+
add(it.label);
|
|
2226
|
+
add(it.value);
|
|
2227
|
+
});
|
|
2228
|
+
break;
|
|
2229
|
+
case "group":
|
|
2230
|
+
add(b.title);
|
|
2231
|
+
b.children.forEach((c, i) => walkBlock(c, `${src}.children[${i}]`, out));
|
|
2232
|
+
break;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
function collectSocialCopy(spec) {
|
|
2236
|
+
const out = [];
|
|
2237
|
+
const menu = spec.menu;
|
|
2238
|
+
if (menu) {
|
|
2239
|
+
const sections = [
|
|
2240
|
+
["settings", menu.settings],
|
|
2241
|
+
["paytable", menu.paytable],
|
|
2242
|
+
["rules", menu.rules]
|
|
2243
|
+
];
|
|
2244
|
+
for (const [sec, blocks] of sections) (blocks ?? []).forEach((b, i) => walkBlock(b, `menu.${sec}[${i}]`, out));
|
|
2245
|
+
if (menu.titles) {
|
|
2246
|
+
for (const [k, v] of Object.entries(menu.titles)) if (v) out.push({ source: `menu.titles.${k}`, text: v });
|
|
2247
|
+
}
|
|
2248
|
+
if (menu.banner?.src) ;
|
|
2249
|
+
}
|
|
2250
|
+
(spec.rules ?? []).forEach((b, i) => walkBlock(b, `rules[${i}]`, out));
|
|
2251
|
+
if (spec.game?.name) out.push({ source: "game.name", text: spec.game.name });
|
|
2252
|
+
const en = spec.locale?.messages?.en;
|
|
2253
|
+
if (en) {
|
|
2254
|
+
for (const [k, v] of Object.entries(en)) if (typeof v === "string") out.push({ source: `locale.en[${JSON.stringify(k)}]`, text: v });
|
|
2255
|
+
}
|
|
2256
|
+
return out;
|
|
2257
|
+
}
|
|
2258
|
+
function checkSocialPhrases(spec) {
|
|
2259
|
+
const issues = [];
|
|
2260
|
+
for (const { source, text } of collectSocialCopy(spec)) {
|
|
2261
|
+
const matches = findForbiddenPhrases(text);
|
|
2262
|
+
if (matches.length) issues.push({ source, text, matches });
|
|
2263
|
+
}
|
|
2264
|
+
return issues;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2058
2267
|
// src/spec/createUI.ts
|
|
2059
2268
|
function resolveTurboModes(modes) {
|
|
2060
2269
|
if (Array.isArray(modes)) return modes.length ? modes : ["off", "on"];
|
|
@@ -2084,12 +2293,27 @@ function createUI(spec = {}, hooks = {}) {
|
|
|
2084
2293
|
const cur = typeof spec.currency === "string" ? resolveCurrency(spec.currency) : spec.currency;
|
|
2085
2294
|
ui.balance.setCurrency(cur);
|
|
2086
2295
|
ui.bet.setCurrency(cur);
|
|
2296
|
+
ui.totalWin.setCurrency(cur);
|
|
2087
2297
|
ui.netPosition.setCurrency(cur);
|
|
2088
2298
|
}
|
|
2089
2299
|
if (spec.social) {
|
|
2090
2300
|
if (typeof spec.social === "object") ui.setSocial(true, spec.social.coin);
|
|
2091
2301
|
else ui.setSocial(true);
|
|
2092
2302
|
}
|
|
2303
|
+
if (spec.social || spec.jurisdiction?.socialCasino) {
|
|
2304
|
+
const found = checkSocialPhrases(spec);
|
|
2305
|
+
for (const issue of found) {
|
|
2306
|
+
hooks.onDataIssue?.({
|
|
2307
|
+
level: "warn",
|
|
2308
|
+
path: issue.source,
|
|
2309
|
+
code: "social-forbidden-phrase",
|
|
2310
|
+
message: `Social mode restricted wording in "${issue.text}": ${issue.matches.map((m) => `\u201C${m.term}\u201D \u2192 ${m.replacement}`).join("; ")}`
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
if (found.length && typeof console !== "undefined") {
|
|
2314
|
+
console.warn(`[open-ui] social mode: ${found.length} string(s) contain restricted phrases (see onDataIssue). First: ${found[0].source} \u2192 "${found[0].text}"`);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2093
2317
|
if (typeof spec.rtp === "number") ui.rtp.set(spec.rtp);
|
|
2094
2318
|
if (spec.game) ui.gameInfo = { name: spec.game.name, version: spec.game.version };
|
|
2095
2319
|
if (spec.betLadder?.levels?.length) {
|
|
@@ -2314,6 +2538,6 @@ function clampBet(amount, cfg, divisor = API_AMOUNT_DIVISOR) {
|
|
|
2314
2538
|
return Math.min(max, Math.max(min, v));
|
|
2315
2539
|
}
|
|
2316
2540
|
|
|
2317
|
-
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, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
|
|
2541
|
+
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 };
|
|
2318
2542
|
//# sourceMappingURL=index.js.map
|
|
2319
2543
|
//# sourceMappingURL=index.js.map
|