@open-slot-ui/core 0.12.0 → 0.13.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
@@ -133,10 +133,10 @@ function anchorFactors(anchor) {
133
133
  function resolvePlacement(spec, screen) {
134
134
  const [ax, ay] = anchorFactors(spec.anchor);
135
135
  const [ox, oy] = spec.offset ?? [0, 0];
136
- const st = screen.stage;
136
+ const rect = spec.origin === "screen" ? { x: 0, y: 0, width: screen.width, height: screen.height } : screen.stage;
137
137
  return {
138
- x: st.x + ax * st.width + ox * screen.scale,
139
- y: st.y + ay * st.height + oy * screen.scale,
138
+ x: rect.x + ax * rect.width + ox * screen.scale,
139
+ y: rect.y + ay * rect.height + oy * screen.scale,
140
140
  scale: screen.scale * (spec.scale ?? 1),
141
141
  rotation: (spec.rotation ?? 0) * Math.PI / 180
142
142
  };
@@ -160,11 +160,13 @@ var landscapeDefaultLayouts = Object.freeze({
160
160
  // Right-anchored so a long localized caption grows LEFT and never clips the edge.
161
161
  "total-win": { anchor: "bottom-right", offset: [-150, -300] },
162
162
  settings: { anchor: "bottom-center", offset: [-740, -320], rotation: -5 },
163
- mute: { anchor: "top-right", offset: [-122, 46] },
164
- fullscreen: { anchor: "top-right", offset: [-46, 46] },
165
- rtp: { anchor: "top-left", offset: [20, 18] },
166
- "session-timer": { anchor: "top-left", offset: [20, 46] },
167
- "net-position": { anchor: "top-left", offset: [20, 74] }
163
+ // Corner utilities anchor to the SCREEN (not the letterboxed frame) so they hug the true
164
+ // viewport corners at every aspect see `LayoutSpec.origin`.
165
+ mute: { anchor: "top-right", offset: [-122, 46], origin: "screen" },
166
+ fullscreen: { anchor: "top-right", offset: [-46, 46], origin: "screen" },
167
+ rtp: { anchor: "top-left", offset: [20, 18], origin: "screen" },
168
+ "session-timer": { anchor: "top-left", offset: [20, 46], origin: "screen" },
169
+ "net-position": { anchor: "top-left", offset: [20, 74], origin: "screen" }
168
170
  });
169
171
  var portraitDefaultLayouts = Object.freeze({
170
172
  // Mobile sizes run ~1.5× the desktop base (the Figma mobile frame draws the
@@ -179,19 +181,21 @@ var portraitDefaultLayouts = Object.freeze({
179
181
  settings: { anchor: "bottom-center", offset: [438, -480], scale: 1.5, rotation: -5 },
180
182
  "bet-minus": { anchor: "bottom-center", offset: [-105, -267], scale: 1.5 },
181
183
  "bet-plus": { anchor: "bottom-center", offset: [105, -267], scale: 1.5 },
182
- mute: { anchor: "top-right", offset: [-159, 57], scale: 1.5 },
183
- fullscreen: { anchor: "top-right", offset: [-57, 57], scale: 1.5 },
184
+ // Corner utilities anchor to the SCREEN (see `LayoutSpec.origin`) so they stay in the true
185
+ // viewport corner even when the portrait frame is letterboxed.
186
+ mute: { anchor: "top-right", offset: [-159, 57], scale: 1.5, origin: "screen" },
187
+ fullscreen: { anchor: "top-right", offset: [-57, 57], scale: 1.5, origin: "screen" },
184
188
  // Compliance readouts scale up ~2× on mobile with wider line spacing.
185
- rtp: { anchor: "top-left", offset: [16, 16], scale: 2 },
186
- "session-timer": { anchor: "top-left", offset: [16, 66], scale: 2 },
187
- "net-position": { anchor: "top-left", offset: [16, 116], scale: 2 },
189
+ rtp: { anchor: "top-left", offset: [16, 16], scale: 2, origin: "screen" },
190
+ "session-timer": { anchor: "top-left", offset: [16, 66], scale: 2, origin: "screen" },
191
+ "net-position": { anchor: "top-left", offset: [16, 116], scale: 2, origin: "screen" },
188
192
  balance: { anchor: "bottom-left", offset: [36, -110], rotation: -5 },
189
193
  bet: { anchor: "bottom-right", offset: [-36, -110], rotation: 5 }
190
194
  });
191
195
  function defaultLayout(id) {
192
196
  const l = landscapeDefaultLayouts[id];
193
197
  if (!l) return { anchor: "center" };
194
- return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation };
198
+ return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation, origin: l.origin };
195
199
  }
196
200
 
197
201
  // src/transition/Transition.ts
@@ -1275,6 +1279,7 @@ var BLOCK_KINDS = [
1275
1279
  "steps",
1276
1280
  "table",
1277
1281
  "paytable",
1282
+ "paylines",
1278
1283
  "image",
1279
1284
  "media",
1280
1285
  "cards",
@@ -1436,6 +1441,10 @@ function validateSpec(spec) {
1436
1441
  if (b.kind === "paytable" && (!b.rows || b.rows.length === 0)) {
1437
1442
  add("warn", `${p}.rows`, "empty-paytable", "a paytable block has no rows");
1438
1443
  }
1444
+ if (b.kind === "paylines") {
1445
+ if (!(b.reels > 0) || !(b.rows > 0)) add("error", `${p}.reels`, "paylines-shape", "a paylines block needs reels > 0 and rows > 0");
1446
+ if (!b.lines || b.lines.length === 0) add("warn", `${p}.lines`, "empty-paylines", "a paylines block has no lines");
1447
+ }
1439
1448
  if (b.kind === "image" && (!b.src || !b.src.trim())) {
1440
1449
  add("error", `${p}.src`, "image-src", "an image block needs a src");
1441
1450
  }
@@ -2459,7 +2468,7 @@ var OpenUI = class {
2459
2468
 
2460
2469
  // src/spec/responsive.ts
2461
2470
  function cloneLayout(l) {
2462
- return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation };
2471
+ return { anchor: l.anchor, offset: l.offset ? [l.offset[0], l.offset[1]] : void 0, scale: l.scale, rotation: l.rotation, origin: l.origin };
2463
2472
  }
2464
2473
  function activeKeys(screen) {
2465
2474
  return [screen.orientation, screen.breakpoint];
@@ -2917,6 +2926,15 @@ function renderBlocksHtml(blocks, tr, facts) {
2917
2926
  case "paytable":
2918
2927
  out.push(`<div class="ohm-grid">${renderPaytable(b.rows, tr)}</div>`);
2919
2928
  break;
2929
+ case "paylines": {
2930
+ const masks = b.lines.map((line, i) => {
2931
+ const cells = [];
2932
+ for (let row = 0; row < b.rows; row++) for (let reel = 0; reel < b.reels; reel++) cells.push(`<i class="${line[reel] === row ? "on" : ""}"></i>`);
2933
+ return `<div class="ohm-line"><div class="ohm-linegrid" style="grid-template-columns:repeat(${b.reels},1fr)">${cells.join("")}</div><span>${i + 1}</span></div>`;
2934
+ }).join("");
2935
+ out.push(`<div class="ohm-lines">${masks}</div>`);
2936
+ break;
2937
+ }
2920
2938
  case "table": {
2921
2939
  const head = b.columns?.length ? `<thead><tr>${b.columns.map((c) => `<th>${esc(tr(c))}</th>`).join("")}</tr></thead>` : "";
2922
2940
  const body = b.rows.map((r) => `<tr>${r.map((c) => `<td>${esc(tr(c))}</td>`).join("")}</tr>`).join("");