@open-slot-ui/pixi 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 CHANGED
@@ -208,6 +208,9 @@ var SpinView = class extends ControlView {
208
208
  }),
209
209
  // re-dim if slam-stop is toggled at runtime (e.g. applyJurisdiction)
210
210
  this.spin.allowSlamStop.subscribe(() => this.updateInteractive()),
211
+ // the ref-counted HUD lock (menu / buy modal / blocking notice) gates
212
+ // `spin.interactable` — refresh eventMode/cursor when it flips
213
+ this.ui.locked.subscribe(() => this.updateInteractive()),
211
214
  this.spin.onTransition((t) => this.play(t)),
212
215
  // free-spins face: switch between the normal skin and the "N FS" counter
213
216
  this.spin.freeSpins.subscribe(() => this.updateFaces()),
@@ -3104,7 +3107,7 @@ var ReadoutView = class extends ControlView {
3104
3107
  let text;
3105
3108
  switch (this.ro.kind) {
3106
3109
  case "currency":
3107
- text = this.ro.currency ? core.formatAmount(v, this.ro.currency.get(), { signed: this.ro.signed }) : String(v);
3110
+ text = this.ro.currency ? core.formatAmountPrecise(v, this.ro.currency.get(), { signed: this.ro.signed }) : String(v);
3108
3111
  break;
3109
3112
  case "percent":
3110
3113
  text = `${v.toFixed(this.ro.decimals)}%`;
@@ -3987,7 +3990,11 @@ var PanelBodyView = class extends ControlView {
3987
3990
  };
3988
3991
  var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3989
3992
  var rich = (s) => esc(s).replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
3990
- function renderBlocks(blocks, tr) {
3993
+ function statGridHtml(items, tr) {
3994
+ const rows = items.map((it) => `<div><dt>${esc(tr(it.label))}</dt><dd>${esc(tr(it.value))}</dd></div>`).join("");
3995
+ return `<dl class="ohm-stats">${rows}</dl>`;
3996
+ }
3997
+ function renderBlocks(blocks, tr, facts) {
3991
3998
  const out = [];
3992
3999
  for (const b of blocks) {
3993
4000
  switch (b.kind) {
@@ -4029,9 +4036,16 @@ function renderBlocks(blocks, tr) {
4029
4036
  out.push(`<table class="ohm-table">${head}<tbody>${body}</tbody></table>`);
4030
4037
  break;
4031
4038
  }
4032
- case "stat-grid": {
4033
- const rows = b.items.map((it) => `<div><dt>${esc(tr(it.label))}</dt><dd>${esc(tr(it.value))}</dd></div>`).join("");
4034
- out.push(`<dl class="ohm-stats">${rows}</dl>`);
4039
+ case "stat-grid":
4040
+ out.push(statGridHtml(b.items, tr));
4041
+ break;
4042
+ // The AUTO per-mode RTP / Max-win grid — rendered straight from the declared
4043
+ // game facts (+ any host extras), so the table can never drift from the config.
4044
+ // `modeStatsItems` localizes the label PARTS itself ("Max win" · the mode name),
4045
+ // so the grid renderer gets identity-tr — no double translation.
4046
+ case "mode-stats": {
4047
+ const extras = (b.extras ?? []).map((e) => ({ label: tr(e.label), value: tr(e.value) }));
4048
+ out.push(statGridHtml([...core.modeStatsItems(facts, tr), ...extras], (s) => s));
4035
4049
  break;
4036
4050
  }
4037
4051
  case "steps": {
@@ -4101,8 +4115,30 @@ function mountInfoMenu(app, ui) {
4101
4115
  const soundMode = menu.sound ?? "sliders";
4102
4116
  const volRow = (id, label, hint, val) => `<div class="ohm-setting"><label class="ohm-row"><span>${esc(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">${esc(tr(hint))}</div></div>`;
4103
4117
  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()) : "";
4104
- const paytableHtml = menu.paytable ? renderBlocks(menu.paytable, tr) : "";
4105
- const rulesHtml = menu.rules ? renderBlocks(menu.rules, tr) : "";
4118
+ const rulesTr = () => {
4119
+ const vars2 = core.factsVars(ui.facts.get(), { "game.name": ui.gameInfo.name ?? "", "game.version": ui.gameInfo.version ?? "" });
4120
+ return (s) => ui.t(s, vars2);
4121
+ };
4122
+ const auditCardHtml = (trr) => {
4123
+ if (!menu.rules?.length) return "";
4124
+ const issues = core.auditRules(ui.facts.get(), menu.rules, { resolve: trr });
4125
+ if (!issues.length) return "";
4126
+ const req = issues.filter((i) => i.level === "required");
4127
+ const rec = issues.filter((i) => i.level === "recommended");
4128
+ const list = (items) => `<ul>${items.map((i) => `<li>${esc(tr(i.message))}</li>`).join("")}</ul>`;
4129
+ return `<div class="ohm-audit" role="alert">
4130
+ <div class="ohm-audit-title">\u26A0 ${esc(tr("openui.rulesAudit.title"))}</div>
4131
+ ${req.length ? `<div class="ohm-audit-sub">${esc(tr("openui.rulesAudit.required"))}</div>${list(req)}` : ""}
4132
+ ${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>` : ""}
4133
+ </div>`;
4134
+ };
4135
+ const rulesInnerHtml = () => {
4136
+ if (!menu.rules) return "";
4137
+ const trr = rulesTr();
4138
+ return auditCardHtml(trr) + renderBlocks(menu.rules, trr, ui.facts.get());
4139
+ };
4140
+ const paytableHtml = menu.paytable ? renderBlocks(menu.paytable, tr, ui.facts.get()) : "";
4141
+ const rulesHtml = rulesInnerHtml();
4106
4142
  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">${esc(ui.gameInfo.name)}</h1>` : "";
4107
4143
  host2.innerHTML = `
4108
4144
  <div class="ohm-backdrop" data-close></div>
@@ -4174,19 +4210,40 @@ function mountInfoMenu(app, ui) {
4174
4210
  else if (el instanceof HTMLSelectElement) el.addEventListener("change", () => ui.bus.emit("optionSelected", { id, value: el.value, index: el.selectedIndex }));
4175
4211
  });
4176
4212
  host2.querySelectorAll("[data-close]").forEach((b) => b.addEventListener("click", () => ui.settingsPanel.closePanel()));
4213
+ const refreshRules = () => {
4214
+ const rulesEl = host2.querySelector("#ohm-rules");
4215
+ if (rulesEl && menu.rules) rulesEl.innerHTML = rulesInnerHtml();
4216
+ };
4177
4217
  disposers.push(
4178
4218
  ui.locale.subscribe(() => {
4179
4219
  const body = host2.querySelector(".ohm-body");
4180
4220
  if (body) {
4181
- const rulesEl = host2.querySelector("#ohm-rules");
4182
- if (rulesEl && menu.rules) rulesEl.innerHTML = renderBlocks(menu.rules, tr);
4221
+ refreshRules();
4183
4222
  if (lang) lang.value = ui.locale.get();
4184
4223
  }
4224
+ }),
4225
+ ui.facts.subscribe(refreshRules)
4226
+ );
4227
+ let menuLocked = false;
4228
+ disposers.push(
4229
+ ui.settingsPanel.state.subscribe(() => {
4230
+ const isOpen = ui.settingsPanel.isOpen;
4231
+ host2.classList.toggle("open", isOpen);
4232
+ if (isOpen && !menuLocked) {
4233
+ ui.lock();
4234
+ menuLocked = true;
4235
+ } else if (!isOpen && menuLocked) {
4236
+ ui.unlock();
4237
+ menuLocked = false;
4238
+ }
4185
4239
  })
4186
4240
  );
4187
- disposers.push(ui.settingsPanel.state.subscribe(() => host2.classList.toggle("open", ui.settingsPanel.isOpen)));
4188
4241
  return () => {
4189
4242
  for (const d of disposers.splice(0)) d();
4243
+ if (menuLocked) {
4244
+ ui.unlock();
4245
+ menuLocked = false;
4246
+ }
4190
4247
  host2.remove();
4191
4248
  };
4192
4249
  }
@@ -4240,6 +4297,13 @@ var OHM_CSS = `
4240
4297
  .ohm-stats { margin: 12px 0; display: grid; grid-template-columns: 1fr 1fr; gap: 0 28px; }
4241
4298
  .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); }
4242
4299
  .ohm-stats dt { color: var(--text-dim); margin: 0; } .ohm-stats dd { margin: 0; font-weight: 700; }
4300
+ .ohm-audit { margin: 12px 0 18px; padding: 14px 16px; border-radius: 12px; border: 2px solid #d03131; background: color-mix(in srgb, #d03131 8%, transparent); }
4301
+ .ohm-audit-title { font-weight: 900; letter-spacing: .5px; color: #b31d1d; }
4302
+ .ohm-audit-sub { margin-top: 8px; font-size: 13px; font-weight: 800; color: #b31d1d; }
4303
+ .ohm-audit-sub--rec { color: #b07d09; }
4304
+ .ohm-audit ul { margin: 6px 0 0; padding-left: 20px; color: var(--text); font-size: 13.5px; line-height: 1.55; }
4305
+ .ohm-audit li { margin: 3px 0; }
4306
+ .ohm-audit-rec ul { color: var(--text-dim); }
4243
4307
  .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); }
4244
4308
  .ohm-callout b { color: var(--accent); } .ohm-callout p { margin: 4px 0 0; color: var(--text); }
4245
4309
  .ohm-callout--warning { border-left-color: #e0a106; background: color-mix(in srgb, #e0a106 10%, transparent); }
@@ -4269,10 +4333,67 @@ var OHM_CSS = `
4269
4333
  .ohm-group { margin: 8px 0; }
4270
4334
  `;
4271
4335
 
4336
+ // src/confirmModal.ts
4337
+ var esc2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4338
+ function showConfirm(ui, opts) {
4339
+ return new Promise((resolve) => {
4340
+ const host2 = document.createElement("div");
4341
+ host2.className = "osc-confirm";
4342
+ host2.style.setProperty("--font", ui.theme.type.family);
4343
+ const title = ui.t(opts.title ?? "Buy Feature");
4344
+ const yes = ui.t(opts.confirmLabel ?? "openui.confirm");
4345
+ const no = ui.t(opts.cancelLabel ?? "openui.cancel");
4346
+ host2.innerHTML = `
4347
+ <div class="osc-confirm-backdrop" data-no></div>
4348
+ <div class="osc-confirm-card" role="dialog" aria-modal="true">
4349
+ ${title ? `<h3 class="osc-confirm-title">${esc2(title)}</h3>` : ""}
4350
+ <p class="osc-confirm-msg">${esc2(opts.message)}</p>
4351
+ <div class="osc-confirm-row">
4352
+ <button class="osc-confirm-btn osc-confirm-no" data-no>${esc2(no)}</button>
4353
+ <button class="osc-confirm-btn osc-confirm-yes" data-yes>${esc2(yes)}</button>
4354
+ </div>
4355
+ </div>`;
4356
+ const style = document.createElement("style");
4357
+ style.textContent = CONFIRM_CSS;
4358
+ host2.appendChild(style);
4359
+ document.body.appendChild(host2);
4360
+ let settled = false;
4361
+ const close = (val) => {
4362
+ if (settled) return;
4363
+ settled = true;
4364
+ window.removeEventListener("keydown", onKey);
4365
+ host2.remove();
4366
+ resolve(val);
4367
+ };
4368
+ const onKey = (e) => {
4369
+ if (e.key === "Escape") close(false);
4370
+ else if (e.key === "Enter") close(true);
4371
+ };
4372
+ host2.querySelectorAll("[data-no]").forEach((b) => b.addEventListener("click", () => close(false)));
4373
+ host2.querySelector("[data-yes]")?.addEventListener("click", () => close(true));
4374
+ window.addEventListener("keydown", onKey);
4375
+ });
4376
+ }
4377
+ var CONFIRM_CSS = `
4378
+ .osc-confirm { position: fixed; inset: 0; z-index: 12000; display: grid; place-items: center; font-family: var(--font); }
4379
+ .osc-confirm *, .osc-confirm *::before, .osc-confirm *::after { box-sizing: border-box; }
4380
+ .osc-confirm-backdrop { position: absolute; inset: 0; background: rgba(8,6,4,.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
4381
+ .osc-confirm-card { position: relative; width: min(90vw, 420px); background: #ffffff; color: #181b20; border: 3px solid #000; border-radius: 14px; padding: 24px; box-shadow: 0 24px 60px rgba(0,0,0,.55); text-align: center; }
4382
+ .osc-confirm-title { margin: 0 0 12px; font-size: 22px; font-weight: 800; letter-spacing: .3px; }
4383
+ .osc-confirm-msg { margin: 0 0 22px; font-size: 19px; font-weight: 700; line-height: 1.4; }
4384
+ .osc-confirm-row { display: flex; gap: 14px; }
4385
+ .osc-confirm-btn { flex: 1; padding: 14px 10px; border-radius: 12px; border: 3px solid #000; font-size: 15px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; cursor: pointer; transition: transform .1s, background .12s; }
4386
+ .osc-confirm-btn:active { transform: scale(.96); }
4387
+ .osc-confirm-no { background: #ffffff; color: #181b20; }
4388
+ .osc-confirm-no:hover { background: #eef1f6; }
4389
+ .osc-confirm-yes { background: #d99000; color: #1a1200; }
4390
+ .osc-confirm-yes:hover { filter: brightness(1.05); }
4391
+ `;
4392
+
4272
4393
  // src/mountHud.ts
4273
4394
  function showReplayModal(ui, info, buttonKey, onSelect) {
4274
4395
  const cur = info.currency ?? ui.balance.currency.get();
4275
- const money2 = (n) => core.formatAmount(n, cur);
4396
+ const money2 = (n) => core.formatAmountPrecise(n, cur);
4276
4397
  ui.showNotice(
4277
4398
  [
4278
4399
  { kind: "heading", id: "openui-replay-h", text: "openui.replay.title" },
@@ -4387,17 +4508,15 @@ function mountHud(app, spec = {}, opts = {}) {
4387
4508
  o.onConfirm();
4388
4509
  return;
4389
4510
  }
4390
- const message = o.message ?? (o.name ? ui.t("openui.buyFeature.confirm", { name: ui.t(o.name), price: o.price ?? "" }) : "openui.buyFeature.message");
4391
- ui.showNotice(
4392
- [
4393
- { kind: "heading", id: "buy-title", text: o.title ?? "openui.buyFeature.title" },
4394
- { kind: "text", id: "buy-body", text: message }
4395
- ],
4396
- [
4397
- { label: o.cancelLabel ?? "openui.cancel", variant: "secondary" },
4398
- { label: o.confirmLabel ?? "openui.confirm", variant: "primary", onSelect: o.onConfirm }
4399
- ]
4400
- );
4511
+ const message = o.message ?? (o.name ? ui.t("openui.buyFeature.confirm", { name: ui.t(o.name), price: o.price ?? "" }) : ui.t("openui.buyFeature.message"));
4512
+ void showConfirm(ui, {
4513
+ title: o.title ?? "openui.buyFeature.title",
4514
+ message,
4515
+ confirmLabel: o.confirmLabel,
4516
+ cancelLabel: o.cancelLabel
4517
+ }).then((ok) => {
4518
+ if (ok) o.onConfirm();
4519
+ });
4401
4520
  },
4402
4521
  showControls: () => pixi.setControlsVisible(true),
4403
4522
  hideControls: () => pixi.setControlsVisible(false),
@@ -4412,13 +4531,14 @@ function mountHud(app, spec = {}, opts = {}) {
4412
4531
  };
4413
4532
  }
4414
4533
  function money(amount, cur) {
4415
- return core.formatAmount(amount, cur);
4534
+ return core.formatAmountPrecise(amount, cur);
4416
4535
  }
4417
- var esc2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4536
+ var esc3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4418
4537
  function mountBuyFeatureModal(_app, hud, features, opts = {}) {
4419
4538
  const ui = hud.ui;
4420
4539
  const tr = (k) => ui.t(k);
4421
4540
  const list = features.slice(0, 4);
4541
+ ui.declareFacts({ modes: list.map((f) => ({ id: f.id, name: f.name, kind: f.variant, cost: f.cost })) });
4422
4542
  const boosts = /* @__PURE__ */ new Set();
4423
4543
  const activation = opts.activation ?? "multi";
4424
4544
  const blocksBuy = opts.activationBlocksBuy ?? false;
@@ -4440,23 +4560,14 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
4440
4560
  <button class="bfm-x" data-close aria-label="Close">\u2715</button>
4441
4561
  <div class="bfm-panel" role="dialog" aria-modal="true">
4442
4562
  <div class="bfm-fit" id="bfm-fit">
4443
- <h2 class="bfm-title" data-t="Buy Feature">${esc2(tr("Buy Feature"))}</h2>
4563
+ <h2 class="bfm-title" data-t="Buy Feature">${esc3(tr("Buy Feature"))}</h2>
4444
4564
  <div class="bfm-bet">
4445
4565
  <button class="bfm-step" id="bfm-minus" aria-label="Decrease">\u2212</button>
4446
- <div class="bfm-betbox"><span class="bfm-betlabel" data-t="Bet">${esc2(tr("Bet"))}</span><b id="bfm-betval">\u2014</b></div>
4566
+ <div class="bfm-betbox"><span class="bfm-betlabel" data-t="Bet">${esc3(tr("Bet"))}</span><b id="bfm-betval">\u2014</b></div>
4447
4567
  <button class="bfm-step" id="bfm-plus" aria-label="Increase">+</button>
4448
4568
  </div>
4449
4569
  <div class="bfm-cards" id="bfm-cards"></div>
4450
4570
  </div>
4451
- </div>
4452
- <div class="bfm-confirm" id="bfm-confirm">
4453
- <div class="bfm-confirm-card">
4454
- <p class="bfm-confirm-msg" id="bfm-confirm-msg"></p>
4455
- <div class="bfm-confirm-row">
4456
- <button class="bfm-confirm-btn bfm-confirm-no" id="bfm-confirm-no" data-t="openui.cancel">${esc2(tr("openui.cancel"))}</button>
4457
- <button class="bfm-confirm-btn bfm-confirm-yes" id="bfm-confirm-yes" data-t="openui.confirm">${esc2(tr("openui.confirm"))}</button>
4458
- </div>
4459
- </div>
4460
4571
  </div>`;
4461
4572
  const style = document.createElement("style");
4462
4573
  style.textContent = BFM_CSS;
@@ -4467,29 +4578,11 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
4467
4578
  const fitEl = $("#bfm-fit");
4468
4579
  const cardsEl = $("#bfm-cards");
4469
4580
  const betValEl = $("#bfm-betval");
4470
- const confirmEl = $("#bfm-confirm");
4471
- const confirmMsg = $("#bfm-confirm-msg");
4472
- const confirmYes = $("#bfm-confirm-yes");
4473
- const confirmNo = $("#bfm-confirm-no");
4474
- let pendingConfirm = null;
4475
- const hideConfirm = () => {
4476
- confirmEl.classList.remove("show");
4477
- pendingConfirm = null;
4478
- };
4479
- confirmNo.addEventListener("click", hideConfirm);
4480
- confirmYes.addEventListener("click", () => {
4481
- const fn = pendingConfirm;
4482
- hideConfirm();
4483
- fn?.();
4484
- });
4485
- const askConfirm = (totalCost, name, price, onYes) => {
4486
- if (!hud.shouldConfirmBuy(totalCost)) {
4487
- onYes();
4488
- return;
4489
- }
4490
- confirmMsg.textContent = ui.t("openui.buyFeature.confirm", { name: ui.t(name), price });
4491
- pendingConfirm = onYes;
4492
- confirmEl.classList.add("show");
4581
+ const askConfirm = (name, price, onYes) => {
4582
+ const message = ui.t("openui.buyFeature.confirm", { name: ui.t(name), price });
4583
+ void showConfirm(ui, { title: "Buy Feature", message }).then((ok) => {
4584
+ if (ok) onYes();
4585
+ });
4493
4586
  };
4494
4587
  const layout = () => {
4495
4588
  const vw = window.innerWidth;
@@ -4521,11 +4614,11 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
4521
4614
  <div class="bfm-cardimg"${img}></div>
4522
4615
  <div class="bfm-strip"></div>
4523
4616
  <div class="bfm-cardbody">
4524
- <span class="bfm-name">${esc2(tr(f.name))}</span>
4525
- <b class="bfm-price">${esc2(price)}</b>
4617
+ <span class="bfm-name">${esc3(tr(f.name))}</span>
4618
+ <b class="bfm-price">${esc3(price)}</b>
4526
4619
  </div>
4527
4620
  </div>
4528
- <button class="${cls}" data-id="${f.id}" data-variant="${f.variant}"${buyBlocked ? " disabled" : ""}>${esc2(label)}</button>
4621
+ <button class="${cls}" data-id="${f.id}" data-variant="${f.variant}"${buyBlocked ? " disabled" : ""}>${esc3(label)}</button>
4529
4622
  </div>`;
4530
4623
  }).join("");
4531
4624
  betValEl.textContent = money(bet, cur);
@@ -4553,11 +4646,11 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
4553
4646
  return;
4554
4647
  }
4555
4648
  const total = 1 + (f?.cost ?? 0);
4556
- askConfirm(total, f?.name ?? id, money(total * bet, cur), commit);
4649
+ askConfirm(f?.name ?? id, money(total * bet, cur), commit);
4557
4650
  } else {
4558
4651
  if (blocksBuy && boosts.size > 0) return;
4559
4652
  const cost = (f?.cost ?? 0) * bet;
4560
- askConfirm(f?.cost ?? 0, f?.name ?? id, money(cost, cur), () => {
4653
+ askConfirm(f?.name ?? id, money(cost, cur), () => {
4561
4654
  ui.bus.emit("cardActivated", { id });
4562
4655
  close();
4563
4656
  opts.onBuy?.(id, cost);
@@ -4568,10 +4661,16 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
4568
4661
  $("#bfm-plus").addEventListener("click", () => ui.betStepper.inc());
4569
4662
  disposers.push(ui.bet.value.subscribe(() => renderCards()));
4570
4663
  const open = () => {
4664
+ if (host2.classList.contains("open")) return;
4571
4665
  renderCards();
4572
4666
  host2.classList.add("open");
4667
+ ui.lock();
4668
+ };
4669
+ const close = () => {
4670
+ if (!host2.classList.contains("open")) return;
4671
+ host2.classList.remove("open");
4672
+ ui.unlock();
4573
4673
  };
4574
- const close = () => host2.classList.remove("open");
4575
4674
  host2.querySelectorAll("[data-close]").forEach((b) => b.addEventListener("click", close));
4576
4675
  disposers.push(ui.on("buttonActivated", ({ id }) => {
4577
4676
  if (id === "bonus") open();
@@ -4589,6 +4688,7 @@ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
4589
4688
  renderCards();
4590
4689
  return () => {
4591
4690
  window.removeEventListener("resize", onResize);
4691
+ if (host2.classList.contains("open")) ui.unlock();
4592
4692
  for (const d of disposers.splice(0)) d();
4593
4693
  host2.remove();
4594
4694
  };
@@ -4626,22 +4726,11 @@ var BFM_CSS = `
4626
4726
  .bfm-action.is-active { background: var(--accent); color: var(--accent-text); border-color: #000; }
4627
4727
  .bfm-action.is-blocked, .bfm-action:disabled { opacity: .38; cursor: not-allowed; box-shadow: none; }
4628
4728
  .bfm-action.is-blocked:hover, .bfm-action:disabled:hover { background: var(--surface); }
4629
- .bfm-confirm { position: absolute; inset: 0; z-index: 5; display: grid; place-items: center; opacity: 0; pointer-events: none; transition: opacity .16s ease; }
4630
- .bfm-confirm.show { opacity: 1; pointer-events: auto; }
4631
- .bfm-confirm::before { content: ""; position: absolute; inset: 0; background: rgba(8,6,4,.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
4632
- .bfm-confirm-card { position: relative; width: min(90%, 420px); background: var(--surface); color: var(--text); border: 3px solid #000; border-radius: 14px; padding: 26px 24px 22px; box-shadow: 0 24px 60px rgba(0,0,0,.55); text-align: center; }
4633
- .bfm-confirm-msg { margin: 0 0 22px; font-size: 19px; font-weight: 700; line-height: 1.4; color: var(--text); }
4634
- .bfm-confirm-row { display: flex; gap: 14px; }
4635
- .bfm-confirm-btn { flex: 1; padding: 14px 10px; border-radius: 12px; border: 3px solid #000; font-size: 15px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; cursor: pointer; transition: transform .1s, background .12s; }
4636
- .bfm-confirm-btn:active { transform: scale(.96); }
4637
- .bfm-confirm-no { background: var(--surface); color: var(--text); }
4638
- .bfm-confirm-no:hover { background: var(--surface-alt); }
4639
- .bfm-confirm-yes { background: var(--accent); color: var(--accent-text); }
4640
4729
  `;
4641
4730
 
4642
4731
  // src/bootError.ts
4643
4732
  var host = null;
4644
- var esc3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4733
+ var esc4 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4645
4734
  function showBootError(opts = {}) {
4646
4735
  const title = opts.title ?? "Connection lost";
4647
4736
  const message = opts.message ?? "The game could not reach the game server. Please reload to reconnect and continue.";
@@ -4655,10 +4744,10 @@ function showBootError(opts = {}) {
4655
4744
  <div class="openui-be-icon" aria-hidden="true">
4656
4745
  <svg viewBox="0 0 48 48" width="48" height="48"><path d="M24 4 3 42h42L24 4Z" fill="none" stroke="#ffc935" stroke-width="3" stroke-linejoin="round"/><rect x="22" y="18" width="4" height="12" rx="2" fill="#ffc935"/><circle cx="24" cy="35" r="2.4" fill="#ffc935"/></svg>
4657
4746
  </div>
4658
- <h1 class="openui-be-title">${esc3(title)}</h1>
4659
- <p class="openui-be-msg">${esc3(message)}</p>
4660
- ${opts.detail ? `<p class="openui-be-detail">${esc3(opts.detail)}</p>` : ""}
4661
- ${showReload ? `<button class="openui-be-reload" type="button">${esc3(opts.reloadLabel ?? "Reload")}</button>` : ""}
4747
+ <h1 class="openui-be-title">${esc4(title)}</h1>
4748
+ <p class="openui-be-msg">${esc4(message)}</p>
4749
+ ${opts.detail ? `<p class="openui-be-detail">${esc4(opts.detail)}</p>` : ""}
4750
+ ${showReload ? `<button class="openui-be-reload" type="button">${esc4(opts.reloadLabel ?? "Reload")}</button>` : ""}
4662
4751
  </div>
4663
4752
  <style>
4664
4753
  .openui-boot-error { position: fixed; inset: 0; z-index: 2147483000; display: grid; place-items: center;
@@ -4742,6 +4831,7 @@ exports.mountBuyFeatureModal = mountBuyFeatureModal;
4742
4831
  exports.mountHud = mountHud;
4743
4832
  exports.mountInfoMenu = mountInfoMenu;
4744
4833
  exports.showBootError = showBootError;
4834
+ exports.showConfirm = showConfirm;
4745
4835
  exports.svgSpinSkin = svgSpinSkin;
4746
4836
  //# sourceMappingURL=index.cjs.map
4747
4837
  //# sourceMappingURL=index.cjs.map