@open-slot-ui/pixi 0.0.1 → 0.1.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.js CHANGED
@@ -181,6 +181,16 @@ var SpinView = class extends ControlView {
181
181
  this.fsFace.addChild(ring, this.fsCount, this.fsLabel);
182
182
  this.fsFace.visible = false;
183
183
  this.art.addChild(this.fsFace);
184
+ const stopRing = new Graphics().circle(0, 0, 96).fill({ color: 16777215 }).stroke({ width: 7, color: 657930 });
185
+ this.stopLabel = new Text({ text: ui.t("openui.stop") === "openui.stop" ? "STOP" : ui.t("openui.stop"), style: { fontFamily: ui.theme.type.family, fontSize: 26, fill: 657930, fontWeight: "900", letterSpacing: 3 } });
186
+ this.stopLabel.anchor.set(0.5);
187
+ this.stopLabel.y = -38;
188
+ this.stopCount = new Text({ text: "0", style: { fontFamily: ui.theme.type.family, fontSize: 64, fill: 657930, fontWeight: "900" } });
189
+ this.stopCount.anchor.set(0.5);
190
+ this.stopCount.y = 16;
191
+ this.stopFace.addChild(stopRing, this.stopLabel, this.stopCount);
192
+ this.stopFace.visible = false;
193
+ this.art.addChild(this.stopFace);
184
194
  this.addChild(this.art);
185
195
  this.hitArea = new Circle(0, 0, 118);
186
196
  this.eventMode = "static";
@@ -199,13 +209,19 @@ var SpinView = class extends ControlView {
199
209
  this.spin.allowSlamStop.subscribe(() => this.updateInteractive()),
200
210
  this.spin.onTransition((t) => this.play(t)),
201
211
  // free-spins face: switch between the normal skin and the "N FS" counter
202
- this.spin.freeSpins.subscribe(() => this.updateFreeSpins()),
212
+ this.spin.freeSpins.subscribe(() => this.updateFaces()),
213
+ // autoplay: while active the spin button becomes the STOP-count button
214
+ this.ui.autoplay.state.subscribe(() => {
215
+ this.updateFaces();
216
+ this.updateInteractive();
217
+ }),
218
+ this.ui.autoplay.count.subscribe(() => this.updateFaces()),
203
219
  this.ui.locale.subscribe(() => {
204
220
  if (!this.destroyed) this.fsLabel.text = this.ui.t("openui.freeSpins");
205
221
  })
206
222
  );
207
223
  this.skin.update(this.spin.current);
208
- this.updateFreeSpins();
224
+ this.updateFaces();
209
225
  this.updateInteractive();
210
226
  }
211
227
  spin;
