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