@open-slot-ui/pixi 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +15 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +16 -203
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -740,7 +740,9 @@ interface FeatureSpec {
|
|
|
740
740
|
id: string;
|
|
741
741
|
name: string;
|
|
742
742
|
variant: 'buy' | 'boost';
|
|
743
|
-
/**
|
|
743
|
+
/** Cost as a multiple of the base bet. For `buy` this is the FULL stake multiple
|
|
744
|
+
* (deducted = cost × bet); for `boost` it is the SURCHARGE over the base bet, so the
|
|
745
|
+
* full per-spin cost shown/deducted is (1 + cost) × bet. */
|
|
744
746
|
cost: number;
|
|
745
747
|
/** Card image URL (or data URI). Optional — a neutral gradient is used when absent. */
|
|
746
748
|
image?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -740,7 +740,9 @@ interface FeatureSpec {
|
|
|
740
740
|
id: string;
|
|
741
741
|
name: string;
|
|
742
742
|
variant: 'buy' | 'boost';
|
|
743
|
-
/**
|
|
743
|
+
/** Cost as a multiple of the base bet. For `buy` this is the FULL stake multiple
|
|
744
|
+
* (deducted = cost × bet); for `boost` it is the SURCHARGE over the base bet, so the
|
|
745
|
+
* full per-spin cost shown/deducted is (1 + cost) × bet. */
|
|
744
746
|
cost: number;
|
|
745
747
|
/** Card image URL (or data URI). Optional — a neutral gradient is used when absent. */
|
|
746
748
|
image?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControlView, ButtonView, SliderView, buildBlockColumn, OpenUIPixi } from './chunk-Z7X5JH7H.js';
|
|
2
2
|
export { AutoplayDrawerView, AutoplayView, ButtonView, ControlView, DialogView, MenuView, OpenUIPixi, ReadoutView, SelectView, SliderView, SpinView, StepperView, TextCellRenderer, ToggleView, TurboView, Tweener, ValueDisplayView, buildBlockColumn, defaultSpinSkin, drawSpin, isDesktop, svgSpinSkin } from './chunk-Z7X5JH7H.js';
|
|
3
3
|
import { Text, Graphics, Container, Rectangle } from 'pixi.js';
|
|
4
|
-
import { LOCALE_LABELS, createUI, composeMenu, buildPanel,
|
|
4
|
+
import { INFO_MENU_VARS, escapeHtml, LOCALE_LABELS, renderBlocksHtml, INFO_MENU_CSS, createUI, composeMenu, buildPanel, formatAmountPrecise, factsVars, renderRulesAuditHtml, auditRules } from '@open-slot-ui/core';
|
|
5
5
|
|
|
6
6
|
var RISE = 26;
|
|
7
7
|
var STAGGER = 38;
|
|
@@ -278,101 +278,16 @@ var PanelBodyView = class extends ControlView {
|
|
|
278
278
|
super.dispose();
|
|
279
279
|
}
|
|
280
280
|
};
|
|
281
|
-
var esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
282
|
-
var rich = (s) => esc(s).replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
283
|
-
function statGridHtml(items, tr) {
|
|
284
|
-
const rows = items.map((it) => `<div><dt>${esc(tr(it.label))}</dt><dd>${esc(tr(it.value))}</dd></div>`).join("");
|
|
285
|
-
return `<dl class="ohm-stats">${rows}</dl>`;
|
|
286
|
-
}
|
|
287
|
-
function renderBlocks(blocks, tr, facts) {
|
|
288
|
-
const out = [];
|
|
289
|
-
for (const b of blocks) {
|
|
290
|
-
switch (b.kind) {
|
|
291
|
-
case "text":
|
|
292
|
-
out.push(`<p>${rich(tr(b.text))}</p>`);
|
|
293
|
-
break;
|
|
294
|
-
case "heading":
|
|
295
|
-
out.push(`<div class="ohm-sec"><span>${esc(tr(b.text))}</span></div>`);
|
|
296
|
-
break;
|
|
297
|
-
case "subheading":
|
|
298
|
-
out.push(`<h4 class="ohm-subh">${esc(tr(b.text))}</h4>`);
|
|
299
|
-
break;
|
|
300
|
-
case "legal":
|
|
301
|
-
out.push(`<p class="ohm-legal">${rich(tr(b.text))}</p>`);
|
|
302
|
-
break;
|
|
303
|
-
case "divider":
|
|
304
|
-
out.push('<hr class="ohm-hr">');
|
|
305
|
-
break;
|
|
306
|
-
case "image":
|
|
307
|
-
out.push(`<img class="ohm-feature" alt="${esc(tr(b.alt ?? ""))}" src="${b.src}" loading="lazy">`);
|
|
308
|
-
break;
|
|
309
|
-
case "media": {
|
|
310
|
-
const img = `<img alt="${esc(tr(b.alt ?? ""))}" src="${b.src}" loading="lazy">`;
|
|
311
|
-
const body = `<div class="ohm-media-body">${b.title ? `<h4>${esc(tr(b.title))}</h4>` : ""}<p>${rich(tr(b.text))}</p></div>`;
|
|
312
|
-
out.push(`<div class="ohm-media ohm-media--${b.side ?? "left"}">${img}${body}</div>`);
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
case "cards": {
|
|
316
|
-
const cards = b.items.map((it) => `<div class="ohm-fcard">${it.icon ? `<img src="${it.icon}" alt="" loading="lazy">` : ""}<h5>${esc(tr(it.title))}</h5>${it.text ? `<p>${rich(tr(it.text))}</p>` : ""}</div>`).join("");
|
|
317
|
-
out.push(`<div class="ohm-cards">${cards}</div>`);
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
case "paytable":
|
|
321
|
-
out.push(`<div class="ohm-grid">${renderPaytable(b.rows, tr)}</div>`);
|
|
322
|
-
break;
|
|
323
|
-
case "table": {
|
|
324
|
-
const head = b.columns?.length ? `<thead><tr>${b.columns.map((c) => `<th>${esc(tr(c))}</th>`).join("")}</tr></thead>` : "";
|
|
325
|
-
const body = b.rows.map((r) => `<tr>${r.map((c) => `<td>${esc(tr(c))}</td>`).join("")}</tr>`).join("");
|
|
326
|
-
out.push(`<table class="ohm-table">${head}<tbody>${body}</tbody></table>`);
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
case "stat-grid":
|
|
330
|
-
out.push(statGridHtml(b.items, tr));
|
|
331
|
-
break;
|
|
332
|
-
// The AUTO per-mode RTP / Max-win grid — rendered straight from the declared
|
|
333
|
-
// game facts (+ any host extras), so the table can never drift from the config.
|
|
334
|
-
// `modeStatsItems` localizes the label PARTS itself ("Max win" · the mode name),
|
|
335
|
-
// so the grid renderer gets identity-tr — no double translation.
|
|
336
|
-
case "mode-stats": {
|
|
337
|
-
const extras = (b.extras ?? []).map((e) => ({ label: tr(e.label), value: tr(e.value) }));
|
|
338
|
-
out.push(statGridHtml([...modeStatsItems(facts, tr), ...extras], (s) => s));
|
|
339
|
-
break;
|
|
340
|
-
}
|
|
341
|
-
case "steps": {
|
|
342
|
-
const items = b.items.map((s) => `<li>${rich(tr(s))}</li>`).join("");
|
|
343
|
-
out.push(b.ordered ? `<ol class="ohm-steps">${items}</ol>` : `<ul class="ohm-steps">${items}</ul>`);
|
|
344
|
-
break;
|
|
345
|
-
}
|
|
346
|
-
case "callout":
|
|
347
|
-
out.push(`<div class="ohm-callout ohm-callout--${b.tone ?? "info"}">${b.title ? `<b>${esc(tr(b.title))}</b>` : ""}<p>${rich(tr(b.text))}</p></div>`);
|
|
348
|
-
break;
|
|
349
|
-
case "group":
|
|
350
|
-
out.push(`<div class="ohm-group">${b.title ? `<h4 class="ohm-subh">${esc(tr(b.title))}</h4>` : ""}${renderBlocks(b.children, tr)}</div>`);
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return out.join("\n");
|
|
355
|
-
}
|
|
356
|
-
function renderPaytable(rows, tr) {
|
|
357
|
-
return rows.map((r) => {
|
|
358
|
-
const icon = r.icon ? `<img class="ohm-symimg" src="${r.icon}" alt="" loading="lazy">` : `<span class="ohm-emoji">${esc(tr(r.symbol ?? ""))}</span>`;
|
|
359
|
-
const lines = r.payouts.split("\n").map((line) => {
|
|
360
|
-
const i = line.indexOf(":");
|
|
361
|
-
return i >= 0 ? `<div><b>${esc(line.slice(0, i))}</b><span>${esc(line.slice(i + 1))}</span></div>` : `<div><span>${esc(line)}</span></div>`;
|
|
362
|
-
}).join("");
|
|
363
|
-
return `<div class="ohm-sym">${icon}<div class="ohm-pay">${lines}</div></div>`;
|
|
364
|
-
}).join("");
|
|
365
|
-
}
|
|
366
281
|
function renderSettingBlock(b, tr) {
|
|
367
|
-
const hint = "hint" in b && b.hint ? `<div class="ohm-hint">${
|
|
368
|
-
const label = "label" in b && b.label ?
|
|
282
|
+
const hint = "hint" in b && b.hint ? `<div class="ohm-hint">${escapeHtml(tr(b.hint))}</div>` : "";
|
|
283
|
+
const label = "label" in b && b.label ? escapeHtml(tr(b.label)) : "";
|
|
369
284
|
switch (b.kind) {
|
|
370
285
|
case "toggle":
|
|
371
286
|
return `<div class="ohm-setting"><label class="ohm-row ohm-check"><span>${label}</span><span class="ohm-ctl"><input data-set="${b.id}" type="checkbox"${b.on ? " checked" : ""}></span></label>${hint}</div>`;
|
|
372
287
|
case "slider":
|
|
373
288
|
return `<div class="ohm-setting"><label class="ohm-row"><span>${label}</span><input data-set="${b.id}" type="range" min="0" max="1" step="0.01" value="${b.initial ?? 1}"></label>${hint}</div>`;
|
|
374
289
|
case "select": {
|
|
375
|
-
const opts = b.options.map((o, i) => `<option value="${
|
|
290
|
+
const opts = b.options.map((o, i) => `<option value="${escapeHtml(o.value)}"${i === (b.index ?? 0) ? " selected" : ""}>${escapeHtml(tr(o.label))}</option>`).join("");
|
|
376
291
|
return `<div class="ohm-setting"><label class="ohm-row"><span>${label}</span><select data-set="${b.id}">${opts}</select></label>${hint}</div>`;
|
|
377
292
|
}
|
|
378
293
|
default:
|
|
@@ -385,25 +300,16 @@ function mountInfoMenu(app, ui) {
|
|
|
385
300
|
const disposers = [];
|
|
386
301
|
const host2 = document.createElement("div");
|
|
387
302
|
host2.className = "ohm-root";
|
|
388
|
-
const vars = {
|
|
389
|
-
"--accent": "#d99000",
|
|
390
|
-
"--accent-text": "#1a1200",
|
|
391
|
-
"--surface": "#ffffff",
|
|
392
|
-
"--surface-alt": "#eef1f6",
|
|
393
|
-
"--text": "#181b20",
|
|
394
|
-
"--text-dim": "#5b6472",
|
|
395
|
-
"--card-radius": "8px",
|
|
396
|
-
"--font": ui.theme.type.family
|
|
397
|
-
};
|
|
303
|
+
const vars = { ...INFO_MENU_VARS, "--font": ui.theme.type.family };
|
|
398
304
|
for (const [k, v] of Object.entries(vars)) host2.style.setProperty(k, v);
|
|
399
305
|
const locales = ui.spec?.locale ? Array.from(/* @__PURE__ */ new Set([ui.spec.locale.locale, ...Object.keys(ui.spec.locale.messages)])) : [];
|
|
400
|
-
const langRow = locales.length > 1 ? `<div class="ohm-setting"><label class="ohm-row"><span>${tr("Language")}</span><select id="ohm-lang">${locales.map((c) => `<option value="${c}"${c === ui.locale.get() ? " selected" : ""}>${
|
|
306
|
+
const langRow = locales.length > 1 ? `<div class="ohm-setting"><label class="ohm-row"><span>${tr("Language")}</span><select id="ohm-lang">${locales.map((c) => `<option value="${c}"${c === ui.locale.get() ? " selected" : ""}>${escapeHtml(LOCALE_LABELS[c] ?? c)}</option>`).join("")}</select></label></div>` : "";
|
|
401
307
|
const turbo = ui.turbo;
|
|
402
308
|
const cap = (m) => m.charAt(0).toUpperCase() + m.slice(1);
|
|
403
|
-
const turboCtl = turbo.modeCount <= 2 ? `<span class="ohm-ctl"><input id="ohm-turbo" type="checkbox"></span>` : `<span class="ohm-ctl"><div class="ohm-segmented" id="ohm-turbo-seg">${turbo.modes.map((m, i) => `<button class="ohm-seg" data-i="${i}">${
|
|
309
|
+
const turboCtl = turbo.modeCount <= 2 ? `<span class="ohm-ctl"><input id="ohm-turbo" type="checkbox"></span>` : `<span class="ohm-ctl"><div class="ohm-segmented" id="ohm-turbo-seg">${turbo.modes.map((m, i) => `<button class="ohm-seg" data-i="${i}">${escapeHtml(tr(cap(m)))}</button>`).join("")}</div></span>`;
|
|
404
310
|
const customSettings = (menu.settings ?? []).map((b) => renderSettingBlock(b, tr)).join("");
|
|
405
311
|
const soundMode = menu.sound ?? "sliders";
|
|
406
|
-
const volRow = (id, label, hint, val) => `<div class="ohm-setting"><label class="ohm-row"><span>${
|
|
312
|
+
const volRow = (id, label, hint, val) => `<div class="ohm-setting"><label class="ohm-row"><span>${escapeHtml(tr(label))}</span><input class="ohm-slider" data-vol="${id}" type="range" min="0" max="1" step="0.01" value="${val}"></label><div class="ohm-hint">${escapeHtml(tr(hint))}</div></div>`;
|
|
407
313
|
const soundSliders = soundMode === "master" ? volRow("master", "Volume", "Adjust the overall game volume.", ui.sfxSlider.value.get()) : soundMode === "sliders" ? volRow("music", "Music", "Adjust the background music volume.", ui.musicSlider.value.get()) + volRow("sfx", "Effects", "Adjust the sound-effects volume.", ui.sfxSlider.value.get()) : "";
|
|
408
314
|
const rulesTr = () => {
|
|
409
315
|
const vars2 = factsVars(ui.facts.get(), { "game.name": ui.gameInfo.name ?? "", "game.version": ui.gameInfo.version ?? "" });
|
|
@@ -411,25 +317,16 @@ function mountInfoMenu(app, ui) {
|
|
|
411
317
|
};
|
|
412
318
|
const auditCardHtml = (trr) => {
|
|
413
319
|
if (!menu.rules?.length) return "";
|
|
414
|
-
|
|
415
|
-
if (!issues.length) return "";
|
|
416
|
-
const req = issues.filter((i) => i.level === "required");
|
|
417
|
-
const rec = issues.filter((i) => i.level === "recommended");
|
|
418
|
-
const list = (items) => `<ul>${items.map((i) => `<li>${esc(tr(i.message))}</li>`).join("")}</ul>`;
|
|
419
|
-
return `<div class="ohm-audit" role="alert">
|
|
420
|
-
<div class="ohm-audit-title">\u26A0 ${esc(tr("openui.rulesAudit.title"))}</div>
|
|
421
|
-
${req.length ? `<div class="ohm-audit-sub">${esc(tr("openui.rulesAudit.required"))}</div>${list(req)}` : ""}
|
|
422
|
-
${rec.length ? `<div class="ohm-audit-sub ohm-audit-sub--rec">${esc(tr("openui.rulesAudit.recommended"))}</div><div class="ohm-audit-rec">${list(rec)}</div>` : ""}
|
|
423
|
-
</div>`;
|
|
320
|
+
return renderRulesAuditHtml(auditRules(ui.facts.get(), menu.rules, { resolve: trr }), tr);
|
|
424
321
|
};
|
|
425
322
|
const rulesInnerHtml = () => {
|
|
426
323
|
if (!menu.rules) return "";
|
|
427
324
|
const trr = rulesTr();
|
|
428
|
-
return auditCardHtml(trr) +
|
|
325
|
+
return auditCardHtml(trr) + renderBlocksHtml(menu.rules, trr, ui.facts.get());
|
|
429
326
|
};
|
|
430
|
-
const paytableHtml = menu.paytable ?
|
|
327
|
+
const paytableHtml = menu.paytable ? renderBlocksHtml(menu.paytable, tr, ui.facts.get()) : "";
|
|
431
328
|
const rulesHtml = rulesInnerHtml();
|
|
432
|
-
const banner = menu.banner ? `<img class="ohm-logo" alt="" src="${menu.banner.src}"${menu.banner.width ? ` width="${menu.banner.width}"` : ""}${menu.banner.height ? ` height="${menu.banner.height}"` : ""}>` : ui.gameInfo.name ? `<h1 class="ohm-logo-text">${
|
|
329
|
+
const banner = menu.banner ? `<img class="ohm-logo" alt="" src="${menu.banner.src}"${menu.banner.width ? ` width="${menu.banner.width}"` : ""}${menu.banner.height ? ` height="${menu.banner.height}"` : ""}>` : ui.gameInfo.name ? `<h1 class="ohm-logo-text">${escapeHtml(ui.gameInfo.name)}</h1>` : "";
|
|
433
330
|
host2.innerHTML = `
|
|
434
331
|
<div class="ohm-backdrop" data-close></div>
|
|
435
332
|
<button class="ohm-x" data-close aria-label="Close">\u2715</button>
|
|
@@ -447,7 +344,7 @@ function mountInfoMenu(app, ui) {
|
|
|
447
344
|
</div>
|
|
448
345
|
</div>`;
|
|
449
346
|
const style = document.createElement("style");
|
|
450
|
-
style.textContent =
|
|
347
|
+
style.textContent = INFO_MENU_CSS;
|
|
451
348
|
host2.appendChild(style);
|
|
452
349
|
document.body.appendChild(host2);
|
|
453
350
|
const $ = (sel) => host2.querySelector(sel);
|
|
@@ -537,91 +434,6 @@ function mountInfoMenu(app, ui) {
|
|
|
537
434
|
host2.remove();
|
|
538
435
|
};
|
|
539
436
|
}
|
|
540
|
-
var OHM_CSS = `
|
|
541
|
-
.ohm-root { position: fixed; inset: 0; z-index: 10000; display: grid; place-items: center; font-family: var(--font); opacity: 0; pointer-events: none; transition: opacity .18s ease; }
|
|
542
|
-
.ohm-root.open { opacity: 1; pointer-events: auto; }
|
|
543
|
-
.ohm-backdrop { position: absolute; inset: 0; background: rgba(8,6,4,0); backdrop-filter: blur(0px) saturate(1); -webkit-backdrop-filter: blur(0px) saturate(1); transition: background .4s ease, backdrop-filter .4s ease, -webkit-backdrop-filter .4s ease; }
|
|
544
|
-
.ohm-root.open .ohm-backdrop { background: rgba(8,6,4,.34); backdrop-filter: blur(6px) saturate(1.1); -webkit-backdrop-filter: blur(6px) saturate(1.1); }
|
|
545
|
-
.ohm-card { position: relative; width: min(92%, 1100px); max-height: 86vh; display: flex; flex-direction: column; background: var(--surface); color: var(--text); border: 1.5px solid #000; border-radius: var(--card-radius); box-shadow: 0 30px 80px rgba(0,0,0,.5); overflow: hidden; transform: translateY(8px) scale(.99); transition: transform .18s ease; }
|
|
546
|
-
.ohm-root.open .ohm-card { transform: none; }
|
|
547
|
-
.ohm-x { position: absolute; top: 18px; right: 22px; width: 46px; height: 46px; border-radius: 999px; border: 0; background: rgba(18,14,10,.82); color: #fff; font-size: 18px; cursor: pointer; display: grid; place-items: center; box-shadow: 0 6px 18px rgba(0,0,0,.45); z-index: 2; transition: transform .12s, background .12s; }
|
|
548
|
-
.ohm-x:hover { transform: scale(1.08); background: rgba(18,14,10,.95); }
|
|
549
|
-
.ohm-body { padding: 24px 26px 26px; overflow-y: scroll; }
|
|
550
|
-
.ohm-body::-webkit-scrollbar { width: 18px; }
|
|
551
|
-
.ohm-body::-webkit-scrollbar-track { background: transparent; margin: 12px 0; }
|
|
552
|
-
.ohm-body::-webkit-scrollbar-thumb { background-color: #111; border: 6px solid transparent; background-clip: padding-box; border-radius: 999px; min-height: 44px; }
|
|
553
|
-
.ohm-body::-webkit-scrollbar-thumb:hover { background-color: #000; }
|
|
554
|
-
.ohm-logo { display: block; margin: 6px auto 18px; max-width: 64%; height: auto; }
|
|
555
|
-
.ohm-logo-text { margin: 6px 0 18px; text-align: center; font-size: 30px; font-weight: 900; letter-spacing: 1px; color: var(--text); }
|
|
556
|
-
.ohm-sec { display: flex; align-items: center; gap: 14px; margin: 26px 0 14px; color: var(--text); font-weight: 800; letter-spacing: 1px; }
|
|
557
|
-
.ohm-sec::before, .ohm-sec::after { content: ""; flex: 1; height: 2px; background: color-mix(in srgb, var(--text) 80%, transparent); border-radius: 2px; }
|
|
558
|
-
.ohm-root *, .ohm-root *::before, .ohm-root *::after { box-sizing: border-box; }
|
|
559
|
-
.ohm-row { display: flex; align-items: center; gap: 16px; margin: 14px 0; font-weight: 700; }
|
|
560
|
-
.ohm-setting { margin: 14px 0; }
|
|
561
|
-
.ohm-setting .ohm-row { margin: 0; }
|
|
562
|
-
.ohm-hint { margin: 3px 0 0; font-size: 12.5px; font-weight: 500; color: var(--text-dim); }
|
|
563
|
-
.ohm-row > span:first-child { flex: none; min-width: 110px; }
|
|
564
|
-
.ohm-row input[type=range] { flex: 1; min-width: 0; max-width: min(440px, 60dvw); accent-color: var(--accent); height: 6px; }
|
|
565
|
-
.ohm-row select { flex: 1; min-width: 0; max-width: min(440px, 60dvw); padding: 11px 14px; border-radius: 4px; border: 2px solid var(--accent); background: var(--surface-alt); color: var(--text); font-weight: 700; font-size: 15px; cursor: pointer; }
|
|
566
|
-
.ohm-row select option { background: var(--surface); color: var(--text); font-weight: 600; }
|
|
567
|
-
.ohm-row select option:checked { background: var(--accent); color: var(--accent-text); }
|
|
568
|
-
.ohm-ctl { flex: 1; min-width: 0; max-width: min(440px, 60dvw); display: flex; align-items: center; justify-content: flex-end; }
|
|
569
|
-
.ohm-check input[type=checkbox] { appearance: none; -webkit-appearance: none; width: 50px; height: 28px; border-radius: 999px; background: color-mix(in srgb, var(--text-dim) 38%, transparent); position: relative; cursor: pointer; transition: background .15s; flex: none; }
|
|
570
|
-
.ohm-check input[type=checkbox]:checked { background: var(--accent); }
|
|
571
|
-
.ohm-check input[type=checkbox]::before { content: ""; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 999px; background: #fff; transition: left .15s; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
|
|
572
|
-
.ohm-check input[type=checkbox]:checked::before { left: 25px; }
|
|
573
|
-
.ohm-segmented { display: inline-flex; gap: 4px; padding: 4px; background: var(--surface-alt); border-radius: 999px; border: 1px solid color-mix(in srgb, var(--text-dim) 30%, transparent); }
|
|
574
|
-
.ohm-seg { border: 0; background: transparent; color: var(--text-dim); font-weight: 700; font-size: 14px; padding: 8px 18px; border-radius: 999px; cursor: pointer; transition: background .12s, color .12s; }
|
|
575
|
-
.ohm-seg.active { background: var(--accent); color: var(--accent-text); }
|
|
576
|
-
.ohm-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
|
|
577
|
-
.ohm-sym { display: flex; align-items: center; gap: 16px; padding: 6px 4px; }
|
|
578
|
-
.ohm-emoji { font-size: 48px; line-height: 1; filter: drop-shadow(0 2px 4px rgba(0,0,0,.3)); }
|
|
579
|
-
.ohm-symimg { width: 56px; height: 56px; object-fit: contain; flex: none; }
|
|
580
|
-
.ohm-pay { font-size: 13px; line-height: 1.6; }
|
|
581
|
-
.ohm-pay div { display: flex; gap: 6px; }
|
|
582
|
-
.ohm-pay b { min-width: 42px; }
|
|
583
|
-
.ohm-pay span { color: var(--accent); font-weight: 700; }
|
|
584
|
-
.ohm-body p { color: var(--text-dim); line-height: 1.6; }
|
|
585
|
-
.ohm-body p b { color: var(--text); }
|
|
586
|
-
.ohm-feature { display: block; width: 100%; height: auto; margin: 10px 0; }
|
|
587
|
-
.ohm-stats { margin: 12px 0; display: grid; grid-template-columns: 1fr 1fr; gap: 0 28px; }
|
|
588
|
-
.ohm-stats > div { display: flex; justify-content: space-between; padding: 9px 0; border-bottom: 1px solid color-mix(in srgb, var(--text-dim) 20%, transparent); }
|
|
589
|
-
.ohm-stats dt { color: var(--text-dim); margin: 0; } .ohm-stats dd { margin: 0; font-weight: 700; }
|
|
590
|
-
.ohm-audit { margin: 12px 0 18px; padding: 14px 16px; border-radius: 12px; border: 2px solid #d03131; background: color-mix(in srgb, #d03131 8%, transparent); }
|
|
591
|
-
.ohm-audit-title { font-weight: 900; letter-spacing: .5px; color: #b31d1d; }
|
|
592
|
-
.ohm-audit-sub { margin-top: 8px; font-size: 13px; font-weight: 800; color: #b31d1d; }
|
|
593
|
-
.ohm-audit-sub--rec { color: #b07d09; }
|
|
594
|
-
.ohm-audit ul { margin: 6px 0 0; padding-left: 20px; color: var(--text); font-size: 13.5px; line-height: 1.55; }
|
|
595
|
-
.ohm-audit li { margin: 3px 0; }
|
|
596
|
-
.ohm-audit-rec ul { color: var(--text-dim); }
|
|
597
|
-
.ohm-callout { margin: 16px 0 4px; padding: 14px 16px; border-radius: 12px; border-left: 4px solid var(--accent); background: color-mix(in srgb, var(--accent) 9%, transparent); }
|
|
598
|
-
.ohm-callout b { color: var(--accent); } .ohm-callout p { margin: 4px 0 0; color: var(--text); }
|
|
599
|
-
.ohm-callout--warning { border-left-color: #e0a106; background: color-mix(in srgb, #e0a106 10%, transparent); }
|
|
600
|
-
.ohm-callout--warning b { color: #b07d09; }
|
|
601
|
-
.ohm-subh { margin: 22px 0 8px; font-size: 15px; font-weight: 800; letter-spacing: .5px; color: var(--text); }
|
|
602
|
-
.ohm-legal { font-size: 12px; line-height: 1.6; color: var(--text-dim); opacity: .85; }
|
|
603
|
-
.ohm-hr { border: 0; border-top: 1px solid color-mix(in srgb, var(--text-dim) 30%, transparent); margin: 18px 0; }
|
|
604
|
-
.ohm-media { display: flex; align-items: center; gap: 18px; margin: 14px 0; }
|
|
605
|
-
.ohm-media--right { flex-direction: row-reverse; }
|
|
606
|
-
.ohm-media > img { width: 40%; max-width: 320px; height: auto; flex: none; }
|
|
607
|
-
.ohm-media-body { flex: 1; }
|
|
608
|
-
.ohm-media-body h4 { margin: 0 0 6px; font-size: 16px; color: var(--text); }
|
|
609
|
-
.ohm-media-body p { margin: 0; }
|
|
610
|
-
.ohm-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 12px 0; }
|
|
611
|
-
.ohm-fcard { padding: 8px 6px; text-align: center; }
|
|
612
|
-
.ohm-fcard img { display: block; width: 48px; height: 48px; margin: 0 auto 8px; }
|
|
613
|
-
.ohm-fcard h5 { margin: 0 0 4px; font-size: 14px; color: var(--text); }
|
|
614
|
-
.ohm-fcard p { margin: 0; font-size: 12px; line-height: 1.5; }
|
|
615
|
-
.ohm-table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 14px; }
|
|
616
|
-
.ohm-table th { text-align: left; padding: 8px 10px; font-weight: 800; color: var(--text); border-bottom: 2px solid color-mix(in srgb, var(--text-dim) 35%, transparent); }
|
|
617
|
-
.ohm-table td { padding: 8px 10px; border-bottom: 1px solid color-mix(in srgb, var(--text-dim) 18%, transparent); }
|
|
618
|
-
.ohm-table td:first-child { color: var(--text); font-weight: 700; }
|
|
619
|
-
.ohm-table td:not(:first-child) { color: var(--accent); font-weight: 700; }
|
|
620
|
-
.ohm-steps { margin: 12px 0; padding-left: 22px; color: var(--text-dim); line-height: 1.7; }
|
|
621
|
-
.ohm-steps li { margin: 5px 0; }
|
|
622
|
-
.ohm-steps b { color: var(--text); }
|
|
623
|
-
.ohm-group { margin: 8px 0; }
|
|
624
|
-
`;
|
|
625
437
|
|
|
626
438
|
// src/confirmModal.ts
|
|
627
439
|
var esc2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -877,7 +689,7 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
|
|
|
877
689
|
const layout = () => {
|
|
878
690
|
const vw = window.innerWidth;
|
|
879
691
|
const vh = window.innerHeight;
|
|
880
|
-
const cols =
|
|
692
|
+
const cols = vw >= 720 && (vw >= 900 || vh <= 540) ? Math.min(4, list.length) : Math.min(2, list.length);
|
|
881
693
|
cardsEl.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
|
|
882
694
|
const fitW = Math.min(vw * 0.96, cols >= 4 ? 1180 : cols === 1 ? 380 : 760);
|
|
883
695
|
fitEl.style.width = `${fitW}px`;
|
|
@@ -894,7 +706,8 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
|
|
|
894
706
|
cardsEl.innerHTML = list.map((f) => {
|
|
895
707
|
const active = boosts.has(f.id);
|
|
896
708
|
const buyBlocked = f.variant === "buy" && blocksBuy && boosts.size > 0;
|
|
897
|
-
const
|
|
709
|
+
const perSpin = (f.variant === "buy" ? f.cost : 1 + f.cost) * bet;
|
|
710
|
+
const price = money(perSpin, cur);
|
|
898
711
|
const label = f.variant === "buy" ? tr("Buy") : active ? tr("Activated") : tr("Activate");
|
|
899
712
|
const cls = `bfm-action bfm-action--${f.variant}${active ? " is-active" : ""}${buyBlocked ? " is-blocked" : ""}`;
|
|
900
713
|
const img = f.image ? ` style="background-image:url('${f.image}')"` : "";
|