@open-slot-ui/pixi 0.8.1 → 0.8.3

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.d.cts CHANGED
@@ -735,6 +735,58 @@ declare function mountHud(app: Application, spec?: UISpec, opts?: HudOptions): B
735
735
  */
736
736
  declare function mountInfoMenu(app: Application, ui: OpenUI): () => void;
737
737
 
738
+ /** A buy-feature card. */
739
+ interface FeatureSpec {
740
+ id: string;
741
+ name: string;
742
+ variant: 'buy' | 'boost';
743
+ /** Price as a multiple of the current bet (× bet). */
744
+ cost: number;
745
+ /** Card image URL (or data URI). Optional — a neutral gradient is used when absent. */
746
+ image?: string;
747
+ }
748
+ /** Configurable behavior of the activatable bet-boost cards. */
749
+ interface BuyFeatureOptions {
750
+ /** `'single'` → only one boost active at a time · `'multi'` → combine them. Default `'multi'`. */
751
+ activation?: 'single' | 'multi';
752
+ /** When any boost is active, disable the Buy buttons. Default `false`. */
753
+ activationBlocksBuy?: boolean;
754
+ /** Host hook for a confirmed Buy (the modal has just closed): deduct + start the feature. */
755
+ onBuy?: (id: string, cost: number) => void;
756
+ /** Host hook whenever the active-boost set changes (the modal stays open). */
757
+ onActivate?: (activeIds: string[], id: string, active: boolean) => void;
758
+ /** BASE bet source for pricing. Default reads ui.bet — pass this when ui.bet
759
+ * displays a modified EFFECTIVE stake (boost active) instead of the base bet. */
760
+ getBet?: () => number;
761
+ }
762
+ /** Mount the buy-feature modal. Returns a leak-free teardown. */
763
+ declare function mountBuyFeatureModal(_app: Application, hud: BootedHud, features: FeatureSpec[], opts?: BuyFeatureOptions): () => void;
764
+
765
+ /** Minimal HUD surface the confirm needs — the translator + theme font. */
766
+ interface ConfirmUI {
767
+ t(key: string, params?: Record<string, string | number>): string;
768
+ theme: {
769
+ type: {
770
+ family: string;
771
+ };
772
+ };
773
+ }
774
+ interface ConfirmOptions {
775
+ /** Heading (already resolved text, or an i18n key). Default `Buy Feature`. */
776
+ title?: string;
777
+ /** Body message (already resolved). */
778
+ message: string;
779
+ /** Confirm button label (resolved text or i18n key). Default `openui.confirm`. */
780
+ confirmLabel?: string;
781
+ /** Cancel button label. Default `openui.cancel`. */
782
+ cancelLabel?: string;
783
+ }
784
+ /**
785
+ * Show the universal confirm dialog. Resolves `true` on Confirm, `false` on Cancel / backdrop
786
+ * / Escape. Self-mounting + self-cleaning; layers above everything (z-index above the buy modal).
787
+ */
788
+ declare function showConfirm(ui: ConfirmUI, opts: ConfirmOptions): Promise<boolean>;
789
+
738
790
  interface BootErrorOptions {
739
791
  title?: string;
740
792
  message?: string;
@@ -817,4 +869,4 @@ declare class Tweener {
817
869
  /** Treat touch devices as non-desktop so we don't fire phantom hover states. */
818
870
  declare function isDesktop(): boolean;
819
871
 
820
- export { AutoplayDrawerView, AutoplayView, type AutoplayViewOptions, type BootErrorOptions, type BootedHud, type ButtonGlyph, ButtonView, type ButtonViewOptions, ControlView, ControlViewFactory, DialogView, type DialogViewOptions, type HudOptions, type InfoContentBuilder, MenuView, type MenuViewOptions, OpenUIPixi, OpenUIPixiOptions, type PanelBodyOptions, PanelBodyView, PanelView, type PanelViewOptions, type PopoverArt, type PopoverParts, PopoverView, ReadoutView, type ReadoutViewOptions, type ReplayInfo, SelectView, type SelectViewOptions, SliderView, SpinSkin, SpinSkinFactory, SpinView, type SpinViewOptions, StepperView, type SvgSpinTextures, TextCellRenderer, type TextCellRendererOptions, ToggleView, type ToggleViewOptions, TurboView, type TurboViewOptions, Tweener, ValueDisplayView, defaultSpinSkin, drawSpin, hideBootError, isDesktop, mountHud, mountInfoMenu, showBootError, svgSpinSkin };
872
+ export { AutoplayDrawerView, AutoplayView, type AutoplayViewOptions, type BootErrorOptions, type BootedHud, type ButtonGlyph, ButtonView, type ButtonViewOptions, type BuyFeatureOptions, type ConfirmOptions, ControlView, ControlViewFactory, DialogView, type DialogViewOptions, type FeatureSpec, type HudOptions, type InfoContentBuilder, MenuView, type MenuViewOptions, OpenUIPixi, OpenUIPixiOptions, type PanelBodyOptions, PanelBodyView, PanelView, type PanelViewOptions, type PopoverArt, type PopoverParts, PopoverView, ReadoutView, type ReadoutViewOptions, type ReplayInfo, SelectView, type SelectViewOptions, SliderView, SpinSkin, SpinSkinFactory, SpinView, type SpinViewOptions, StepperView, type SvgSpinTextures, TextCellRenderer, type TextCellRendererOptions, ToggleView, type ToggleViewOptions, TurboView, type TurboViewOptions, Tweener, ValueDisplayView, defaultSpinSkin, drawSpin, hideBootError, isDesktop, mountBuyFeatureModal, mountHud, mountInfoMenu, showBootError, showConfirm, svgSpinSkin };
package/dist/index.d.ts CHANGED
@@ -735,6 +735,58 @@ declare function mountHud(app: Application, spec?: UISpec, opts?: HudOptions): B
735
735
  */
736
736
  declare function mountInfoMenu(app: Application, ui: OpenUI): () => void;
737
737
 
738
+ /** A buy-feature card. */
739
+ interface FeatureSpec {
740
+ id: string;
741
+ name: string;
742
+ variant: 'buy' | 'boost';
743
+ /** Price as a multiple of the current bet (× bet). */
744
+ cost: number;
745
+ /** Card image URL (or data URI). Optional — a neutral gradient is used when absent. */
746
+ image?: string;
747
+ }
748
+ /** Configurable behavior of the activatable bet-boost cards. */
749
+ interface BuyFeatureOptions {
750
+ /** `'single'` → only one boost active at a time · `'multi'` → combine them. Default `'multi'`. */
751
+ activation?: 'single' | 'multi';
752
+ /** When any boost is active, disable the Buy buttons. Default `false`. */
753
+ activationBlocksBuy?: boolean;
754
+ /** Host hook for a confirmed Buy (the modal has just closed): deduct + start the feature. */
755
+ onBuy?: (id: string, cost: number) => void;
756
+ /** Host hook whenever the active-boost set changes (the modal stays open). */
757
+ onActivate?: (activeIds: string[], id: string, active: boolean) => void;
758
+ /** BASE bet source for pricing. Default reads ui.bet — pass this when ui.bet
759
+ * displays a modified EFFECTIVE stake (boost active) instead of the base bet. */
760
+ getBet?: () => number;
761
+ }
762
+ /** Mount the buy-feature modal. Returns a leak-free teardown. */
763
+ declare function mountBuyFeatureModal(_app: Application, hud: BootedHud, features: FeatureSpec[], opts?: BuyFeatureOptions): () => void;
764
+
765
+ /** Minimal HUD surface the confirm needs — the translator + theme font. */
766
+ interface ConfirmUI {
767
+ t(key: string, params?: Record<string, string | number>): string;
768
+ theme: {
769
+ type: {
770
+ family: string;
771
+ };
772
+ };
773
+ }
774
+ interface ConfirmOptions {
775
+ /** Heading (already resolved text, or an i18n key). Default `Buy Feature`. */
776
+ title?: string;
777
+ /** Body message (already resolved). */
778
+ message: string;
779
+ /** Confirm button label (resolved text or i18n key). Default `openui.confirm`. */
780
+ confirmLabel?: string;
781
+ /** Cancel button label. Default `openui.cancel`. */
782
+ cancelLabel?: string;
783
+ }
784
+ /**
785
+ * Show the universal confirm dialog. Resolves `true` on Confirm, `false` on Cancel / backdrop
786
+ * / Escape. Self-mounting + self-cleaning; layers above everything (z-index above the buy modal).
787
+ */
788
+ declare function showConfirm(ui: ConfirmUI, opts: ConfirmOptions): Promise<boolean>;
789
+
738
790
  interface BootErrorOptions {
739
791
  title?: string;
740
792
  message?: string;
@@ -817,4 +869,4 @@ declare class Tweener {
817
869
  /** Treat touch devices as non-desktop so we don't fire phantom hover states. */
818
870
  declare function isDesktop(): boolean;
819
871
 
820
- export { AutoplayDrawerView, AutoplayView, type AutoplayViewOptions, type BootErrorOptions, type BootedHud, type ButtonGlyph, ButtonView, type ButtonViewOptions, ControlView, ControlViewFactory, DialogView, type DialogViewOptions, type HudOptions, type InfoContentBuilder, MenuView, type MenuViewOptions, OpenUIPixi, OpenUIPixiOptions, type PanelBodyOptions, PanelBodyView, PanelView, type PanelViewOptions, type PopoverArt, type PopoverParts, PopoverView, ReadoutView, type ReadoutViewOptions, type ReplayInfo, SelectView, type SelectViewOptions, SliderView, SpinSkin, SpinSkinFactory, SpinView, type SpinViewOptions, StepperView, type SvgSpinTextures, TextCellRenderer, type TextCellRendererOptions, ToggleView, type ToggleViewOptions, TurboView, type TurboViewOptions, Tweener, ValueDisplayView, defaultSpinSkin, drawSpin, hideBootError, isDesktop, mountHud, mountInfoMenu, showBootError, svgSpinSkin };
872
+ export { AutoplayDrawerView, AutoplayView, type AutoplayViewOptions, type BootErrorOptions, type BootedHud, type ButtonGlyph, ButtonView, type ButtonViewOptions, type BuyFeatureOptions, type ConfirmOptions, ControlView, ControlViewFactory, DialogView, type DialogViewOptions, type FeatureSpec, type HudOptions, type InfoContentBuilder, MenuView, type MenuViewOptions, OpenUIPixi, OpenUIPixiOptions, type PanelBodyOptions, PanelBodyView, PanelView, type PanelViewOptions, type PopoverArt, type PopoverParts, PopoverView, ReadoutView, type ReadoutViewOptions, type ReplayInfo, SelectView, type SelectViewOptions, SliderView, SpinSkin, SpinSkinFactory, SpinView, type SpinViewOptions, StepperView, type SvgSpinTextures, TextCellRenderer, type TextCellRendererOptions, ToggleView, type ToggleViewOptions, TurboView, type TurboViewOptions, Tweener, ValueDisplayView, defaultSpinSkin, drawSpin, hideBootError, isDesktop, mountBuyFeatureModal, mountHud, mountInfoMenu, showBootError, showConfirm, svgSpinSkin };
package/dist/index.js CHANGED
@@ -562,10 +562,67 @@ var OHM_CSS = `
562
562
  .ohm-group { margin: 8px 0; }
563
563
  `;
564
564
 
565
+ // src/confirmModal.ts
566
+ var esc2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
567
+ function showConfirm(ui, opts) {
568
+ return new Promise((resolve) => {
569
+ const host2 = document.createElement("div");
570
+ host2.className = "osc-confirm";
571
+ host2.style.setProperty("--font", ui.theme.type.family);
572
+ const title = ui.t(opts.title ?? "Buy Feature");
573
+ const yes = ui.t(opts.confirmLabel ?? "openui.confirm");
574
+ const no = ui.t(opts.cancelLabel ?? "openui.cancel");
575
+ host2.innerHTML = `
576
+ <div class="osc-confirm-backdrop" data-no></div>
577
+ <div class="osc-confirm-card" role="dialog" aria-modal="true">
578
+ ${title ? `<h3 class="osc-confirm-title">${esc2(title)}</h3>` : ""}
579
+ <p class="osc-confirm-msg">${esc2(opts.message)}</p>
580
+ <div class="osc-confirm-row">
581
+ <button class="osc-confirm-btn osc-confirm-no" data-no>${esc2(no)}</button>
582
+ <button class="osc-confirm-btn osc-confirm-yes" data-yes>${esc2(yes)}</button>
583
+ </div>
584
+ </div>`;
585
+ const style = document.createElement("style");
586
+ style.textContent = CONFIRM_CSS;
587
+ host2.appendChild(style);
588
+ document.body.appendChild(host2);
589
+ let settled = false;
590
+ const close = (val) => {
591
+ if (settled) return;
592
+ settled = true;
593
+ window.removeEventListener("keydown", onKey);
594
+ host2.remove();
595
+ resolve(val);
596
+ };
597
+ const onKey = (e) => {
598
+ if (e.key === "Escape") close(false);
599
+ else if (e.key === "Enter") close(true);
600
+ };
601
+ host2.querySelectorAll("[data-no]").forEach((b) => b.addEventListener("click", () => close(false)));
602
+ host2.querySelector("[data-yes]")?.addEventListener("click", () => close(true));
603
+ window.addEventListener("keydown", onKey);
604
+ });
605
+ }
606
+ var CONFIRM_CSS = `
607
+ .osc-confirm { position: fixed; inset: 0; z-index: 12000; display: grid; place-items: center; font-family: var(--font); }
608
+ .osc-confirm *, .osc-confirm *::before, .osc-confirm *::after { box-sizing: border-box; }
609
+ .osc-confirm-backdrop { position: absolute; inset: 0; background: rgba(8,6,4,.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
610
+ .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; }
611
+ .osc-confirm-title { margin: 0 0 12px; font-size: 22px; font-weight: 800; letter-spacing: .3px; }
612
+ .osc-confirm-msg { margin: 0 0 22px; font-size: 19px; font-weight: 700; line-height: 1.4; }
613
+ .osc-confirm-row { display: flex; gap: 14px; }
614
+ .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; }
615
+ .osc-confirm-btn:active { transform: scale(.96); }
616
+ .osc-confirm-no { background: #ffffff; color: #181b20; }
617
+ .osc-confirm-no:hover { background: #eef1f6; }
618
+ .osc-confirm-yes { background: #d99000; color: #1a1200; }
619
+ .osc-confirm-yes:hover { filter: brightness(1.05); }
620
+ `;
621
+
565
622
  // src/mountHud.ts
566
623
  function showReplayModal(ui, info, buttonKey, onSelect) {
567
624
  const cur = info.currency ?? ui.balance.currency.get();
568
- const money = (n) => formatAmount(n, cur);
625
+ const money2 = (n) => formatAmount(n, cur);
569
626
  ui.showNotice(
570
627
  [
571
628
  { kind: "heading", id: "openui-replay-h", text: "openui.replay.title" },
@@ -573,10 +630,10 @@ function showReplayModal(ui, info, buttonKey, onSelect) {
573
630
  kind: "stat-grid",
574
631
  id: "openui-replay-g",
575
632
  items: [
576
- { label: "openui.replay.baseBet", value: money(info.baseBet) },
633
+ { label: "openui.replay.baseBet", value: money2(info.baseBet) },
577
634
  { label: "openui.replay.costMultiplier", value: `${info.costMultiplier}\xD7` },
578
635
  { label: "openui.replay.payoutMultiplier", value: `${info.payoutMultiplier}\xD7` },
579
- { label: "openui.replay.amount", value: money(info.amount) }
636
+ { label: "openui.replay.amount", value: money2(info.amount) }
580
637
  ]
581
638
  }
582
639
  ],
@@ -680,17 +737,15 @@ function mountHud(app, spec = {}, opts = {}) {
680
737
  o.onConfirm();
681
738
  return;
682
739
  }
683
- const message = o.message ?? (o.name ? ui.t("openui.buyFeature.confirm", { name: ui.t(o.name), price: o.price ?? "" }) : "openui.buyFeature.message");
684
- ui.showNotice(
685
- [
686
- { kind: "heading", id: "buy-title", text: o.title ?? "openui.buyFeature.title" },
687
- { kind: "text", id: "buy-body", text: message }
688
- ],
689
- [
690
- { label: o.cancelLabel ?? "openui.cancel", variant: "secondary" },
691
- { label: o.confirmLabel ?? "openui.confirm", variant: "primary", onSelect: o.onConfirm }
692
- ]
693
- );
740
+ const message = o.message ?? (o.name ? ui.t("openui.buyFeature.confirm", { name: ui.t(o.name), price: o.price ?? "" }) : ui.t("openui.buyFeature.message"));
741
+ void showConfirm(ui, {
742
+ title: o.title ?? "openui.buyFeature.title",
743
+ message,
744
+ confirmLabel: o.confirmLabel,
745
+ cancelLabel: o.cancelLabel
746
+ }).then((ok) => {
747
+ if (ok) o.onConfirm();
748
+ });
694
749
  },
695
750
  showControls: () => pixi.setControlsVisible(true),
696
751
  hideControls: () => pixi.setControlsVisible(false),
@@ -704,10 +759,199 @@ function mountHud(app, spec = {}, opts = {}) {
704
759
  dispose: teardown
705
760
  };
706
761
  }
762
+ function money(amount, cur) {
763
+ return formatAmount(amount, cur);
764
+ }
765
+ var esc3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
766
+ function mountBuyFeatureModal(_app, hud, features, opts = {}) {
767
+ const ui = hud.ui;
768
+ const tr = (k) => ui.t(k);
769
+ const list = features.slice(0, 4);
770
+ const boosts = /* @__PURE__ */ new Set();
771
+ const activation = opts.activation ?? "multi";
772
+ const blocksBuy = opts.activationBlocksBuy ?? false;
773
+ const disposers = [];
774
+ const host2 = document.createElement("div");
775
+ host2.className = "bfm-root";
776
+ const vars = {
777
+ "--accent": "#d99000",
778
+ "--accent-text": "#1a1200",
779
+ "--surface": "#ffffff",
780
+ "--surface-alt": "#eef1f6",
781
+ "--text": "#181b20",
782
+ "--text-dim": "#5b6472",
783
+ "--font": ui.theme.type.family
784
+ };
785
+ for (const [k, v] of Object.entries(vars)) host2.style.setProperty(k, v);
786
+ host2.innerHTML = `
787
+ <div class="bfm-backdrop" data-close></div>
788
+ <button class="bfm-x" data-close aria-label="Close">\u2715</button>
789
+ <div class="bfm-panel" role="dialog" aria-modal="true">
790
+ <div class="bfm-fit" id="bfm-fit">
791
+ <h2 class="bfm-title" data-t="Buy Feature">${esc3(tr("Buy Feature"))}</h2>
792
+ <div class="bfm-bet">
793
+ <button class="bfm-step" id="bfm-minus" aria-label="Decrease">\u2212</button>
794
+ <div class="bfm-betbox"><span class="bfm-betlabel" data-t="Bet">${esc3(tr("Bet"))}</span><b id="bfm-betval">\u2014</b></div>
795
+ <button class="bfm-step" id="bfm-plus" aria-label="Increase">+</button>
796
+ </div>
797
+ <div class="bfm-cards" id="bfm-cards"></div>
798
+ </div>
799
+ </div>`;
800
+ const style = document.createElement("style");
801
+ style.textContent = BFM_CSS;
802
+ host2.appendChild(style);
803
+ document.body.appendChild(host2);
804
+ const $ = (sel) => host2.querySelector(sel);
805
+ const panel = $(".bfm-panel");
806
+ const fitEl = $("#bfm-fit");
807
+ const cardsEl = $("#bfm-cards");
808
+ const betValEl = $("#bfm-betval");
809
+ const askConfirm = (name, price, onYes) => {
810
+ const message = ui.t("openui.buyFeature.confirm", { name: ui.t(name), price });
811
+ void showConfirm(ui, { title: "Buy Feature", message }).then((ok) => {
812
+ if (ok) onYes();
813
+ });
814
+ };
815
+ const layout = () => {
816
+ const vw = window.innerWidth;
817
+ const vh = window.innerHeight;
818
+ const cols = vh <= 540 || vw >= 900 ? Math.min(4, list.length) : Math.min(2, list.length);
819
+ cardsEl.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
820
+ const fitW = Math.min(vw * 0.96, cols >= 4 ? 1180 : cols === 1 ? 380 : 760);
821
+ fitEl.style.width = `${fitW}px`;
822
+ fitEl.style.transform = "none";
823
+ const natH = fitEl.offsetHeight;
824
+ const s = Math.min(1, vh * 0.95 / natH);
825
+ fitEl.style.transform = `translateX(-50%) scale(${s})`;
826
+ panel.style.width = `${Math.ceil(fitW * s)}px`;
827
+ panel.style.height = `${Math.ceil(natH * s)}px`;
828
+ };
829
+ const renderCards = () => {
830
+ const bet = opts.getBet ? opts.getBet() : ui.bet.get();
831
+ const cur = ui.bet.currency.get();
832
+ cardsEl.innerHTML = list.map((f) => {
833
+ const active = boosts.has(f.id);
834
+ const buyBlocked = f.variant === "buy" && blocksBuy && boosts.size > 0;
835
+ const price = f.variant === "buy" ? money(f.cost * bet, cur) : `+${money(f.cost * bet, cur)}`;
836
+ const label = f.variant === "buy" ? tr("Buy") : active ? tr("Activated") : tr("Activate");
837
+ const cls = `bfm-action bfm-action--${f.variant}${active ? " is-active" : ""}${buyBlocked ? " is-blocked" : ""}`;
838
+ const img = f.image ? ` style="background-image:url('${f.image}')"` : "";
839
+ return `
840
+ <div class="bfm-cell">
841
+ <div class="bfm-card">
842
+ <div class="bfm-cardimg"${img}></div>
843
+ <div class="bfm-strip"></div>
844
+ <div class="bfm-cardbody">
845
+ <span class="bfm-name">${esc3(tr(f.name))}</span>
846
+ <b class="bfm-price">${esc3(price)}</b>
847
+ </div>
848
+ </div>
849
+ <button class="${cls}" data-id="${f.id}" data-variant="${f.variant}"${buyBlocked ? " disabled" : ""}>${esc3(label)}</button>
850
+ </div>`;
851
+ }).join("");
852
+ betValEl.textContent = money(bet, cur);
853
+ cardsEl.querySelectorAll(".bfm-action").forEach((b) => {
854
+ b.addEventListener("click", () => onAction(b.dataset.id, b.dataset.variant));
855
+ });
856
+ layout();
857
+ };
858
+ const onAction = (id, variant) => {
859
+ const f = list.find((x) => x.id === id);
860
+ const bet = opts.getBet ? opts.getBet() : ui.bet.get();
861
+ const cur = ui.bet.currency.get();
862
+ if (variant === "boost") {
863
+ const wasActive = boosts.has(id);
864
+ const commit = () => {
865
+ if (activation === "single") boosts.clear();
866
+ if (wasActive) boosts.delete(id);
867
+ else boosts.add(id);
868
+ ui.bus.emit("cardActivated", { id });
869
+ renderCards();
870
+ opts.onActivate?.([...boosts], id, boosts.has(id));
871
+ };
872
+ if (wasActive) {
873
+ commit();
874
+ return;
875
+ }
876
+ const total = 1 + (f?.cost ?? 0);
877
+ askConfirm(f?.name ?? id, money(total * bet, cur), commit);
878
+ } else {
879
+ if (blocksBuy && boosts.size > 0) return;
880
+ const cost = (f?.cost ?? 0) * bet;
881
+ askConfirm(f?.name ?? id, money(cost, cur), () => {
882
+ ui.bus.emit("cardActivated", { id });
883
+ close();
884
+ opts.onBuy?.(id, cost);
885
+ });
886
+ }
887
+ };
888
+ $("#bfm-minus").addEventListener("click", () => ui.betStepper.dec());
889
+ $("#bfm-plus").addEventListener("click", () => ui.betStepper.inc());
890
+ disposers.push(ui.bet.value.subscribe(() => renderCards()));
891
+ const open = () => {
892
+ renderCards();
893
+ host2.classList.add("open");
894
+ };
895
+ const close = () => host2.classList.remove("open");
896
+ host2.querySelectorAll("[data-close]").forEach((b) => b.addEventListener("click", close));
897
+ disposers.push(ui.on("buttonActivated", ({ id }) => {
898
+ if (id === "bonus") open();
899
+ }));
900
+ const onResize = () => {
901
+ if (host2.classList.contains("open")) layout();
902
+ };
903
+ window.addEventListener("resize", onResize);
904
+ disposers.push(
905
+ ui.locale.subscribe(() => {
906
+ host2.querySelectorAll("[data-t]").forEach((n) => n.textContent = tr(n.dataset.t));
907
+ renderCards();
908
+ })
909
+ );
910
+ renderCards();
911
+ return () => {
912
+ window.removeEventListener("resize", onResize);
913
+ for (const d of disposers.splice(0)) d();
914
+ host2.remove();
915
+ };
916
+ }
917
+ var BFM_CSS = `
918
+ .bfm-root { position: fixed; inset: 0; z-index: 11000; display: grid; place-items: center; font-family: var(--font); opacity: 0; pointer-events: none; transition: opacity .18s ease; }
919
+ .bfm-root.open { opacity: 1; pointer-events: auto; }
920
+ .bfm-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; }
921
+ .bfm-root.open .bfm-backdrop { background: rgba(8,6,4,.5); backdrop-filter: blur(10px) saturate(1.1); -webkit-backdrop-filter: blur(10px) saturate(1.1); }
922
+ .bfm-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; }
923
+ .bfm-x:hover { transform: scale(1.08); background: rgba(18,14,10,.95); }
924
+ .bfm-root *, .bfm-root *::before, .bfm-root *::after { box-sizing: border-box; }
925
+ .bfm-panel { position: relative; transform: translateY(8px) scale(.985); transition: transform .18s ease; }
926
+ .bfm-root.open .bfm-panel { transform: none; }
927
+ .bfm-fit { position: absolute; top: 0; left: 50%; transform: translateX(-50%); transform-origin: top center; }
928
+ .bfm-title { margin: 0 0 14px; text-align: center; color: #fff; font-size: 30px; font-weight: 800; letter-spacing: 1px; text-shadow: 0 2px 12px rgba(0,0,0,.6); }
929
+ .bfm-bet { display: flex; align-items: center; justify-content: center; gap: 16px; margin: 0 0 24px; }
930
+ .bfm-betbox { min-width: 200px; padding: 10px 22px; border-radius: 12px; background: var(--surface); border: 3px solid #000; display: flex; flex-direction: column; align-items: center; line-height: 1.1; }
931
+ .bfm-betlabel { font-size: 12px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px; }
932
+ .bfm-betbox b { font-size: 24px; color: var(--text); }
933
+ .bfm-step { flex: none; width: 54px; height: 54px; border-radius: 999px; border: 3px solid #000; background: var(--surface); color: var(--text); font-size: 28px; font-weight: 800; cursor: pointer; display: grid; place-items: center; line-height: 1; transition: transform .1s, background .12s; box-shadow: 0 4px 12px rgba(0,0,0,.3); }
934
+ .bfm-step:hover { background: var(--surface-alt); }
935
+ .bfm-step:active { transform: scale(.92); }
936
+ .bfm-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; align-items: start; }
937
+ .bfm-cell { display: flex; flex-direction: column; min-width: 0; }
938
+ .bfm-card { background: var(--surface); border: 4px solid #000; border-radius: 14px; overflow: hidden; box-shadow: 0 14px 34px rgba(0,0,0,.45); }
939
+ .bfm-cardimg { width: 100%; aspect-ratio: 16 / 10; background-size: cover; background-position: center; background-color: var(--surface-alt); background-image: linear-gradient(135deg, #e7ebf2, #cfd6e2); }
940
+ .bfm-strip { height: 8px; background: linear-gradient(90deg, #f0a500, #ffd166, #f0a500); }
941
+ .bfm-cardbody { padding: 12px 14px 16px; text-align: center; }
942
+ .bfm-name { display: block; font-size: 15px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
943
+ .bfm-price { display: block; margin-top: 2px; font-size: 22px; font-weight: 800; color: var(--text); }
944
+ .bfm-action { display: block; width: 100%; margin-top: 12px; padding: 14px 10px; border-radius: 12px; border: 4px solid #000; background: var(--surface); color: var(--text); font-size: 15px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; cursor: pointer; transition: transform .1s, background .12s, color .12s; box-shadow: 0 5px 14px rgba(0,0,0,.35); white-space: nowrap; }
945
+ .bfm-action:hover { background: var(--surface-alt); }
946
+ .bfm-action:active { transform: scale(.96); }
947
+ .bfm-action.is-active { background: var(--accent); color: var(--accent-text); border-color: #000; }
948
+ .bfm-action.is-blocked, .bfm-action:disabled { opacity: .38; cursor: not-allowed; box-shadow: none; }
949
+ .bfm-action.is-blocked:hover, .bfm-action:disabled:hover { background: var(--surface); }
950
+ `;
707
951
 
708
952
  // src/bootError.ts
709
953
  var host = null;
710
- var esc2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
954
+ var esc4 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
711
955
  function showBootError(opts = {}) {
712
956
  const title = opts.title ?? "Connection lost";
713
957
  const message = opts.message ?? "The game could not reach the game server. Please reload to reconnect and continue.";
@@ -721,10 +965,10 @@ function showBootError(opts = {}) {
721
965
  <div class="openui-be-icon" aria-hidden="true">
722
966
  <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>
723
967
  </div>
724
- <h1 class="openui-be-title">${esc2(title)}</h1>
725
- <p class="openui-be-msg">${esc2(message)}</p>
726
- ${opts.detail ? `<p class="openui-be-detail">${esc2(opts.detail)}</p>` : ""}
727
- ${showReload ? `<button class="openui-be-reload" type="button">${esc2(opts.reloadLabel ?? "Reload")}</button>` : ""}
968
+ <h1 class="openui-be-title">${esc4(title)}</h1>
969
+ <p class="openui-be-msg">${esc4(message)}</p>
970
+ ${opts.detail ? `<p class="openui-be-detail">${esc4(opts.detail)}</p>` : ""}
971
+ ${showReload ? `<button class="openui-be-reload" type="button">${esc4(opts.reloadLabel ?? "Reload")}</button>` : ""}
728
972
  </div>
729
973
  <style>
730
974
  .openui-boot-error { position: fixed; inset: 0; z-index: 2147483000; display: grid; place-items: center;
@@ -752,6 +996,6 @@ function hideBootError() {
752
996
  host = null;
753
997
  }
754
998
 
755
- export { PanelBodyView, PanelView, PopoverView, hideBootError, mountHud, mountInfoMenu, showBootError };
999
+ export { PanelBodyView, PanelView, PopoverView, hideBootError, mountBuyFeatureModal, mountHud, mountInfoMenu, showBootError, showConfirm };
756
1000
  //# sourceMappingURL=index.js.map
757
1001
  //# sourceMappingURL=index.js.map