@@ -219,13 +235,25 @@ var SpinView = class extends ControlView {
219
235
  fsFace = new Container();
220
236
  fsCount;
221
237
  fsLabel;
222
- /** Swap the spin face: free-spins counter when `freeSpins > 0`, else the skin. */
223
- updateFreeSpins() {
238
+ /** Autoplay STOP face a "STOP" label over the remaining count (or ∞); shown while
239
+ * autoplay is active. Tapping the spin button then STOPS autoplay. */
240
+ stopFace = new Container();
241
+ stopCount;
242
+ stopLabel;
243
+ /** Pick the spin face: autoplay STOP-count (top priority) → free-spins counter →
244
+ * the normal skin. */
245
+ updateFaces() {
246
+ const auto = this.ui.autoplay.isActive;
224
247
  const n = this.spin.freeSpins.get();
225
- const fs = n > 0;
226
- this.fsCount.text = String(n);
248
+ const fs = n > 0 && !auto;
249
+ if (auto) {
250
+ const c = this.ui.autoplay.count.get();
251
+ this.stopCount.text = c === Infinity ? "\u221E" : String(c);
252
+ }
253
+ if (fs) this.fsCount.text = String(n);
254
+ this.stopFace.visible = auto;
227
255
  this.fsFace.visible = fs;
228
- this.skin.view.visible = !fs;
256
+ this.skin.view.visible = !auto && !fs;
229
257
  }
230
258
  onOver = () => {
231
259
  if (isDesktop() && this.spin.current === "idle") this.spin.setState("hover");
@@ -253,6 +281,10 @@ var SpinView = class extends ControlView {
253
281
  onUp = () => {
254
282
  if (this.holding) return this.endHold();
255
283
  this.clearHoldTimer();
284
+ if (this.ui.autoplay.isActive) {
285
+ this.ui.autoplay.stop();
286
+ return;
287
+ }
256
288
  if (this.spin.current === "pressed") {
257
289
  this.spin.setState("idle");
258
290
  this.spin.activate();
@@ -281,7 +313,7 @@ var SpinView = class extends ControlView {
281
313
  if (this.spin.current === "pressed") this.spin.setState("idle");
282
314
  }
283
315
  updateInteractive() {
284
- const ok = this.spin.interactable;
316
+ const ok = this.spin.interactable || this.ui.autoplay.isActive;
285
317
  this.eventMode = ok ? "static" : "none";
286
318
  this.cursor = ok ? "pointer" : "default";
287
319
  const inSpin = this.spin.current === "spinning" || this.spin.current === "auto" || this.spin.current === "stop";
@@ -1138,9 +1170,9 @@ var TextCellRenderer = class {
1138
1170
  };
1139
1171
 
1140
1172
  // src/views/ValueDisplayView.ts
1141
- var DIGIT_W = 26;
1142
- var DIGIT_H = 46;
1143
- var FONT_SIZE = 40;
1173
+ var DIGIT_W = 39;
1174
+ var DIGIT_H = 68;
1175
+ var FONT_SIZE = 48;
1144
1176
  var FIT_BUDGET_COLS = 9;
1145
1177
  var ValueDisplayView = class extends ControlView {
1146
1178
  constructor(vd, ui, ticker, fitGsap) {
@@ -1174,6 +1206,7 @@ var ValueDisplayView = class extends ControlView {
1174
1206
  fitGsap;
1175
1207
  counter;
1176
1208
  caption;
1209
+ captionPill;
1177
1210
  side() {
1178
1211
  const a = this.vd.layout.anchor;
1179
1212
  return a.includes("left") ? "left" : a.includes("right") ? "right" : "center";
@@ -1183,13 +1216,20 @@ var ValueDisplayView = class extends ControlView {
1183
1216
  const side = this.side();
1184
1217
  if (this.vd.label) {
1185
1218
  this.caption = new Text({
1186
- text: this.ui.t(this.vd.label).toUpperCase(),
1187
- style: { fontFamily: theme.type.family, fontSize: 13, fill: theme.color.text, fontWeight: "700", letterSpacing: 2 }
1219
+ // Figma: the label is NOT uppercased — "Баланс" / "Ставка" as authored.
1220
+ text: this.ui.t(this.vd.label),
1221
+ style: { fontFamily: theme.type.family, fontSize: 28, fill: 16777215, fontWeight: "900", letterSpacing: 0 }
1188
1222
  });
1189
- this.caption.alpha = 0.55;
1190
- this.caption.anchor.set(side === "left" ? 0 : side === "right" ? 1 : 0.5, 1);
1191
- this.caption.y = -DIGIT_H / 2 - 3;
1192
- this.addChild(this.caption);
1223
+ this.caption.anchor.set(0.5, 0.5);
1224
+ const padX = 24;
1225
+ const padY = 8;
1226
+ const pillW = Math.ceil(this.caption.width) + padX * 2;
1227
+ const pillH = Math.ceil(this.caption.height) + padY * 2;
1228
+ const pillX = side === "left" ? 0 : side === "right" ? -pillW : -pillW / 2;
1229
+ const pillY = -DIGIT_H / 2 - pillH - 14;
1230
+ this.captionPill = new Graphics().roundRect(pillX, pillY, pillW, pillH, 12).fill({ color: 0 });
1231
+ this.caption.position.set(pillX + pillW / 2, pillY + pillH / 2);
1232
+ this.addChild(this.captionPill, this.caption);
1193
1233
  }
1194
1234
  const cur = this.vd.currency.get();
1195
1235
  this.buildCounter(displayDigits(this.vd.get(), this.vd.digits, cur.decimals), this.vd.minorUnits);
@@ -1201,7 +1241,16 @@ var ValueDisplayView = class extends ControlView {
1201
1241
  const cur = this.vd.currency.get();
1202
1242
  const side = this.side();
1203
1243
  const renderer = new TextCellRenderer({
1204
- style: { fontFamily: theme.type.family, fontSize: FONT_SIZE, fill: theme.color.text, fontWeight: "700" },
1244
+ // Figma "default" readout: Montserrat Black, white, with a bold BLACK OUTLINE
1245
+ // plus a soft drop shadow (0px 4px, 25% black) under it.
1246
+ style: {
1247
+ fontFamily: theme.type.family,
1248
+ fontSize: FONT_SIZE,
1249
+ fill: 16777215,
1250
+ fontWeight: "900",
1251
+ stroke: { color: 0, width: 8, join: "round" },
1252
+ dropShadow: { color: 0, alpha: 0.25, blur: 2, distance: 4, angle: Math.PI / 2 }
1253
+ },
1205
1254
  digitWidth: DIGIT_W,
1206
1255
  digitHeight: DIGIT_H
1207
1256
  // no fillerChar → rolling cells show real digits (crisp, no blur, no blank flicker)
@@ -1246,8 +1295,10 @@ var ValueDisplayView = class extends ControlView {
1246
1295
  rebuild() {
1247
1296
  this.counter?.destroy();
1248
1297
  this.caption?.destroy();
1298
+ this.captionPill?.destroy();
1249
1299
  this.counter = void 0;
1250
1300
  this.caption = void 0;
1301
+ this.captionPill = void 0;
1251
1302
  this.removeChildren();
1252
1303
  this.build();
1253
1304
  }
@@ -1256,6 +1307,12 @@ var ValueDisplayView = class extends ControlView {
1256
1307
  super.dispose();
1257
1308
  }
1258
1309
  };
1310
+ var GLYPH_SVG = {
1311
+ speaker: '<svg viewBox="0 0 22.5 22.5" xmlns="http://www.w3.org/2000/svg"><path d="M13.6745 5.01252C13.9316 4.84399 14.2647 4.8292 14.5397 4.97456C14.8146 5.1199 14.9868 5.40142 14.9872 5.706L15.0007 16.8107C15.0011 17.1153 14.8296 17.3934 14.555 17.5333C14.2804 17.6731 13.9472 17.6517 13.6897 17.4781L10.4454 15.2905L8.61605 15.2722C6.76074 15.2537 5.25495 13.7655 5.25266 11.9484L5.25073 10.3739C5.2486 8.55679 6.75081 7.09863 8.6061 7.11714L10.4355 7.13539L13.6745 5.01252Z" fill="none" stroke="black" stroke-width="1.5" stroke-linejoin="round"/><path d="M16.1933 9.375C16.6252 10.4652 16.6015 11.654 16.1257 12.75" fill="none" stroke="black" stroke-width="3" stroke-linecap="round"/></svg>',
1312
+ "speaker-mute": '<svg viewBox="0 0 22.5 22.5" xmlns="http://www.w3.org/2000/svg"><path d="M13.6745 5.01252C13.9316 4.84399 14.2647 4.8292 14.5397 4.97456C14.8146 5.1199 14.9868 5.40142 14.9872 5.706L15.0007 16.8107C15.0011 17.1153 14.8296 17.3934 14.555 17.5333C14.2804 17.6731 13.9472 17.6517 13.6897 17.4781L10.4454 15.2905L8.61605 15.2722C6.76074 15.2537 5.25495 13.7655 5.25266 11.9484L5.25073 10.3739C5.2486 8.55679 6.75081 7.09863 8.6061 7.11714L10.4355 7.13539L13.6745 5.01252Z" fill="none" stroke="black" stroke-width="1.5" stroke-linejoin="round"/><path d="M15.5 8.5L19.5 13.5M19.5 8.5L15.5 13.5" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"/></svg>',
1313
+ fullscreen: '<svg viewBox="0 0 22.5 22.5" xmlns="http://www.w3.org/2000/svg"><path d="M7.875 18H7.5C5.84315 18 4.5 16.6569 4.5 15V14.25M7.875 4.5H7.5C5.84315 4.5 4.5 5.84315 4.5 7.5V8.25M14.25 4.5H15C16.6569 4.5 18 5.84315 18 7.5V8.25M14.25 18H15C16.6569 18 18 16.6569 18 15V14.25" fill="none" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
1314
+ "fullscreen-exit": '<svg viewBox="0 0 22.5 22.5" xmlns="http://www.w3.org/2000/svg"><path d="M4.5 8.25H5.25C6.9 8.25 8.25 6.9 8.25 5.25V4.5M14.25 4.5V5.25C14.25 6.9 15.6 8.25 17.25 8.25H18M18 14.25H17.25C15.6 14.25 14.25 15.6 14.25 17.25V18M8.25 18V17.25C8.25 15.6 6.9 14.25 5.25 14.25H4.5" fill="none" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>'
1315
+ };
1259
1316
  var ButtonView = class extends ControlView {
1260
1317
  constructor(btn, ui, ticker, opts = {}) {
1261
1318
  super(btn, ui);
@@ -1267,6 +1324,7 @@ var ButtonView = class extends ControlView {
1267
1324
  this.glyph = opts.glyph ?? "none";
1268
1325
  this.iconTarget = opts.iconTarget ?? 84;
1269
1326
  this.mono = opts.mono ?? false;
1327
+ this.dark = opts.dark ?? false;
1270
1328
  this.addChild(this.art);
1271
1329
  if (opts.iconTexture) {
1272
1330
  this.sprite = new Sprite(opts.iconTexture);
@@ -1274,7 +1332,7 @@ var ButtonView = class extends ControlView {
1274
1332
  this.fitSprite();
1275
1333
  this.art.addChild(this.sprite);
1276
1334
  } else {
1277
- this.art.addChild(this.bg);
1335
+ this.art.addChild(this.bg, this.glyphG);
1278
1336
  if (btn.label) {
1279
1337
  this.labelText = new Text({
1280
1338
  text: ui.t(btn.label),
@@ -1286,8 +1344,6 @@ var ButtonView = class extends ControlView {
1286
1344
  }
1287
1345
  this.eventMode = "static";
1288
1346
  this.cursor = "pointer";
1289
- this.on("pointerover", this.onOver);
1290
- this.on("pointerout", this.onOut);
1291
1347
  this.on("pointerdown", this.onDown);
1292
1348
  this.on("pointerup", this.onUp);
1293
1349
  this.on("pointerupoutside", this.onUpOutside);
@@ -1297,6 +1353,12 @@ var ButtonView = class extends ControlView {
1297
1353
  this.updateInteractive();
1298
1354
  }),
1299
1355
  this.btn.onTransition((t) => this.play(t)),
1356
+ // The host input lock gates `interactable` without a state change, so react to
1357
+ // it directly: a locked/disabled button dims to a semi-transparent state.
1358
+ this.ui.locked.subscribe(() => {
1359
+ this.redraw();
1360
+ this.updateInteractive();
1361
+ }),
1300
1362
  this.ui.locale.subscribe(() => {
1301
1363
  if (this.destroyed) return;
1302
1364
  if (this.labelText && this.btn.label) {
@@ -1312,6 +1374,7 @@ var ButtonView = class extends ControlView {
1312
1374
  btn;
1313
1375
  art = new Container();
1314
1376
  bg = new Graphics();
1377
+ glyphG = new Graphics();
1315
1378
  sprite;
1316
1379
  labelText;
1317
1380
  tween;
@@ -1321,6 +1384,7 @@ var ButtonView = class extends ControlView {
1321
1384
  glyph;
1322
1385
  iconTarget;
1323
1386
  mono;
1387
+ dark;
1324
1388
  /** Swap the displayed art (e.g. ☰ → ✕). */
1325
1389
  setIconTexture(tex) {
1326
1390
  if (!this.sprite) return;
@@ -1340,12 +1404,6 @@ var ButtonView = class extends ControlView {
1340
1404
  this.sprite.scale.set(1);
1341
1405
  this.sprite.scale.set(this.iconTarget / this.sprite.height);
1342
1406
  }
1343
- onOver = () => {
1344
- if (isDesktop() && this.btn.current === "idle") this.btn.setState("hover");
1345
- };
1346
- onOut = () => {
1347
- if (this.btn.current === "hover") this.btn.setState("idle");
1348
- };
1349
1407
  onDown = () => {
1350
1408
  if (this.btn.interactable) this.btn.setState("pressed");
1351
1409
  };
@@ -1374,6 +1432,7 @@ var ButtonView = class extends ControlView {
1374
1432
  const ok = this.btn.interactable;
1375
1433
  this.eventMode = ok ? "static" : "none";
1376
1434
  this.cursor = ok ? "pointer" : "default";
1435
+ this.alpha = ok ? 1 : 0.4;
1377
1436
  }
1378
1437
  redraw() {
1379
1438
  if (this.sprite) {
@@ -1384,31 +1443,37 @@ var ButtonView = class extends ControlView {
1384
1443
  const g = this.bg;
1385
1444
  g.clear();
1386
1445
  const disabled = this.btn.current === "disabled";
1387
- const fillC = this.mono ? "#ffffff" : t.color.surface;
1446
+ const fillC = this.dark ? "#0a0a0a" : this.mono ? "#ffffff" : t.color.surface;
1388
1447
  const lineC = this.mono ? "#0a0a0a" : t.color.accent;
1389
- const ink = this.mono ? disabled ? "#9aa0a6" : "#0a0a0a" : disabled ? t.color.textDim : t.color.text;
1448
+ const ink = this.dark ? "#ffffff" : this.mono ? disabled ? "#9aa0a6" : "#0a0a0a" : disabled ? t.color.textDim : t.color.text;
1449
+ this.glyphG.clear();
1450
+ this.glyphG.visible = false;
1390
1451
  if (this.shape === "circle") {
1391
1452
  const r = this.radius;
1392
- g.circle(0, 0, r).fill({ color: fillC, alpha: this.mono && disabled ? 0.6 : 1 });
1393
- g.circle(0, 0, r).stroke({ width: this.mono ? 5 : 4, color: lineC });
1394
- if (this.glyph === "menu") {
1395
- for (const dy of [-8, 0, 8]) g.moveTo(-13, dy).lineTo(13, dy).stroke({ width: 4, color: ink });
1453
+ if (this.mono) {
1454
+ g.circle(0, r * 0.16, r).fill({ color: 0, alpha: disabled ? 0.08 : 0.18 });
1455
+ g.circle(0, 0, r).fill({ color: "#ffffff", alpha: disabled ? 0.6 : 1 });
1456
+ g.circle(0, 0, r * 0.925).stroke({ width: r * 0.15, color: "#0a0a0a", alpha: disabled ? 0.5 : 1 });
1457
+ } else if (this.dark) {
1458
+ g.circle(0, r * 0.16, r).fill({ color: 0, alpha: 0.18 });
1459
+ g.circle(0, 0, r).fill({ color: "#0a0a0a" });
1460
+ } else {
1461
+ g.circle(0, 0, r).fill({ color: fillC }).circle(0, 0, r).stroke({ width: 4, color: lineC });
1462
+ }
1463
+ const svg = GLYPH_SVG[this.glyph];
1464
+ if (svg) {
1465
+ const s = r / 15;
1466
+ this.glyphG.svg(svg);
1467
+ this.glyphG.scale.set(s);
1468
+ this.glyphG.position.set(-11.25 * s, -11.25 * s);
1469
+ this.glyphG.alpha = disabled ? 0.5 : 1;
1470
+ this.glyphG.visible = true;
1471
+ } else if (this.glyph === "menu") {
1472
+ const barW = this.dark ? 6 : 4;
1473
+ const spread = this.dark ? 9 : 8;
1474
+ for (const dy of [-spread, 0, spread]) g.moveTo(-14, dy).lineTo(14, dy).stroke({ width: barW, color: ink, cap: "round" });
1396
1475
  } else if (this.glyph === "close") {
1397
1476
  g.moveTo(-10, -10).lineTo(10, 10).moveTo(10, -10).lineTo(-10, 10).stroke({ width: 4, color: ink });
1398
- } else if (this.glyph === "speaker" || this.glyph === "speaker-mute") {
1399
- g.poly([-12, -4, -6, -4, 1, -11, 1, 11, -6, 4, -12, 4]).fill({ color: ink });
1400
- if (this.glyph === "speaker") {
1401
- g.moveTo(6, -6).lineTo(11, 0).lineTo(6, 6).stroke({ width: 2.5, color: ink });
1402
- g.moveTo(11, -9).lineTo(17, 0).lineTo(11, 9).stroke({ width: 2.5, color: ink });
1403
- } else {
1404
- g.moveTo(7, -6).lineTo(17, 6).moveTo(17, -6).lineTo(7, 6).stroke({ width: 2.5, color: ink });
1405
- }
1406
- } else if (this.glyph === "fullscreen") {
1407
- const a = 13, b = 6;
1408
- g.moveTo(-a, -a + b).lineTo(-a, -a).lineTo(-a + b, -a).moveTo(a - b, -a).lineTo(a, -a).lineTo(a, -a + b).moveTo(-a, a - b).lineTo(-a, a).lineTo(-a + b, a).moveTo(a - b, a).lineTo(a, a).lineTo(a, a - b).stroke({ width: 3, color: ink });
1409
- } else if (this.glyph === "fullscreen-exit") {
1410
- const c = 4, d = 12;
1411
- g.moveTo(-d, -c).lineTo(-c, -c).lineTo(-c, -d).moveTo(d, -c).lineTo(c, -c).lineTo(c, -d).moveTo(-d, c).lineTo(-c, c).lineTo(-c, d).moveTo(d, c).lineTo(c, c).lineTo(c, d).stroke({ width: 3, color: ink });
1412
1477
  }
1413
1478
  } else {
1414
1479
  const w = this.labelText ? this.labelText.width + 48 : 120;
@@ -1653,24 +1718,30 @@ var AutoplayView = class extends ControlView {
1653
1718
  }
1654
1719
  drawButton() {
1655
1720
  const st = this.auto.current;
1656
- this.countText.visible = st === "active";
1657
- if (st === "active") this.countText.text = this.fmt(this.auto.count.get());
1658
- if (st === "active") {
1659
- if (this.sprite) this.sprite.visible = false;
1660
- const r = this.sprite ? this.target / 2 : this.radius;
1661
- this.bg.clear();
1662
- this.bg.roundRect(-r, -r, 2 * r, 2 * r, r * 0.35).fill({ color: this.ui.theme.color.accent }).stroke({ width: 4, color: this.ui.theme.color.accentText });
1721
+ this.countText.visible = false;
1722
+ const th = this.ui.theme;
1723
+ const r = this.sprite ? this.target / 2 : this.radius;
1724
+ const active = st === "active";
1725
+ this.eventMode = active ? "none" : "static";
1726
+ this.cursor = active ? "default" : "pointer";
1727
+ if (active) {
1728
+ this.bg.clear().circle(0, 0, r).fill({ color: th.color.accent }).stroke({ width: 4, color: th.color.accentText });
1729
+ if (this.sprite) {
1730
+ this.sprite.visible = true;
1731
+ this.sprite.tint = th.color.accentText;
1732
+ this.sprite.alpha = 0.55;
1733
+ }
1663
1734
  return;
1664
1735
  }
1665
1736
  if (this.sprite) {
1666
1737
  this.sprite.visible = true;
1738
+ this.sprite.tint = 16777215;
1667
1739
  this.bg.clear();
1668
1740
  const tex = this.idleTex ?? this.activeTex;
1669
1741
  if (tex) this.sprite.texture = tex;
1670
1742
  this.fit();
1671
1743
  this.sprite.alpha = st === "disabled" ? 0.5 : 1;
1672
1744
  } else {
1673
- const th = this.ui.theme;
1674
1745
  this.bg.clear();
1675
1746
  this.bg.circle(0, 0, this.radius).fill({ color: th.color.surface }).stroke({ width: 4, color: th.color.accent });
1676
1747
  }
@@ -1841,8 +1912,8 @@ var AutoplayDrawerView = class extends Container {
1841
1912
  this.startBtn.position.set(W / 2, startY);
1842
1913
  this.startBtn.hitArea = new Rectangle(-startW / 2, -startH / 2, startW, startH);
1843
1914
  this.sheetH = startY + startH / 2 + 20 * k;
1844
- this.sheetBg.clear().rect(0, 0, W, this.sheetH + 40).fill({ color: DARK });
1845
- this.handle.clear().rect(W / 2 - 30 * k, 13 * k, 60 * k, 5 * k).fill({ color: DIM });
1915
+ this.sheetBg.clear().rect(-1, 0, W + 2, this.sheetH + 40 + 600).fill({ color: DARK });
1916
+ this.handle.clear().roundRect(W / 2 - 30 * k, 13 * k, 60 * k, 5 * k, 2.5 * k).fill({ color: DIM });
1846
1917
  return this.sheetH + 40;
1847
1918
  }
1848
1919
  drawGroup(g) {
@@ -2906,7 +2977,27 @@ var ReadoutView = class extends ControlView {
2906
2977
  this.ticker = ticker;
2907
2978
  const t = ui.theme;
2908
2979
  const inline = opts.inline ?? false;
2909
- const fill = opts.mono ? "#ffffff" : t.color.text;
2980
+ this.prefix = opts.prefix ?? false;
2981
+ const fill = opts.fill ?? (opts.mono ? "#ffffff" : t.color.text);
2982
+ if (this.prefix) {
2983
+ this.valueText = new Text({ text: "", style: { fontFamily: t.type.family, fontSize: 12, fill: opts.fill ?? "#ffffff", fontWeight: "400" } });
2984
+ this.valueText.alpha = opts.fill ? 1 : 0.5;
2985
+ this.valueText.anchor.set(0, 0);
2986
+ this.addChild(this.valueText);
2987
+ this.disposers.push(
2988
+ this.ro.value.subscribe(() => this.render()),
2989
+ this.ui.locale.subscribe(() => {
2990
+ if (!this.destroyed) this.render();
2991
+ })
2992
+ );
2993
+ if (this.ro.currency) this.disposers.push(this.ro.currency.subscribe(() => this.render()));
2994
+ if (this.ro.kind === "duration") {
2995
+ this.tick = (tk) => this.ro.tick(tk.deltaMS / 1e3);
2996
+ this.ticker.add(this.tick);
2997
+ }
2998
+ this.render();
2999
+ return;
3000
+ }
2910
3001
  if (ro.label) {
2911
3002
  this.caption = new Text({
2912
3003
  text: ui.t(ro.label).toUpperCase(),
@@ -2949,6 +3040,7 @@ var ReadoutView = class extends ControlView {
2949
3040
  caption;
2950
3041
  valueText;
2951
3042
  tick;
3043
+ prefix;
2952
3044
  render() {
2953
3045
  const v = this.ro.value.get();
2954
3046
  let text;
@@ -2965,7 +3057,7 @@ var ReadoutView = class extends ControlView {
2965
3057
  default:
2966
3058
  text = String(v);
2967
3059
  }
2968
- this.valueText.text = text;
3060
+ this.valueText.text = this.prefix && this.ro.label ? `${this.ui.t(this.ro.label)}: ${text}` : text;
2969
3061
  }
2970
3062
  dispose() {
2971
3063
  if (this.tick) this.ticker.remove(this.tick);
@@ -2973,9 +3065,18 @@ var ReadoutView = class extends ControlView {
2973
3065
  }
2974
3066
  };
2975
3067
  var INSET2 = 24;
2976
- var BTN_H = 50;
2977
- var BTN_GAP = 12;
3068
+ var BTN_H = 52;
3069
+ var BTN_GAP = 14;
2978
3070
  var ROW_GAP = 16;
3071
+ var LIGHT = {
3072
+ surface: "#ffffff",
3073
+ surfaceAlt: "#eef1f6",
3074
+ text: "#181b20",
3075
+ textDim: "#5b6472",
3076
+ border: "#000000",
3077
+ primary: "#0a0a0a",
3078
+ primaryText: "#ffffff"
3079
+ };
2979
3080
  var DialogView = class extends ControlView {
2980
3081
  constructor(panel, blocks, actions, ui, ticker, opts = {}) {
2981
3082
  super(panel, ui);
@@ -2986,6 +3087,8 @@ var DialogView = class extends ControlView {
2986
3087
  this.opts = opts;
2987
3088
  this.zIndex = 130;
2988
3089
  this.maxWidth = opts.maxWidth ?? 520;
3090
+ const lightTheme = { ...ui.theme, color: { ...ui.theme.color, surface: LIGHT.surface, surfaceAlt: LIGHT.surfaceAlt, text: LIGHT.text, textDim: LIGHT.textDim } };
3091
+ this.lightUi = new Proxy(ui, { get: (t, p) => p === "theme" ? lightTheme : Reflect.get(t, p) });
2989
3092
  this.backdrop.eventMode = "static";
2990
3093
  this.backdrop.on("pointertap", () => {
2991
3094
  if (!this.ui.noticeBlocking.get()) this.panel.closePanel();
@@ -3025,11 +3128,13 @@ var DialogView = class extends ControlView {
3025
3128
  childViews = [];
3026
3129
  screen;
3027
3130
  maxWidth;
3131
+ /** `ui` proxy with a light theme — so the shared block renderer draws dark-on-white
3132
+ * content inside the white modal card (Figma "default" dialog look). */
3133
+ lightUi;
3028
3134
  buildClose() {
3029
- const t = this.ui.theme;
3030
- const r = 18;
3031
- const bg = new Graphics().circle(0, 0, r).fill({ color: t.color.surfaceAlt }).stroke({ width: 2, color: t.color.textDim });
3032
- const x = new Graphics().moveTo(-6, -6).lineTo(6, 6).moveTo(6, -6).lineTo(-6, 6).stroke({ width: 3, color: t.color.text });
3135
+ const r = 22;
3136
+ const bg = new Graphics().circle(0, 0, r).fill({ color: LIGHT.primary });
3137
+ const x = new Graphics().moveTo(-7, -7).lineTo(7, 7).moveTo(7, -7).lineTo(-7, 7).stroke({ width: 3, color: "#ffffff", cap: "round" });
3033
3138
  this.closeBtn.addChild(bg, x);
3034
3139
  this.closeBtn.eventMode = "static";
3035
3140
  this.closeBtn.cursor = "pointer";
@@ -3048,13 +3153,12 @@ var DialogView = class extends ControlView {
3048
3153
  if (!this.panel.isOpen) return;
3049
3154
  const W = s.width;
3050
3155
  const H = s.height;
3051
- const t = this.ui.theme;
3052
3156
  for (const v of this.childViews) v.dispose();
3053
3157
  this.childViews.length = 0;
3054
3158
  for (const ch of this.content.removeChildren()) ch.destroy();
3055
3159
  const cardW = Math.min(W - 48, this.maxWidth);
3056
3160
  const innerW = cardW - INSET2 * 2;
3057
- const col = buildBlockColumn(this.blocks.get(), [], this.ui, this.ticker, innerW, { controlSkins: this.opts.controlSkins });
3161
+ const col = buildBlockColumn(this.blocks.get(), [], this.lightUi, this.ticker, innerW, { controlSkins: this.opts.controlSkins });
3058
3162
  this.childViews = col.views;
3059
3163
  const kids = col.content.removeChildren();
3060
3164
  if (kids.length) this.content.addChild(...kids);
@@ -3064,15 +3168,15 @@ var DialogView = class extends ControlView {
3064
3168
  const cardH = Math.min(H - 48, wantH);
3065
3169
  const cx = (W - cardW) / 2;
3066
3170
  const cy = (H - cardH) / 2;
3067
- this.backdrop.clear().rect(0, 0, W, H).fill({ color: 0, alpha: 0.62 });
3171
+ this.backdrop.clear().rect(0, 0, W, H).fill({ color: 0, alpha: 0.5 });
3068
3172
  this.backdrop.hitArea = new Rectangle(0, 0, W, H);
3069
- this.card.clear().roundRect(cx, cy, cardW, cardH, t.radius.card).fill({ color: t.color.surface }).stroke({ width: 2, color: t.color.accent });
3173
+ this.card.clear().roundRect(cx, cy, cardW, cardH, 14).fill({ color: LIGHT.surface }).stroke({ width: 2.5, color: LIGHT.border });
3070
3174
  const bodyH = cardH - (buttonsH ? ROW_GAP + buttonsH + INSET2 : INSET2);
3071
3175
  this.content.x = cx + cardW / 2;
3072
3176
  this.content.y = cy;
3073
3177
  this.maskG.clear().rect(cx, cy, cardW, Math.max(0, bodyH)).fill({ color: 16777215 });
3074
3178
  this.buttons.position.set(cx + cardW / 2, cy + cardH - INSET2 / 2 - buttonsH / 2);
3075
- this.closeBtn.position.set(cx + cardW - 20, cy + 20);
3179
+ this.closeBtn.position.set(cx + cardW - 8, cy + 8);
3076
3180
  }
3077
3181
  /** Build the footer buttons from `noticeActions` into a centered row (shrunk to
3078
3182
  * fit if needed). Returns the row height (0 when there are no actions). */
@@ -3105,7 +3209,7 @@ var DialogView = class extends ControlView {
3105
3209
  const bg = new Graphics();
3106
3210
  const label = new Text({
3107
3211
  text: this.ui.t(action.label),
3108
- style: { fontFamily: t.type.family, fontSize: 18, fontWeight: "800", fill: variant === "primary" ? t.color.accentText : t.color.text, letterSpacing: 0.5 }
3212
+ style: { fontFamily: t.type.family, fontSize: 18, fontWeight: "800", fill: variant === "primary" ? LIGHT.primaryText : LIGHT.text, letterSpacing: 0.5 }
3109
3213
  });
3110
3214
  label.anchor.set(0.5);
3111
3215
  node.addChild(bg, label);
@@ -3122,12 +3226,11 @@ var DialogView = class extends ControlView {
3122
3226
  return { node, bg, width, variant };
3123
3227
  }
3124
3228
  drawButton(bg, variant, width) {
3125
- const t = this.ui.theme;
3126
3229
  bg.clear();
3127
3230
  if (variant === "primary") {
3128
- bg.roundRect(-width / 2, -BTN_H / 2, width, BTN_H, BTN_H / 2).fill({ color: t.color.accent });
3231
+ bg.roundRect(-width / 2, -BTN_H / 2, width, BTN_H, BTN_H / 2).fill({ color: LIGHT.primary });
3129
3232
  } else {
3130
- bg.roundRect(-width / 2, -BTN_H / 2, width, BTN_H, BTN_H / 2).fill({ color: t.color.surfaceAlt }).stroke({ width: 2, color: t.color.accent });
3233
+ bg.roundRect(-width / 2, -BTN_H / 2, width, BTN_H, BTN_H / 2).fill({ color: LIGHT.surface }).stroke({ width: 2.5, color: LIGHT.border });
3131
3234
  }
3132
3235
  }
3133
3236
  applyOpen(open) {
@@ -3261,8 +3364,8 @@ var OpenUIPixi = class {
3261
3364
  glyph: "menu",
3262
3365
  iconTexture: ic.settingsIdle,
3263
3366
  iconTarget: 88,
3264
- mono: true
3265
- // b&w like the turbo button (used on the drawn glyph path)
3367
+ dark: true
3368
+ // Figma ☰: solid black circle, white bars, no ring
3266
3369
  });
3267
3370
  settingsView.zIndex = 5;
3268
3371
  const menuView = this.opts.menu === false ? void 0 : this.buildMenu(ticker);
@@ -3270,7 +3373,7 @@ var OpenUIPixi = class {
3270
3373
  offTexture: ic.turboOff,
3271
3374
  onTexture: ic.turboOn,
3272
3375
  modeTextures: ic.turboModes,
3273
- target: 88
3376
+ target: 79
3274
3377
  });
3275
3378
  const spinOff = this.ui.spin.layout.offset ?? [0, 0];
3276
3379
  const autoOff = this.ui.autoplay.layout.offset ?? [0, 0];
@@ -3278,23 +3381,24 @@ var OpenUIPixi = class {
3278
3381
  const autoplayView = new AutoplayView(this.ui.autoplay, this.ui, ticker, {
3279
3382
  idleTexture: ic.autoIdle,
3280
3383
  activeTexture: ic.autoActive,
3281
- target: 88,
3384
+ target: 73,
3282
3385
  picker,
3283
3386
  arcCenter: { x: spinOff[0] - autoOff[0], y: spinOff[1] - autoOff[1] },
3284
3387
  arcStepDeg: 22
3285
3388
  });
3286
3389
  autoplayView.zIndex = 20;
3287
- const bonusView = new ButtonView(this.ui.bonusButton, this.ui, ticker, { shape: "circle", radius: 44, iconTexture: ic.bonus, iconTarget: 110 });
3390
+ const bonusView = new ButtonView(this.ui.bonusButton, this.ui, ticker, { shape: "circle", radius: 60, iconTexture: ic.bonus, iconTarget: 130 });
3288
3391
  const betPlusView = new ButtonView(this.ui.betPlus, this.ui, ticker, { shape: "circle", radius: 30, iconTexture: ic.betPlus, iconTarget: 64 });
3289
3392
  const betMinusView = new ButtonView(this.ui.betMinus, this.ui, ticker, { shape: "circle", radius: 30, iconTexture: ic.betMinus, iconTarget: 64 });
3290
- const muteView = new ButtonView(this.ui.muteButton, this.ui, ticker, { shape: "circle", radius: 22, glyph: "speaker", iconTarget: 44, mono: true });
3291
- const fullscreenView = new ButtonView(this.ui.fullscreenButton, this.ui, ticker, { shape: "circle", radius: 22, glyph: "fullscreen", iconTarget: 44, mono: true });
3393
+ const muteView = new ButtonView(this.ui.muteButton, this.ui, ticker, { shape: "circle", radius: 30, glyph: "speaker", iconTarget: 60, mono: true });
3394
+ const fullscreenView = new ButtonView(this.ui.fullscreenButton, this.ui, ticker, { shape: "circle", radius: 30, glyph: "fullscreen", iconTarget: 60, mono: true });
3292
3395
  muteView.zIndex = 65;
3293
3396
  fullscreenView.zIndex = 65;
3294
3397
  const statusBarSide = this.opts.statusBar;
3295
- const rtpView = statusBarSide ? void 0 : new ReadoutView(this.ui.rtp, this.ui, ticker);
3296
- const netView = statusBarSide ? void 0 : new ReadoutView(this.ui.netPosition, this.ui, ticker);
3297
- const timerView = statusBarSide ? void 0 : new ReadoutView(this.ui.sessionTimer, this.ui, ticker);
3398
+ const roOpts = { prefix: true, fill: this.opts.readoutColor };
3399
+ const rtpView = statusBarSide ? void 0 : new ReadoutView(this.ui.rtp, this.ui, ticker, roOpts);
3400
+ const netView = statusBarSide ? void 0 : new ReadoutView(this.ui.netPosition, this.ui, ticker, roOpts);
3401
+ const timerView = statusBarSide ? void 0 : new ReadoutView(this.ui.sessionTimer, this.ui, ticker, roOpts);
3298
3402
  const entries = [
3299
3403
  [this.ui.spin.id, spinView],
3300
3404
  [this.ui.balance.id, balanceView],
@@ -3375,6 +3479,23 @@ var OpenUIPixi = class {
3375
3479
  const bar = new StatusBarView([this.ui.netPosition, this.ui.rtp, this.ui.sessionTimer], this.ui, ticker, statusBarSide);
3376
3480
  this.root.addChild(bar);
3377
3481
  this.overlays.push(bar);
3482
+ } else {
3483
+ const vignette = new Graphics();
3484
+ vignette.zIndex = -1;
3485
+ this.root.addChild(vignette);
3486
+ this.overlays.push({
3487
+ applyLayout: (s) => {
3488
+ const r = 200 * s.scale;
3489
+ vignette.clear();
3490
+ for (let i = 0; i < 14; i++) {
3491
+ const t = i / 14;
3492
+ vignette.circle(0, 0, r * (1 - t)).fill({ color: 0, alpha: 0.5 / 14 });
3493
+ }
3494
+ },
3495
+ dispose: () => {
3496
+ if (!vignette.destroyed) vignette.destroy();
3497
+ }
3498
+ });
3378
3499
  }
3379
3500
  const replayBadge = new Container();
3380
3501
  const replayBg = new Graphics();
@@ -3404,21 +3525,6 @@ var OpenUIPixi = class {
3404
3525
  if (!replayBadge.destroyed) replayText.text = this.ui.t("openui.replay").toUpperCase();
3405
3526
  })
3406
3527
  );
3407
- if (ic.settingsIdle && ic.settingsActive) {
3408
- const idle = ic.settingsIdle;
3409
- const active = ic.settingsActive;
3410
- this.disposers.push(
3411
- this.ui.settingsPanel.state.subscribe(() => {
3412
- settingsView.setIconTexture(this.ui.settingsPanel.isOpen ? active : idle);
3413
- })
3414
- );
3415
- } else {
3416
- this.disposers.push(
3417
- this.ui.settingsPanel.state.subscribe(() => {
3418
- settingsView.setGlyph(this.ui.settingsPanel.isOpen ? "close" : "menu");
3419
- })
3420
- );
3421
- }
3422
3528
  const applyLayout = () => {
3423
3529
  const screen = this.ui.screen.get();
3424
3530
  const barH = statusBarSide ? StatusBarView.heightFor(screen) : 0;