@energy8platform/shell 0.7.2 → 0.8.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/html.cjs.js +1 -1
- package/dist/html.d.ts +7 -2
- package/dist/html.esm.js +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.esm.js +1 -1
- package/dist/pixi.cjs.js +201 -18
- package/dist/pixi.cjs.js.map +1 -1
- package/dist/pixi.d.ts +7 -2
- package/dist/pixi.esm.js +201 -18
- package/dist/pixi.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/core/bonusGroups.ts +29 -0
- package/src/core/types.ts +5 -0
- package/src/core/version.ts +1 -1
- package/src/ui/pixi/components/BuyBonus.ts +195 -19
package/dist/pixi.d.ts
CHANGED
|
@@ -162,6 +162,11 @@ interface BonusOption$1 {
|
|
|
162
162
|
priceMultiplier: number;
|
|
163
163
|
/** Per-option accent override. Falls back to the type default (bonus → purple, feature → gold). */
|
|
164
164
|
accentColor?: string;
|
|
165
|
+
/** Options sharing a `groupedBy` key occupy ONE card slot and are flipped through with arrows —
|
|
166
|
+
* e.g. four same-priced Ante variants that each swap the character, its art and its volatility.
|
|
167
|
+
* Pixi shell only; the DOM shell ignores the key and keeps a card per option. Options with a
|
|
168
|
+
* `custom` renderer are never grouped. */
|
|
169
|
+
groupedBy?: string;
|
|
165
170
|
/** Override the card UI. Return the card's inner content; the shell keeps the grid wrapper,
|
|
166
171
|
* accent vars and live re-pricing, and runs the normal buy flow when you call `ctx.select()`.
|
|
167
172
|
* Core uses `unknown`; each renderer re-exports a typed alias (ui/html → HTMLElement,
|
|
@@ -633,7 +638,7 @@ declare class ShellController extends EventEmitter<ShellEvents> implements Shell
|
|
|
633
638
|
tokens: ShellTokens;
|
|
634
639
|
layout: ShellLayoutMode;
|
|
635
640
|
soundOn: boolean;
|
|
636
|
-
readonly engineVersion = "0.
|
|
641
|
+
readonly engineVersion = "0.8.0";
|
|
637
642
|
readonly actions: ShellActions;
|
|
638
643
|
private renderer;
|
|
639
644
|
private i18n;
|
|
@@ -778,7 +783,7 @@ interface I18n {
|
|
|
778
783
|
declare function createI18n(opts: I18nOptions): I18n;
|
|
779
784
|
|
|
780
785
|
/** The @energy8platform/shell package version, stamped into the game-info footer. */
|
|
781
|
-
declare const PACKAGE_VERSION = "0.
|
|
786
|
+
declare const PACKAGE_VERSION = "0.8.0";
|
|
782
787
|
|
|
783
788
|
/** A shell: the renderer-agnostic controller plus the surface facade (safeArea/barHeight/setVisible)
|
|
784
789
|
* an embedding host reads. `createShell`, `createGameShell` and `createPixiShell` all return this. */
|
package/dist/pixi.esm.js
CHANGED
|
@@ -1692,7 +1692,7 @@ class KeyboardController {
|
|
|
1692
1692
|
|
|
1693
1693
|
// AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
|
|
1694
1694
|
/** The @energy8platform/shell package version, stamped into the game-info footer. */
|
|
1695
|
-
const PACKAGE_VERSION = '0.
|
|
1695
|
+
const PACKAGE_VERSION = '0.8.0';
|
|
1696
1696
|
|
|
1697
1697
|
/** Apply defaults to the raw config (the mount target lives on the renderer, not here). */
|
|
1698
1698
|
function resolveConfig(config) {
|
|
@@ -5377,6 +5377,34 @@ function imageBox(url, w, h) {
|
|
|
5377
5377
|
return c;
|
|
5378
5378
|
}
|
|
5379
5379
|
|
|
5380
|
+
/** Collapse buy-bonus options that share a `groupedBy` key into one slot. A slot with more than one
|
|
5381
|
+
* member is rendered by the pixi shell as a single card with arrows (the DOM shell ignores the key
|
|
5382
|
+
* and keeps a card per option).
|
|
5383
|
+
*
|
|
5384
|
+
* A group takes the position of its FIRST member, so the strip order stays predictable however the
|
|
5385
|
+
* members are scattered through the array. Options with a game-supplied `custom` renderer never
|
|
5386
|
+
* join a group — the shell has no layout for their interior and could not place arrows over it. */
|
|
5387
|
+
function groupBonusSlots(bonuses) {
|
|
5388
|
+
const slots = [];
|
|
5389
|
+
const byKey = new Map();
|
|
5390
|
+
for (const bonus of bonuses) {
|
|
5391
|
+
const key = bonus.groupedBy;
|
|
5392
|
+
if (!key || bonus.custom) {
|
|
5393
|
+
slots.push([bonus]);
|
|
5394
|
+
continue;
|
|
5395
|
+
}
|
|
5396
|
+
const open = byKey.get(key);
|
|
5397
|
+
if (open) {
|
|
5398
|
+
open.push(bonus);
|
|
5399
|
+
continue;
|
|
5400
|
+
}
|
|
5401
|
+
const slot = [bonus];
|
|
5402
|
+
byKey.set(key, slot);
|
|
5403
|
+
slots.push(slot);
|
|
5404
|
+
}
|
|
5405
|
+
return slots;
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5380
5408
|
/** Below this frame height (px), a wide/landscape popout (e.g. Popout S 400×225) stacks its cards
|
|
5381
5409
|
* vertically and scrolls — like mobile — so descriptions stay readable instead of shrinking to a
|
|
5382
5410
|
* ~4px floor. Wide + taller frames (Popout L and up) keep the centred horizontal row. */
|
|
@@ -5410,6 +5438,9 @@ class BuyBonusOverlay extends Container {
|
|
|
5410
5438
|
cardEntries = [];
|
|
5411
5439
|
/** Keyboard focus index into the affordable subset of cardEntries. -1 = none. */
|
|
5412
5440
|
focusIndex = -1;
|
|
5441
|
+
/** Which member each `groupedBy` slot is showing, kept across rebuilds (bet steps, resizes) so a
|
|
5442
|
+
* flipped-to variant doesn't snap back to the first one under the player. */
|
|
5443
|
+
groupIndex = new Map();
|
|
5413
5444
|
/** true when cards are stacked vertically (mobile, or a short landscape popout). */
|
|
5414
5445
|
stack = false;
|
|
5415
5446
|
// Drag-scroll state. The drag is handled on the (unmasked) overlay, not the masked strip — a mask
|
|
@@ -5528,7 +5559,9 @@ class BuyBonusOverlay extends Container {
|
|
|
5528
5559
|
const top = this.headerH + 6;
|
|
5529
5560
|
const areaH = this.h - top - this.footerH - 6;
|
|
5530
5561
|
const gap = 14;
|
|
5531
|
-
|
|
5562
|
+
// Grouped options share ONE slot (a carousel card), so the width fit counts SLOTS, not options.
|
|
5563
|
+
const slots = groupBonusSlots(this.bonuses);
|
|
5564
|
+
const n = Math.max(1, slots.length);
|
|
5532
5565
|
// The whole card is laid out in `em`; pick the largest em that fits BOTH dimensions of the
|
|
5533
5566
|
// available area, so the card is always fully visible (no horizontal clip, CTA never under the
|
|
5534
5567
|
// footer). emH keeps the card height within the band between header and footer; emW makes the
|
|
@@ -5540,16 +5573,44 @@ class BuyBonusOverlay extends Container {
|
|
|
5540
5573
|
: (this.w - 48 - (n - 1) * gap) / (18 * n); // row: N cards + gaps fit the frame width
|
|
5541
5574
|
const em = stack ? Math.min(12, emW) : clamp(4, Math.min(emH, emW), 12);
|
|
5542
5575
|
const cardW = Math.min(18 * em, this.w - 48);
|
|
5543
|
-
const cards =
|
|
5576
|
+
const cards = [];
|
|
5577
|
+
this.cardEntries = [];
|
|
5578
|
+
for (const slot of slots) {
|
|
5579
|
+
if (slot.length === 1) {
|
|
5580
|
+
const view = this.buildCard(slot[0], cardW, em, stack, areaH);
|
|
5581
|
+
cards.push(view);
|
|
5582
|
+
this.cardEntries.push({ view, bonus: slot[0], affordable: this.isAffordable(slot[0]) });
|
|
5583
|
+
continue;
|
|
5584
|
+
}
|
|
5585
|
+
// Carousel slot: every member is built (the slot sizes to the tallest and flipping costs
|
|
5586
|
+
// nothing), but only the current one is visible.
|
|
5587
|
+
const key = slot[0].groupedBy;
|
|
5588
|
+
// Flipping the card also moves keyboard focus onto the shown member: one cursor, not two
|
|
5589
|
+
// (otherwise the next rebuild would snap the card back to whatever the focus still pointed at).
|
|
5590
|
+
const group = new GroupCard((i) => {
|
|
5591
|
+
this.groupIndex.set(key, i);
|
|
5592
|
+
this.focusShownMember(group, i);
|
|
5593
|
+
});
|
|
5594
|
+
const members = slot.map((b, i) => this.buildCard(b, cardW, em, stack, areaH, {
|
|
5595
|
+
index: i,
|
|
5596
|
+
count: slot.length,
|
|
5597
|
+
prevLabel: `bb-nav-prev:${key}`,
|
|
5598
|
+
nextLabel: `bb-nav-next:${key}`,
|
|
5599
|
+
onStep: (dir) => {
|
|
5600
|
+
if (this.dragged)
|
|
5601
|
+
return; // a scroll gesture, not a tap
|
|
5602
|
+
group.step(dir);
|
|
5603
|
+
},
|
|
5604
|
+
}));
|
|
5605
|
+
group.setCards(members, this.groupIndex.get(key) ?? 0);
|
|
5606
|
+
cards.push(group);
|
|
5607
|
+
for (let i = 0; i < slot.length; i++) {
|
|
5608
|
+
this.cardEntries.push({ view: members[i], bonus: slot[i], affordable: this.isAffordable(slot[i]), group });
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5544
5611
|
const cardH = Math.max(...cards.map((c) => c.height));
|
|
5545
5612
|
for (const c of cards)
|
|
5546
5613
|
c.setHeight(cardH);
|
|
5547
|
-
// Rebuild card entries for keyboard navigation
|
|
5548
|
-
this.cardEntries = this.bonuses.map((b, i) => ({
|
|
5549
|
-
view: cards[i],
|
|
5550
|
-
bonus: b,
|
|
5551
|
-
affordable: this.isAffordable(b),
|
|
5552
|
-
}));
|
|
5553
5614
|
// Restore or init keyboard focus on the first affordable card
|
|
5554
5615
|
const affordable = this.cardEntries.filter((e) => e.affordable);
|
|
5555
5616
|
if (affordable.length > 0) {
|
|
@@ -5590,7 +5651,7 @@ class BuyBonusOverlay extends Container {
|
|
|
5590
5651
|
}
|
|
5591
5652
|
}
|
|
5592
5653
|
// ── one card ──────────────────────────────────────────────────────────────
|
|
5593
|
-
buildCard(bonus, cardW, em, stack, areaH) {
|
|
5654
|
+
buildCard(bonus, cardW, em, stack, areaH, nav) {
|
|
5594
5655
|
const accent = effectiveAccent(bonus);
|
|
5595
5656
|
const ink = contrastText(accent);
|
|
5596
5657
|
const price = bonus.priceMultiplier * this.host.state.bet;
|
|
@@ -5626,6 +5687,7 @@ class BuyBonusOverlay extends Container {
|
|
|
5626
5687
|
enabled,
|
|
5627
5688
|
ctaLabel: this.host.t(bonus.type === 'feature' ? 'Activate' : 'Buy'),
|
|
5628
5689
|
onSelect: select,
|
|
5690
|
+
nav,
|
|
5629
5691
|
});
|
|
5630
5692
|
return card;
|
|
5631
5693
|
}
|
|
@@ -5744,7 +5806,7 @@ class BuyBonusOverlay extends Container {
|
|
|
5744
5806
|
// the buttons keep the card silhouette without a mask blocking their pointer events.
|
|
5745
5807
|
const half = cardW / 2;
|
|
5746
5808
|
const r = 1.3 * em;
|
|
5747
|
-
card.addChild(footerButton(this.host, this.host.t('Cancel'), 'ghost', half, ctaH, 0, y, () => this.removeConfirm(), undefined, r, 0), footerButton(this.host, this.host.t(bonus.type === 'feature' ? 'Activate' : 'Buy'), accent, half, ctaH, half, y, () => {
|
|
5809
|
+
card.addChild(footerButton(this.host, this.host.t('Cancel'), 'ghost', half, ctaH, 0, y, () => this.removeConfirm(), undefined, r, 0), labelled('bb-confirm-ok', footerButton(this.host, this.host.t(bonus.type === 'feature' ? 'Activate' : 'Buy'), accent, half, ctaH, half, y, () => {
|
|
5748
5810
|
if (!this.isAffordable(bonus))
|
|
5749
5811
|
return;
|
|
5750
5812
|
if (bonus.type === 'feature')
|
|
@@ -5752,7 +5814,7 @@ class BuyBonusOverlay extends Container {
|
|
|
5752
5814
|
else
|
|
5753
5815
|
this.host.actions.selectBuyBonus(bonus.id);
|
|
5754
5816
|
this.host.closeLayer();
|
|
5755
|
-
}, ink, 0, r));
|
|
5817
|
+
}, ink, 0, r)));
|
|
5756
5818
|
card.position.set((this.w - cardW) / 2, (this.h - cardH) / 2);
|
|
5757
5819
|
layer.addChild(card);
|
|
5758
5820
|
// close X pinned to screen corner
|
|
@@ -5778,14 +5840,28 @@ class BuyBonusOverlay extends Container {
|
|
|
5778
5840
|
this.buildCards();
|
|
5779
5841
|
this.buildFooter();
|
|
5780
5842
|
}
|
|
5843
|
+
/** Move keyboard focus onto the member a carousel slot just flipped to. Skipped when that member
|
|
5844
|
+
* can't be bought at the current bet — focus only ever sits on affordable cards. */
|
|
5845
|
+
focusShownMember(group, index) {
|
|
5846
|
+
const card = group.cardAt(index);
|
|
5847
|
+
const at = this.cardEntries.filter((ce) => ce.affordable).findIndex((ce) => ce.group === group && ce.view === card);
|
|
5848
|
+
if (at < 0)
|
|
5849
|
+
return;
|
|
5850
|
+
this.focusIndex = at;
|
|
5851
|
+
this.applyFocusRing();
|
|
5852
|
+
}
|
|
5781
5853
|
/** Apply or clear the focus ring on affordable cards. */
|
|
5782
5854
|
applyFocusRing() {
|
|
5783
5855
|
const affordable = this.cardEntries.filter((ce) => ce.affordable);
|
|
5784
5856
|
for (let i = 0; i < affordable.length; i++) {
|
|
5785
|
-
const
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5857
|
+
const entry = affordable[i];
|
|
5858
|
+
const focused = i === this.focusIndex;
|
|
5859
|
+
// Focusing a member of a carousel slot brings it to the front — the keyboard walks options,
|
|
5860
|
+
// and the card follows.
|
|
5861
|
+
if (focused && entry.group)
|
|
5862
|
+
entry.group.showCard(entry.view);
|
|
5863
|
+
if (entry.view instanceof BonusCard)
|
|
5864
|
+
entry.view.setFocused(focused);
|
|
5789
5865
|
}
|
|
5790
5866
|
}
|
|
5791
5867
|
/** Two-phase keyboard handler.
|
|
@@ -5894,6 +5970,55 @@ class CustomCard {
|
|
|
5894
5970
|
this.height = total; // the game owns its own layout; we only record the slot height
|
|
5895
5971
|
}
|
|
5896
5972
|
}
|
|
5973
|
+
/** One slot shared by several options (`groupedBy`) — e.g. four same-priced Ante characters. Every
|
|
5974
|
+
* member card is built (the slot sizes to the tallest, and flipping is then just a visibility
|
|
5975
|
+
* swap, so the card never resizes under the player); one is visible at a time. */
|
|
5976
|
+
class GroupCard {
|
|
5977
|
+
node = new Container();
|
|
5978
|
+
height = 0;
|
|
5979
|
+
cards = [];
|
|
5980
|
+
index = 0;
|
|
5981
|
+
/** Reports the shown member back to the overlay, which keeps it across rebuilds. */
|
|
5982
|
+
onIndex;
|
|
5983
|
+
constructor(onIndex) {
|
|
5984
|
+
this.onIndex = onIndex;
|
|
5985
|
+
}
|
|
5986
|
+
setCards(cards, index) {
|
|
5987
|
+
this.cards = cards;
|
|
5988
|
+
for (const c of cards)
|
|
5989
|
+
this.node.addChild(c.node);
|
|
5990
|
+
this.height = Math.max(...cards.map((c) => c.height));
|
|
5991
|
+
this.show(index, false); // restoring a remembered position is not a player action
|
|
5992
|
+
}
|
|
5993
|
+
/** Flip to member `i`, wrapping around in both directions. */
|
|
5994
|
+
show(i, notify = true) {
|
|
5995
|
+
const n = this.cards.length;
|
|
5996
|
+
if (n === 0)
|
|
5997
|
+
return;
|
|
5998
|
+
this.index = ((i % n) + n) % n;
|
|
5999
|
+
for (let k = 0; k < n; k++)
|
|
6000
|
+
this.cards[k].node.visible = k === this.index;
|
|
6001
|
+
if (notify)
|
|
6002
|
+
this.onIndex(this.index);
|
|
6003
|
+
}
|
|
6004
|
+
step(dir) {
|
|
6005
|
+
this.show(this.index + dir);
|
|
6006
|
+
}
|
|
6007
|
+
cardAt(index) {
|
|
6008
|
+
return this.cards[index];
|
|
6009
|
+
}
|
|
6010
|
+
/** Bring a specific member's card to the front (keyboard focus landing on that option). */
|
|
6011
|
+
showCard(card) {
|
|
6012
|
+
const i = this.cards.findIndex((c) => c === card);
|
|
6013
|
+
if (i >= 0 && i !== this.index)
|
|
6014
|
+
this.show(i);
|
|
6015
|
+
}
|
|
6016
|
+
setHeight(total) {
|
|
6017
|
+
this.height = total;
|
|
6018
|
+
for (const c of this.cards)
|
|
6019
|
+
c.setHeight(total);
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
5897
6022
|
class BonusCard {
|
|
5898
6023
|
node = new Container();
|
|
5899
6024
|
height = 0;
|
|
@@ -5915,9 +6040,22 @@ class BonusCard {
|
|
|
5915
6040
|
const volColor = opts.enabled ? accent : 'rgba(255,255,255,.4)';
|
|
5916
6041
|
const top = new Container();
|
|
5917
6042
|
let y = 1.25 * em;
|
|
5918
|
-
|
|
6043
|
+
// With arrows the title has to clear them on both sides, or a long name wraps under the glyphs.
|
|
6044
|
+
const arrowSz = 1.5 * em;
|
|
6045
|
+
const arrowPad = 0.45 * em;
|
|
6046
|
+
const titleW = opts.nav ? innerW - 2 * (arrowSz + 2 * arrowPad) : innerW;
|
|
6047
|
+
const title = makeText(bonus.title, { size: 1.3 * em, weight: '800', color: titleColor, letterSpacing: 1.3 * em * 0.04, upper: true, align: 'center', wrapWidth: titleW });
|
|
5919
6048
|
title.position.set((cardW - title.width) / 2, y);
|
|
5920
6049
|
top.addChild(title);
|
|
6050
|
+
if (opts.nav) {
|
|
6051
|
+
const boxSz = arrowSz + 2 * arrowPad;
|
|
6052
|
+
const rowY = y + (title.height - boxSz) / 2;
|
|
6053
|
+
const prev = arrowButton(opts.nav.prevLabel, -1, arrowSz, arrowPad, accent, opts.nav.onStep);
|
|
6054
|
+
const next = arrowButton(opts.nav.nextLabel, 1, arrowSz, arrowPad, accent, opts.nav.onStep);
|
|
6055
|
+
prev.position.set(0.6 * em, rowY);
|
|
6056
|
+
next.position.set(cardW - 0.6 * em - boxSz, rowY);
|
|
6057
|
+
top.addChild(prev, next);
|
|
6058
|
+
}
|
|
5921
6059
|
y += title.height + 0.75 * em;
|
|
5922
6060
|
const thumb = thumbNode(host, bonus, accent, 6.2 * em);
|
|
5923
6061
|
thumb.position.set((cardW - 6.2 * em) / 2, y);
|
|
@@ -5927,6 +6065,13 @@ class BonusCard {
|
|
|
5927
6065
|
desc.position.set((cardW - desc.width) / 2, y);
|
|
5928
6066
|
top.addChild(desc);
|
|
5929
6067
|
y += desc.height;
|
|
6068
|
+
if (opts.nav) {
|
|
6069
|
+
y += 0.7 * em;
|
|
6070
|
+
const dots = dotRow(opts.nav.count, opts.nav.index, accent, 0.24 * em, 0.5 * em);
|
|
6071
|
+
dots.position.set((cardW - dots.width) / 2, y);
|
|
6072
|
+
top.addChild(dots);
|
|
6073
|
+
y += 0.48 * em;
|
|
6074
|
+
}
|
|
5930
6075
|
this.topH = y;
|
|
5931
6076
|
// bottom block: volatility + price
|
|
5932
6077
|
let by = 0;
|
|
@@ -5941,7 +6086,8 @@ class BonusCard {
|
|
|
5941
6086
|
this.bottomBlock.addChild(priceText);
|
|
5942
6087
|
by += priceText.height;
|
|
5943
6088
|
this.bottomH = by;
|
|
5944
|
-
this.cta = ctaButton(host, opts.ctaLabel, accent, opts.ink, cardW, this.ctaH, opts.enabled, opts.onSelect, 1.4 * em);
|
|
6089
|
+
this.cta = labelled(`bb-cta:${bonus.id}`, ctaButton(host, opts.ctaLabel, accent, opts.ink, cardW, this.ctaH, opts.enabled, opts.onSelect, 1.4 * em));
|
|
6090
|
+
this.node.label = `bb-card:${bonus.id}`;
|
|
5945
6091
|
this.node.addChild(this.bg, top, this.bottomBlock, this.cta);
|
|
5946
6092
|
if (opts.enabled) {
|
|
5947
6093
|
this.node.eventMode = 'static';
|
|
@@ -5992,6 +6138,43 @@ class BonusCard {
|
|
|
5992
6138
|
}
|
|
5993
6139
|
}
|
|
5994
6140
|
// ── shared card bits ─────────────────────────────────────────────────────────
|
|
6141
|
+
/** Tag a node so the shell (and its tests) can find it in the display tree. */
|
|
6142
|
+
function labelled(label, node) {
|
|
6143
|
+
node.label = label;
|
|
6144
|
+
return node;
|
|
6145
|
+
}
|
|
6146
|
+
/** One carousel arrow. `stopPropagation` keeps the tap off the card underneath, which would
|
|
6147
|
+
* otherwise open the confirm dialog for the member being flipped away from. */
|
|
6148
|
+
function arrowButton(label, dir, size, pad, accent, onStep) {
|
|
6149
|
+
const b = new Container();
|
|
6150
|
+
b.label = label;
|
|
6151
|
+
const glyph = makeIcon('chevronRight', size, '#ffffff');
|
|
6152
|
+
if (dir === -1)
|
|
6153
|
+
glyph.spin = Math.PI; // no chevronLeft in the set — the glyph spins about its centre
|
|
6154
|
+
glyph.position.set(pad, pad);
|
|
6155
|
+
b.addChild(rectHit(size + pad * 2, size + pad * 2), glyph);
|
|
6156
|
+
b.eventMode = 'static';
|
|
6157
|
+
b.cursor = 'pointer';
|
|
6158
|
+
b.on('pointerover', () => glyph.setColor(accent));
|
|
6159
|
+
b.on('pointerout', () => glyph.setColor('#ffffff'));
|
|
6160
|
+
b.on('pointertap', (e) => {
|
|
6161
|
+
e.stopPropagation();
|
|
6162
|
+
onStep(dir);
|
|
6163
|
+
});
|
|
6164
|
+
return b;
|
|
6165
|
+
}
|
|
6166
|
+
/** Position indicator for a carousel slot: one dot per member, the current one in the accent. */
|
|
6167
|
+
function dotRow(count, index, accent, r, gap) {
|
|
6168
|
+
const c = new Container();
|
|
6169
|
+
for (let i = 0; i < count; i++) {
|
|
6170
|
+
const dot = new Graphics();
|
|
6171
|
+
const on = i === index;
|
|
6172
|
+
dot.circle(r, r, r).fill(on ? accent : 'rgba(255,255,255,.28)');
|
|
6173
|
+
dot.position.set(i * (2 * r + gap), 0);
|
|
6174
|
+
c.addChild(dot);
|
|
6175
|
+
}
|
|
6176
|
+
return c;
|
|
6177
|
+
}
|
|
5995
6178
|
function thumbNode(host, bonus, accent, h) {
|
|
5996
6179
|
const c = new Container();
|
|
5997
6180
|
c.addChild(rectHit(h, h, 0)); // size anchor (transparent)
|