@open-slot-ui/core 0.2.3 → 0.3.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.d.cts CHANGED
@@ -239,6 +239,30 @@ interface Placement {
239
239
  /** Resolve a control's layout against the current screen. Pure math. */
240
240
  declare function resolvePlacement(spec: LayoutSpec, screen: ScreenState): Placement;
241
241
 
242
+ /**
243
+ * The reference HUD arrangement, cloned 1:1 from the Figma design frames.
244
+ *
245
+ * Landscape ("Desk DEF", 1920×1080 = the landscape reference, so px map
246
+ * directly): a bottom bar — balance · play · SPIN · turbo · bet (with the ±
247
+ * steppers stacked to its right) — plus the ☰ menu on the lower-left rail and
248
+ * the buy (bonus) button on the lower-right rail. Everything lives in the
249
+ * bottom ~320 reference px so the reels area above stays clear.
250
+ *
251
+ * Portrait ("Mobile DEF", 360-wide × 3 = the 1080×2337 portrait reference):
252
+ * a thumb-friendly stack — buy · play · SPIN · turbo · ☰ on one row, the bet ±
253
+ * steppers below SPIN, balance/bet tilted into the corners, readouts larger.
254
+ *
255
+ * These are the LIBRARY defaults: `OpenUI` constructs controls with the
256
+ * landscape values, and `createUI` installs the portrait set as a built-in
257
+ * `responsive.portrait` bucket (layered UNDER host overrides — a host's static
258
+ * `controls.{id}.layout` wins everywhere, and a host's own
259
+ * `responsive.portrait` entry wins per-control).
260
+ */
261
+ declare const landscapeDefaultLayouts: Readonly<Record<string, LayoutSpec>>;
262
+ declare const portraitDefaultLayouts: Readonly<Record<string, LayoutSpec>>;
263
+ /** A control's default layout for the landscape reference frame (cloned — safe to mutate). */
264
+ declare function defaultLayout(id: string): LayoutSpec;
265
+
242
266
  /**
243
267
  * Transitions are composable OOP descriptors (Charter B6) — pure data, renderer-agnostic.
244
268
  * The renderer interprets the `kind`; the core never animates anything itself.
@@ -1805,4 +1829,4 @@ declare function buildBetLadder(cfg: RgsBetConfig, divisor?: number): {
1805
1829
  /** Clamp a stake (major units) to the RGS min/max and snap to `stepBet`. Pure. */
1806
1830
  declare function clampBet(amount: number, cfg: RgsBetConfig, divisor?: number): number;
1807
1831
 
1808
- export { API_AMOUNT_DIVISOR, type Anchor, AutoplayControl, type AutoplayLimits, type AutoplayMode, type AutoplayOptions, BLOCK_KINDS, type BlockFactory, type BlockSpec, type Breakpoint, type BuiltPanel, ButtonControl, type ButtonOptions, CURRENCY_TABLE, CardControl, type CardOptions, type ComposeMenuOptions, Control, type ControlOptions, type ControlOverride, ControlRegistry, type ControlSnapshot, type CurrencyInfo, type CurrencySpec, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, type DeepPartial, DictionaryTranslator, type Dispose, EventBus, EventLog, type EventLogOptions, type EventMap, Fade, type HostHooks, type JurisdictionConfig, type KnownControlId, LOCALE_LABELS, type LayoutConfig, type LayoutSpec, type LoggedEvent, type MenuSpec, type NoticeAction, type NoticeOptions, OpenUI, type OpenUIEvents, type OpenUIOptions, type Orientation, PanelControl, type PanelOptions, type PanelSpec, type PanelVariant, Parallel, type Placement, Pulse, RGS_ERROR_KEYS, ReadoutControl, type ReadoutKind, type ReadoutOptions, type RealityCheckOptions, type Rect, type ResponsiveKey, type ResponsiveOverride, type RgsBetConfig, type RgsErrorCode, type RgsErrorOptions, type ScreenState, SelectControl, type SelectControlOptions, type SelectOption, Sequence, Signal, SliderControl, type SliderOptions, type SpecIssue, SpinControl, type SpinPress, type SpinSpec, Squish, type StateDef, type StateMap, StepperControl, type StepperOptions, type Subscribable, type Theme, type ThemeChoice, type ThemeIssue, type ThemeOverrides, type ThemePreset, ToggleControl, type ToggleOptions, type Transition, type Translator, TurboControl, type TurboOptions, type TurboSpec, Turn, type UISpec, ValueDisplay, type ValueDisplayOptions, type ViewInspect, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, openuiDefaults, openuiSocialDefaults, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
1832
+ export { API_AMOUNT_DIVISOR, type Anchor, AutoplayControl, type AutoplayLimits, type AutoplayMode, type AutoplayOptions, BLOCK_KINDS, type BlockFactory, type BlockSpec, type Breakpoint, type BuiltPanel, ButtonControl, type ButtonOptions, CURRENCY_TABLE, CardControl, type CardOptions, type ComposeMenuOptions, Control, type ControlOptions, type ControlOverride, ControlRegistry, type ControlSnapshot, type CurrencyInfo, type CurrencySpec, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, type DeepPartial, DictionaryTranslator, type Dispose, EventBus, EventLog, type EventLogOptions, type EventMap, Fade, type HostHooks, type JurisdictionConfig, type KnownControlId, LOCALE_LABELS, type LayoutConfig, type LayoutSpec, type LoggedEvent, type MenuSpec, type NoticeAction, type NoticeOptions, OpenUI, type OpenUIEvents, type OpenUIOptions, type Orientation, PanelControl, type PanelOptions, type PanelSpec, type PanelVariant, Parallel, type Placement, Pulse, RGS_ERROR_KEYS, ReadoutControl, type ReadoutKind, type ReadoutOptions, type RealityCheckOptions, type Rect, type ResponsiveKey, type ResponsiveOverride, type RgsBetConfig, type RgsErrorCode, type RgsErrorOptions, type ScreenState, SelectControl, type SelectControlOptions, type SelectOption, Sequence, Signal, SliderControl, type SliderOptions, type SpecIssue, SpinControl, type SpinPress, type SpinSpec, Squish, type StateDef, type StateMap, StepperControl, type StepperOptions, type Subscribable, type Theme, type ThemeChoice, type ThemeIssue, type ThemeOverrides, type ThemePreset, ToggleControl, type ToggleOptions, type Transition, type Translator, TurboControl, type TurboOptions, type TurboSpec, Turn, type UISpec, ValueDisplay, type ValueDisplayOptions, type ViewInspect, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
package/dist/index.d.ts CHANGED
@@ -239,6 +239,30 @@ interface Placement {
239
239
  /** Resolve a control's layout against the current screen. Pure math. */
240
240
  declare function resolvePlacement(spec: LayoutSpec, screen: ScreenState): Placement;
241
241
 
242
+ /**
243
+ * The reference HUD arrangement, cloned 1:1 from the Figma design frames.
244
+ *
245
+ * Landscape ("Desk DEF", 1920×1080 = the landscape reference, so px map
246
+ * directly): a bottom bar — balance · play · SPIN · turbo · bet (with the ±
247
+ * steppers stacked to its right) — plus the ☰ menu on the lower-left rail and
248
+ * the buy (bonus) button on the lower-right rail. Everything lives in the
249
+ * bottom ~320 reference px so the reels area above stays clear.
250
+ *
251
+ * Portrait ("Mobile DEF", 360-wide × 3 = the 1080×2337 portrait reference):
252
+ * a thumb-friendly stack — buy · play · SPIN · turbo · ☰ on one row, the bet ±
253
+ * steppers below SPIN, balance/bet tilted into the corners, readouts larger.
254
+ *
255
+ * These are the LIBRARY defaults: `OpenUI` constructs controls with the
256
+ * landscape values, and `createUI` installs the portrait set as a built-in
257
+ * `responsive.portrait` bucket (layered UNDER host overrides — a host's static
258
+ * `controls.{id}.layout` wins everywhere, and a host's own
259
+ * `responsive.portrait` entry wins per-control).
260
+ */
261
+ declare const landscapeDefaultLayouts: Readonly<Record<string, LayoutSpec>>;
262
+ declare const portraitDefaultLayouts: Readonly<Record<string, LayoutSpec>>;
263
+ /** A control's default layout for the landscape reference frame (cloned — safe to mutate). */
264
+ declare function defaultLayout(id: string): LayoutSpec;
265
+
242
266
  /**
243
267
  * Transitions are composable OOP descriptors (Charter B6) — pure data, renderer-agnostic.
244
268
  * The renderer interprets the `kind`; the core never animates anything itself.
@@ -1805,4 +1829,4 @@ declare function buildBetLadder(cfg: RgsBetConfig, divisor?: number): {
1805
1829
  /** Clamp a stake (major units) to the RGS min/max and snap to `stepBet`. Pure. */
1806
1830
  declare function clampBet(amount: number, cfg: RgsBetConfig, divisor?: number): number;
1807
1831
 
1808
- export { API_AMOUNT_DIVISOR, type Anchor, AutoplayControl, type AutoplayLimits, type AutoplayMode, type AutoplayOptions, BLOCK_KINDS, type BlockFactory, type BlockSpec, type Breakpoint, type BuiltPanel, ButtonControl, type ButtonOptions, CURRENCY_TABLE, CardControl, type CardOptions, type ComposeMenuOptions, Control, type ControlOptions, type ControlOverride, ControlRegistry, type ControlSnapshot, type CurrencyInfo, type CurrencySpec, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, type DeepPartial, DictionaryTranslator, type Dispose, EventBus, EventLog, type EventLogOptions, type EventMap, Fade, type HostHooks, type JurisdictionConfig, type KnownControlId, LOCALE_LABELS, type LayoutConfig, type LayoutSpec, type LoggedEvent, type MenuSpec, type NoticeAction, type NoticeOptions, OpenUI, type OpenUIEvents, type OpenUIOptions, type Orientation, PanelControl, type PanelOptions, type PanelSpec, type PanelVariant, Parallel, type Placement, Pulse, RGS_ERROR_KEYS, ReadoutControl, type ReadoutKind, type ReadoutOptions, type RealityCheckOptions, type Rect, type ResponsiveKey, type ResponsiveOverride, type RgsBetConfig, type RgsErrorCode, type RgsErrorOptions, type ScreenState, SelectControl, type SelectControlOptions, type SelectOption, Sequence, Signal, SliderControl, type SliderOptions, type SpecIssue, SpinControl, type SpinPress, type SpinSpec, Squish, type StateDef, type StateMap, StepperControl, type StepperOptions, type Subscribable, type Theme, type ThemeChoice, type ThemeIssue, type ThemeOverrides, type ThemePreset, ToggleControl, type ToggleOptions, type Transition, type Translator, TurboControl, type TurboOptions, type TurboSpec, Turn, type UISpec, ValueDisplay, type ValueDisplayOptions, type ViewInspect, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, openuiDefaults, openuiSocialDefaults, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
1832
+ export { API_AMOUNT_DIVISOR, type Anchor, AutoplayControl, type AutoplayLimits, type AutoplayMode, type AutoplayOptions, BLOCK_KINDS, type BlockFactory, type BlockSpec, type Breakpoint, type BuiltPanel, ButtonControl, type ButtonOptions, CURRENCY_TABLE, CardControl, type CardOptions, type ComposeMenuOptions, Control, type ControlOptions, type ControlOverride, ControlRegistry, type ControlSnapshot, type CurrencyInfo, type CurrencySpec, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, type DeepPartial, DictionaryTranslator, type Dispose, EventBus, EventLog, type EventLogOptions, type EventMap, Fade, type HostHooks, type JurisdictionConfig, type KnownControlId, LOCALE_LABELS, type LayoutConfig, type LayoutSpec, type LoggedEvent, type MenuSpec, type NoticeAction, type NoticeOptions, OpenUI, type OpenUIEvents, type OpenUIOptions, type Orientation, PanelControl, type PanelOptions, type PanelSpec, type PanelVariant, Parallel, type Placement, Pulse, RGS_ERROR_KEYS, ReadoutControl, type ReadoutKind, type ReadoutOptions, type RealityCheckOptions, type Rect, type ResponsiveKey, type ResponsiveOverride, type RgsBetConfig, type RgsErrorCode, type RgsErrorOptions, type ScreenState, SelectControl, type SelectControlOptions, type SelectOption, Sequence, Signal, SliderControl, type SliderOptions, type SpecIssue, SpinControl, type SpinPress, type SpinSpec, Squish, type StateDef, type StateMap, StepperControl, type StepperOptions, type Subscribable, type Theme, type ThemeChoice, type ThemeIssue, type ThemeOverrides, type ThemePreset, ToggleControl, type ToggleOptions, type Transition, type Translator, TurboControl, type TurboOptions, type TurboSpec, Turn, type UISpec, ValueDisplay, type ValueDisplayOptions, type ViewInspect, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
package/dist/index.js CHANGED
@@ -133,6 +133,48 @@ function resolvePlacement(spec, screen) {
133
133
  };
134
134
  }
135
135
 
136
+ // src/layout/defaultLayouts.ts
137
+ var landscapeDefaultLayouts = Object.freeze({
138
+ spin: { anchor: "bottom-center", offset: [0, -140], scale: 0.9 },
139
+ autoplay: { anchor: "bottom-center", offset: [-204, -112] },
140
+ turbo: { anchor: "bottom-center", offset: [204, -112] },
141
+ balance: { anchor: "bottom-left", offset: [135, -104], rotation: -5 },
142
+ bet: { anchor: "bottom-right", offset: [-186, -104], rotation: 5 },
143
+ "bet-plus": { anchor: "bottom-right", offset: [-120, -188], rotation: 5 },
144
+ "bet-minus": { anchor: "bottom-right", offset: [-120, -104], rotation: 5 },
145
+ bonus: { anchor: "bottom-center", offset: [740, -320] },
146
+ settings: { anchor: "bottom-center", offset: [-740, -320], rotation: -5 },
147
+ mute: { anchor: "top-right", offset: [-122, 46] },
148
+ fullscreen: { anchor: "top-right", offset: [-46, 46] },
149
+ rtp: { anchor: "top-left", offset: [20, 18] },
150
+ "session-timer": { anchor: "top-left", offset: [20, 46] },
151
+ "net-position": { anchor: "top-left", offset: [20, 74] }
152
+ });
153
+ var portraitDefaultLayouts = Object.freeze({
154
+ // Mobile sizes run ~1.5× the desktop base (the Figma mobile frame draws the
155
+ // controls larger relative to its width) so the buttons read well on a phone.
156
+ spin: { anchor: "bottom-center", offset: [0, -510], scale: 1.35 },
157
+ autoplay: { anchor: "bottom-center", offset: [-258, -510], scale: 1.5 },
158
+ turbo: { anchor: "bottom-center", offset: [258, -510], scale: 1.5 },
159
+ bonus: { anchor: "bottom-center", offset: [-438, -480], scale: 1.3 },
160
+ settings: { anchor: "bottom-center", offset: [438, -480], scale: 1.5, rotation: -5 },
161
+ "bet-minus": { anchor: "bottom-center", offset: [-105, -267], scale: 1.5 },
162
+ "bet-plus": { anchor: "bottom-center", offset: [105, -267], scale: 1.5 },
163
+ mute: { anchor: "top-right", offset: [-159, 57], scale: 1.5 },
164
+ fullscreen: { anchor: "top-right", offset: [-57, 57], scale: 1.5 },
165
+ // Compliance readouts scale up ~2× on mobile with wider line spacing.
166
+ rtp: { anchor: "top-left", offset: [16, 16], scale: 2 },
167
+ "session-timer": { anchor: "top-left", offset: [16, 66], scale: 2 },
168
+ "net-position": { anchor: "top-left", offset: [16, 116], scale: 2 },
169
+ balance: { anchor: "bottom-left", offset: [36, -110], rotation: -5 },
170
+ bet: { anchor: "bottom-right", offset: [-36, -110], rotation: 5 }
171
+ });
172
+ function defaultLayout(id) {
173
+ const l = landscapeDefaultLayouts[id];
174
+ if (!l) return { anchor: "center" };
175
+ return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation };
176
+ }
177
+
136
178
  // src/transition/Transition.ts
137
179
  var Squish = class {
138
180
  constructor(scale = 0.92, ms = 120) {
@@ -1583,24 +1625,24 @@ var OpenUI = class {
1583
1625
  this.bus.emit("localeChanged", loc);
1584
1626
  })
1585
1627
  );
1586
- this.spin = new SpinControl({ layout: { anchor: "bottom-center", offset: [0, -440] } }, this.bus);
1628
+ this.spin = new SpinControl({ layout: defaultLayout("spin") }, this.bus);
1587
1629
  this.balance = new ValueDisplay({
1588
1630
  id: "balance",
1589
1631
  label: "openui.balance",
1590
1632
  // i18n key → localizes + swaps in social mode
1591
- layout: { anchor: "bottom-left", offset: [220, -96] },
1633
+ layout: defaultLayout("balance"),
1592
1634
  currency: { code: "USD", decimals: 2 },
1593
1635
  initial: 1e3
1594
1636
  });
1595
1637
  this.bet = new ValueDisplay({
1596
1638
  id: "bet",
1597
1639
  label: "openui.bet",
1598
- layout: { anchor: "bottom-right", offset: [-220, -96] },
1640
+ layout: defaultLayout("bet"),
1599
1641
  currency: { code: "USD", decimals: 2 },
1600
1642
  initial: 1
1601
1643
  });
1602
1644
  this.settingsButton = new ButtonControl(
1603
- { id: "settings", layout: { anchor: "bottom-center", offset: [400, -440] } },
1645
+ { id: "settings", layout: defaultLayout("settings") },
1604
1646
  this.bus
1605
1647
  );
1606
1648
  this.settingsPanel = new PanelControl(
@@ -1609,20 +1651,20 @@ var OpenUI = class {
1609
1651
  );
1610
1652
  this.musicSlider = new SliderControl({ id: "music", label: "Music", layout: { anchor: "center" }, initial: 0.7 }, this.bus);
1611
1653
  this.sfxSlider = new SliderControl({ id: "sfx", label: "Sound", layout: { anchor: "center" }, initial: 0.5 }, this.bus);
1612
- this.bonusButton = new ButtonControl({ id: "bonus", layout: { anchor: "bottom-center", offset: [-400, -440] } }, this.bus);
1654
+ this.bonusButton = new ButtonControl({ id: "bonus", layout: defaultLayout("bonus") }, this.bus);
1613
1655
  this.autoplay = new AutoplayControl(
1614
- { id: "autoplay", layout: { anchor: "bottom-center", offset: [-225, -440] }, options: [5, 10, 25, 50, 100, Infinity] },
1656
+ { id: "autoplay", layout: defaultLayout("autoplay"), options: [5, 10, 25, 50, 100, Infinity] },
1615
1657
  this.bus
1616
1658
  );
1617
- this.turbo = new TurboControl({ id: "turbo", layout: { anchor: "bottom-center", offset: [225, -440] } }, this.bus);
1618
- this.betMinus = new ButtonControl({ id: "bet-minus", layout: { anchor: "bottom-center", offset: [-150, -270] } }, this.bus);
1619
- this.betPlus = new ButtonControl({ id: "bet-plus", layout: { anchor: "bottom-center", offset: [150, -270] } }, this.bus);
1659
+ this.turbo = new TurboControl({ id: "turbo", layout: defaultLayout("turbo") }, this.bus);
1660
+ this.betMinus = new ButtonControl({ id: "bet-minus", layout: defaultLayout("bet-minus") }, this.bus);
1661
+ this.betPlus = new ButtonControl({ id: "bet-plus", layout: defaultLayout("bet-plus") }, this.bus);
1620
1662
  this.betStepper = new StepperControl({ id: "bet-stepper", layout: { anchor: "center" }, levels: [0.5, 1, 2, 5, 10, 20], index: 1 }, this.bus);
1621
- this.muteButton = new ButtonControl({ id: "mute", layout: { anchor: "top-right", offset: [-122, 46] } }, this.bus);
1622
- this.fullscreenButton = new ButtonControl({ id: "fullscreen", layout: { anchor: "top-right", offset: [-46, 46] } }, this.bus);
1623
- this.rtp = new ReadoutControl({ id: "rtp", kind: "percent", label: "RTP", layout: { anchor: "top-left", offset: [18, 18] } });
1624
- this.sessionTimer = new ReadoutControl({ id: "session-timer", kind: "duration", label: "Session Time", layout: { anchor: "top-left", offset: [18, 36] } });
1625
- this.netPosition = new ReadoutControl({ id: "net-position", kind: "currency", label: "Net", currency: { code: "USD", symbol: "$", display: "symbol", position: "prefix", decimals: 2 }, layout: { anchor: "top-left", offset: [18, 60] } });
1663
+ this.muteButton = new ButtonControl({ id: "mute", layout: defaultLayout("mute") }, this.bus);
1664
+ this.fullscreenButton = new ButtonControl({ id: "fullscreen", layout: defaultLayout("fullscreen") }, this.bus);
1665
+ this.rtp = new ReadoutControl({ id: "rtp", kind: "percent", label: "RTP", layout: defaultLayout("rtp") });
1666
+ this.sessionTimer = new ReadoutControl({ id: "session-timer", kind: "duration", label: "Session Time", layout: defaultLayout("session-timer") });
1667
+ this.netPosition = new ReadoutControl({ id: "net-position", kind: "currency", label: "Net", currency: { code: "USD", symbol: "$", display: "symbol", position: "prefix", decimals: 2 }, layout: defaultLayout("net-position") });
1626
1668
  this.noticePanel = new PanelControl({ id: "notice-panel", variant: "modal", layout: { anchor: "center" } }, this.bus);
1627
1669
  for (const c of [
1628
1670
  this.spin,
@@ -2062,9 +2104,16 @@ function createUI(spec = {}, hooks = {}) {
2062
2104
  }
2063
2105
  }
2064
2106
  if (spec.jurisdiction) ui.applyJurisdiction(spec.jurisdiction);
2065
- if (spec.responsive) {
2066
- installResponsive(ui, spec.responsive);
2067
- }
2107
+ const portraitControls = {};
2108
+ for (const [id, layout] of Object.entries(portraitDefaultLayouts)) {
2109
+ if (spec.controls?.[id]?.layout) continue;
2110
+ portraitControls[id] = { layout };
2111
+ }
2112
+ Object.assign(portraitControls, spec.responsive?.portrait?.controls);
2113
+ installResponsive(ui, {
2114
+ ...spec.responsive,
2115
+ portrait: { ...spec.responsive?.portrait, controls: portraitControls }
2116
+ });
2068
2117
  if (spec.lockDuringSpin !== false) {
2069
2118
  let wasSpinning = false;
2070
2119
  ui.spin.state.subscribe((s) => {
@@ -2242,6 +2291,6 @@ function clampBet(amount, cfg, divisor = API_AMOUNT_DIVISOR) {
2242
2291
  return Math.min(max, Math.max(min, v));
2243
2292
  }
2244
2293
 
2245
- export { API_AMOUNT_DIVISOR, AutoplayControl, BLOCK_KINDS, ButtonControl, CURRENCY_TABLE, CardControl, Control, ControlRegistry, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, DictionaryTranslator, EventBus, EventLog, Fade, LOCALE_LABELS, OpenUI, PanelControl, Parallel, Pulse, RGS_ERROR_KEYS, ReadoutControl, SelectControl, Sequence, Signal, SliderControl, SpinControl, Squish, StepperControl, ToggleControl, TurboControl, Turn, ValueDisplay, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, openuiDefaults, openuiSocialDefaults, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
2294
+ export { API_AMOUNT_DIVISOR, AutoplayControl, BLOCK_KINDS, ButtonControl, CURRENCY_TABLE, CardControl, Control, ControlRegistry, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, DictionaryTranslator, EventBus, EventLog, Fade, LOCALE_LABELS, OpenUI, PanelControl, Parallel, Pulse, RGS_ERROR_KEYS, ReadoutControl, SelectControl, Sequence, Signal, SliderControl, SpinControl, Squish, StepperControl, ToggleControl, TurboControl, Turn, ValueDisplay, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
2246
2295
  //# sourceMappingURL=index.js.map
2247
2296
  //# sourceMappingURL=index.js.map