Move the live NET readout from the panel center onto the tab sub-bar

The pending trade's credit delta now sits at the right end of the
Buy/Sell + Buyback sub-bar, past a hairline divider — tabs AND the
credit math on one console, one glance apart (entry state dims it to
0 cr). The center column keeps Confirm/Clear (re-centered); the
buyback panel's Confirm moves up to sit just below its rows.

Knobs: data/shopui.json → preview (bar sizing, zeroColor, divider).
Docs: docs/ECONOMY.md §12 mockups + bullets follow the new layout.
Tests: dev/shop-ui.test.mjs asserts the bar's NET readout mirrors the
pending trade (+N cr while picking, 0 cr after Confirm).
This commit is contained in:
Brian Fertig 2026-09-11 22:09:49 -06:00
parent 05b6f573c3
commit 2b860cc899
4 changed files with 126 additions and 74 deletions

View File

@ -1,9 +1,9 @@
{
"_comment": "SHOP UI — the buy/sell (and buyback) screen over the shop clip (data/shopui.json). LAYOUT: the two 4×4 grids sit side by side (SELL = the player's cargo bays on the LEFT, BUY = the shop's stock on the RIGHT), a center column carries the live NET preview + Confirm/Clear, and a tab bar up top toggles Buy/Sell ↔ Buyback (Buyback is disabled until the player has SOLD something this session). The panels are semi-transparent so the shop clip shows through; items + text are full alpha. CELLS show the good's ICON only — the name + price appear in a hover TOOLTIP (→ tooltip) instead of over the icon. GEOMETRY (slot/gap/gridCols/gridRows/centerW) is primitives the code (js/ui/shop/ShopView.js) lays out against the live scene size — not pixel positions. The ECONOMY itself (prices, stock) is Economy.js + data/goods.json + data/economy.json; this file is only presentation.",
"_comment": "SHOP UI — the buy/sell (and buyback) screen over the shop clip (data/shopui.json). LAYOUT: the two 4×4 grids sit side by side (SELL = the player's cargo bays on the LEFT, BUY = the shop's stock on the RIGHT), Confirm/Clear sit in the center column, the live NET-credits readout rides on the TAB SUB-BAR (right of the Buy/Sell + Buyback buttons, past a hairline divider), and the tab sub-bar toggles Buy/Sell ↔ Buyback (Buyback is disabled until the player has SOLD something this session). The panels are semi-transparent so the shop clip shows through; items + text are full alpha. CELLS show the good's ICON only — the name + price appear in a hover TOOLTIP (→ tooltip) instead of over the icon. GEOMETRY (slot/gap/gridCols/gridRows/centerW) is primitives the code (js/ui/shop/ShopView.js) lays out against the live scene size — not pixel positions. The ECONOMY itself (prices, stock) is Economy.js + data/goods.json + data/economy.json; this file is only presentation.",
"enabled": true,
"tabs": {
"_comment": "The tab sub-bar (two MenuButtons, the deck's cyberpunk style — js/ui/MenuButton.js). Buy/Sell opens the trade panel; Buyback opens the buyback panel (goods sold this session, session-local only). Size/palette come from data/save.json → subBar + colors (the Menu sub-bar recipe); this section just holds the labels + Buy/Sell accent. The sub-bar rests above the command deck, anchored to the deck's Shop button (SurfaceScene.shopAnchor).",
"_comment": "The tab sub-bar (two MenuButtons, the deck's cyberpunk style — js/ui/MenuButton.js), EXTENDED at its right end by the live NET readout (→ preview) past a hairline divider. Buy/Sell opens the trade panel; Buyback opens the buyback panel (goods sold this session, session-local only). Size/palette come from data/save.json → subBar + colors (the Menu sub-bar recipe); this section just holds the labels + Buy/Sell accent. The sub-bar rests above the command deck, anchored to the deck's Shop button (SurfaceScene.shopAnchor).",
"buySell": "Buy/Sell",
"buyback": "Buyback",
"w": 184, "h": 46, "gap": 14,
@ -61,13 +61,16 @@
},
"preview": {
"_comment": "The live NET-credits readout in the center column. Positive (you're up) = green, negative (you'd spend past your credits / it's a net spend) = the value shown; the Confirm button is disabled while the trade is invalid (can't go negative, hold overflows).",
"_comment": "The live NET-credits readout — on the TAB SUB-BAR, right of the Buy/Sell + Buyback buttons, past a hairline divider (→ divider): tabs AND the pending trade's credit delta on one console, one glance apart. Positive (you're up) = green, negative (a net spend) = the value shown in magenta, zero = dim (entry state / nothing selected). The value is right-aligned at the bar's end so longer numbers (+1,240 cr) never resize the bar. Confirm is disabled while the trade is invalid (can't go negative, hold overflows).",
"label": "NET",
"labelColor": "#7d92c4",
"labelFontSize": 13,
"valueFontSize": 34,
"labelFontSize": 12,
"valueFontSize": 22,
"zeroColor": "#7d92c4",
"posColor": "#7ce8a4",
"negColor": "#ff2d6f"
"negColor": "#ff2d6f",
"gap": 12,
"divider": { "width": 2, "height": 26, "color": "#22405f", "alpha": 0.9 }
},
"buttons": {

View File

@ -333,12 +333,19 @@ console.log('\n== SELL — check a bay, Confirm earns credits + empties the hold
assert.equal(pv.net, sellA, '… net = the sale (nothing bought)');
assert.equal(pv.valid, true, '… and it\u2019s legal');
// The live NET readout rides on the TAB SUB-BAR (not the center column) —
// it mirrors the pending trade's credit delta, one glance from the tabs.
assert.ok(view._netValue, 'the sub-bar carries the NET value readout');
assert.ok(view._netLabel, '… with the NET label');
assert.equal(view._netValue.text, `+${sellA} cr`, '… it shows the pending +cr');
const before = ship.credits;
assert.equal(view.confirm(), true, 'Confirm goes through');
assert.equal(ship.credits, before + sellA, 'credits rise by the sale');
assert.deepEqual(ship.cargo.snapshot(), [B], 'A leaves the hold, B stays');
assert.equal(view.isBuybackEnabled(), true, '… and A is now buyback-eligible');
assert.equal(gs.refreshed, 1, 'the credits HUD was refreshed');
assert.equal(view._netValue.text, '0 cr', '… and the bar’s NET settles back to 0 cr');
}
console.log('SELL: OK');

View File

@ -298,7 +298,8 @@ existing `data/actionbar.json` slot); the shop clip is already live, and the scr
it. It's one component — `js/ui/shop/ShopView.js` — composing two `TradeGrid`s.
**Entry state (showcase the clip).** When you first open the shop, **only the tab sub-bar**
is up — the cyberpunk **MenuButton** pair `[ BUY/SELL | BUYBACK ]` (Buyback grayed out), the
is up — the cyberpunk **MenuButton** pair `[ BUY/SELL | BUYBACK ]` (Buyback grayed out),
extended at its right end by the live **NET** readout (dim `0 cr` until you start picking), the
same style + placement as the deck's **Menu** sub-bar (`js/ui/MenuSubBar.js`). It rests **above
the command deck**, right-aligned to the deck's **Shop** button — so it reads as the same family
of UI as the Menu drawer. The screen stays open on the shop video so the clip plays. Clicking
@ -308,24 +309,23 @@ of UI as the Menu drawer. The screen stays open on the shop video so the clip pl
# first click — shop video + the tab sub-bar (rests above the deck):
┌─────────────────────────────────────────────────────────────────┐
│ (clip plays) │
│ ┌─────────────────────────┐ │
│ │ BUY/SELL │ BUYBACK │ ← sub-bar │
│ └─────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ BUY/SELL │ BUYBACK │ NET 0 cr │ ← sub-bar │
│ └──────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ SHOP │ BUILD │ SHIP │ QUESTS │ TAKE OFF │ MENU ← command deck│
└─────────────────────────────────────────────────────────────────┘
# after clicking Buy/Sell — the trade panel reveals (middle of the screen):
┌─────────────────────────────────────────────────────────────────┐
│ ┌SELL 4×4┐ NET +420 cr ┌BUY 4×4┐ │
│ │ bays │ (green ≥ 0 / │ stock │ │
│ │(cargo) │ red < 0) │(12–16) │ │
│ └────────┘ [ CONFIRM ] └────────┘ │
│ [ CLEAR ] │
│ ┌SELL 4×4┐ [ CONFIRM ] ┌BUY 4×4┐ │
│ │ bays │ [ CLEAR ] │ stock │ │
│ │(cargo) │ │(12–16) │ │
│ └────────┘ └────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────┐ │
│ │ BUY/SELL │ BUYBACK │ ← sub-bar │
│ └─────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ BUY/SELL │ BUYBACK │ NET +420 cr │ ← sub-bar │
│ └──────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ SHOP │ BUILD │ SHIP │ QUESTS │ TAKE OFF │ MENU ← command deck│
└─────────────────────────────────────────────────────────────────┘
@ -335,9 +335,11 @@ of UI as the Menu drawer. The screen stays open on the shop video so the clip pl
cyberpunk style: cut-corner panel, neon edge, hover sweep, press punch) in a console-blue
panel that rests above the command deck, anchored to the Shop button — the exact recipe of
the Menu sub-bar. Buyback is **disabled** (grayed) until you've sold something this visit.
- **Confirm / Clear** — the same `MenuButton` style; Confirm is **disabled** (grayed) while
the trade is invalid (can't go negative, hold would overflow), and both appear once you start
picking.
The bar is **extended** past a hairline divider to carry the live **NET** readout —
one glance sees the tabs *and* the pending trade's credit delta.
- **Confirm / Clear** — the same `MenuButton` style, in the center column between the grids;
Confirm is **disabled** (grayed) while the trade is invalid (can't go negative, hold would
overflow), and both appear once you start picking.
- **SELL (left)** — your **cargo bays**. 16 cells (the fixed grid); the first `cargoHold`
are live bays (filled with a good, or empty), the rest are **locked** (the hold's upgrade
@ -345,8 +347,9 @@ of UI as the Menu drawer. The screen stays open on the shop video so the clip pl
quantity is simply the number of its bays that are checked.
- **BUY (right)** — the shop's **stock** (12–16 distinct goods from `Economy.shopFor`). Each
cell has a **−/qty/+ stepper**; buy as many as you like (capped by credits + hold room).
- **NET preview (center)** — the live credit delta. Green when you're up (a net sale), red when
you'd spend. Updates as you check bays / step goods.
- **NET readout (tab sub-bar)** — the live credit delta, right of the tabs past the divider.
Green when you're up (a net sale), red when you'd spend, dim at zero. Updates as you check
bays / step goods (it's the same `preview()` the Confirm button is gated on).
- **SELL → BUY is atomic** — check bays to sell *and* step goods to buy, then one Confirm
executes it: credits `+ sale − buy`, cargo `− sold + bought`, in that order (free room
first). Selling to free a bay for a buy lands in the same Confirm.

View File

@ -1,22 +1,24 @@
/**
* ShopView — the buy/sell (and buyback) screen over the shop clip video.
*
* ┌─────────────────────────────────────────────────────────────┐
* │ [ Buy/Sell | Buyback ] ← tabs │
* │ │
* │ ┌SELL 4×4┐ ┌ NET ┐ ┌BUY 4×4┐ │
* │ │ bays │ │ +420 │ │ stock │ │
* │ │ (cargo)│ │ cr │ │ (12–16)│ │
* │ └────────┘ │[Conf]│ └────────┘ │
* │ │[Clear]│ │
* └─────────────────────────────────────────────────────────────┘
* ┌────────────────────────────────────────────────────────────┐
* │ ┌SELL 4×4┐ [ Confirm ] ┌BUY 4×4┐ │
* │ │ bays │ [ Clear ] │ stock │ │
* │ │ (cargo)│ │ (12–16)│ │
* │ └────────┘ └────────┘ │
* └────────────────────────────────────────────────────────────┘
* ┌────────────────────────────────────────────────────────────┐
* │ BUY/SELL BUYBACK │ NET +420 cr ← tab sub-bar │
* └────────────────────────────────────────────────────────────┘
*
* The two sides are TradeGrids (js/ui/shop/TradeGrid.js):
* · SELL (left) — the player's cargo bays. Tap a filled bay to check it
* off (a bay = 1 unit). Locked bays are the hold's upgrade room.
* · BUY (right) — the shop's stock, each with a −/qty/+ stepper.
* The live NET preview (green ≥ 0, red < 0) and Confirm/Clear live in the
* center; Confirm is disabled while the trade is invalid (can't go negative,
* The live NET preview (green ≥ 0, red < 0) sits on the TAB SUB-BAR, right
* of the Buy/Sell + Buyback buttons past a hairline divider (tabs AND the
* pending trade's credit math on one console); Confirm/Clear stay in the
* center. Confirm is disabled while the trade is invalid (can't go negative,
* hold would overflow). SELL→BUY happens atomically (sell to free room for a
* buy).
*
@ -83,7 +85,8 @@ export class ShopView extends Phaser.GameObjects.Container {
this.sellGrid = null;
this.buyGrid = null;
this._content = null; // the active content container
this._previewText = null;
this._netLabel = null; // the sub-bar NET label (persistent)
this._netValue = null; // the sub-bar NET value (live)
this._confirmBtn = null;
this._clearBtn = null;
this._tabBuyback = null;
@ -132,6 +135,12 @@ export class ShopView extends Phaser.GameObjects.Container {
// signature top rail, holding cyberpunk MenuButtons. It rests ABOVE the
// command deck, right-aligned to the deck's "Shop" button (this.anchor),
// so it reads as the same family of UI as the Menu drawer.
//
// EXTENDED (data/shopui.json → preview): the live NET credit readout —
// the pending trade's credit delta — sits at the bar's right end past a
// hairline divider. Tabs AND the credit math on ONE console: the readout
// is one glance away from the tabs at every moment (entry state included,
// where it dims to "0 cr").
const a = this.anchor;
const H = config.get('save.subBar.height', 58);
const pad = config.get('save.subBar.padding', 16);
@ -139,6 +148,7 @@ export class ShopView extends Phaser.GameObjects.Container {
const tabs = config.get('shopui.tabs', {});
const c = config.section('save.colors', {});
const btnCfg = config.section('save.subBar.button', {});
const pv = config.section('shopui.preview', {});
const items = [
{ id: 'trade', label: tabs.buySell ?? 'Buy/Sell', accent: tabs.activeAccent ?? c.neon ?? '#00e5ff' },
@ -165,10 +175,31 @@ export class ShopView extends Phaser.GameObjects.Container {
const btns = items.map(mkBtn);
const widths = btns.map((b) => b.style?.width ?? 120);
const W = Math.min(
this._W() - 24,
pad * 2 + widths.reduce((s, w) => s + w, 0) + gap * (widths.length - 1),
);
const tabsW = widths.reduce((s, w) => s + w, 0) + gap * (widths.length - 1);
// The NET readout — created BEFORE the panel body so its REAL width feeds
// the bar width (a headless fake without text metrics falls back to an
// estimate). The value is RIGHT-aligned at the bar's end, so longer
// numbers ("+1,240 cr") never need the bar to grow.
const labelStr = String(pv.label ?? 'NET');
const valueFont = Math.max(8, Number(pv.valueFontSize) || 22);
this._netLabel = this._makeText(0, -H / 2, labelStr, {
fontSize: `${Math.max(8, Number(pv.labelFontSize) || 12)}px`,
color: toCss(pv.labelColor ?? c.dim ?? '#7d92c4'),
align: 'left',
});
this._netValue = this._makeText(0, -H / 2, '0 cr', {
fontSize: `${valueFont}px`,
color: toCss(pv.zeroColor ?? pv.labelColor ?? '#7d92c4'),
align: 'left',
});
const labelW = (Number(this._netLabel?.width) > 0 ? this._netLabel.width : labelStr.length * 8) || 30;
const valueW = (Number(this._netValue?.width) > 0 ? this._netValue.width : 4 * valueFont * 0.6) || 40;
const divW = Math.max(1, Number(pv.divider?.width) || 2);
const divH = Math.max(10, Number(pv.divider?.height) || Math.round(H * 0.45));
// Row: [ BUY/SELL ][ BUYBACK ] │ NET value
const W = Math.min(this._W() - 24, pad * 2 + tabsW + gap + divW + gap + labelW + gap + valueW);
// Position: bottom edge on the deck's top rail (a.y), right edge on the
// Shop button's right edge — the MenuSubBar recipe. The sub-bar is a
@ -187,15 +218,18 @@ export class ShopView extends Phaser.GameObjects.Container {
this.add?.(bar);
// The body FIRST (v4 paints children in list order — the opaque panel
// fill must come before the buttons or it would cover them).
// fill must come before the buttons or it would cover them). The hairline
// divider between the tabs and the NET block is part of the body, so it
// paints under both.
const divX = -W / 2 + pad + tabsW + gap + divW / 2;
const bodyG = this.scene.add?.graphics?.();
if (bodyG) {
bodyG.setScrollFactor?.(0);
bar.add?.(bodyG);
this._drawSubBarBody(bodyG, W, H, c);
this._drawSubBarBody(bodyG, W, H, c, divX, divH);
}
// The buttons LAST (above the body), centered in the row.
// The buttons LAST (above the body), left-anchored in the row.
let bx = -W / 2 + pad + widths[0] / 2;
items.forEach((it, i) => {
const btn = btns[i];
@ -206,12 +240,23 @@ export class ShopView extends Phaser.GameObjects.Container {
bx += widths[i] / 2 + gap + (i < items.length - 1 ? widths[i + 1] / 2 : 0);
});
// The NET block: the label right after the divider, the value right-
// aligned to the bar's end (so a growing number never shifts the label).
this._netLabel.setPosition?.(divX + divW / 2 + gap + labelW / 2, -H / 2);
this._netValue.setPosition?.(W / 2 - pad - valueW / 2, -H / 2);
bar.add?.(this._netLabel);
bar.add?.(this._netValue);
// Buyback starts grayed (disabled) until a sale exists this visit.
this._refreshTabs();
}
/** The sub-bar panel body (the MenuSubBar recipe at full height). */
_drawSubBarBody(g, W, H, c) {
/**
* The sub-bar panel body (the MenuSubBar recipe at full height).
* `divX`/`divH` (optional) add the hairline divider between the tabs and
* the NET readout — drawn as body so it paints under the buttons + text.
*/
_drawSubBarBody(g, W, H, c, divX, divH) {
if (!g || typeof g.clear !== 'function') return;
const panelTop = toColor(c.panelTop ?? '#0e1930');
const panelBottom = toColor(c.panelBottom ?? '#050912');
@ -238,6 +283,10 @@ export class ShopView extends Phaser.GameObjects.Container {
g.fillStyle(ink, 0.8); g.fillRect(-W / 2 + cut, -H + 0.5, W - cut * 2, 0.8);
g.fillStyle(magenta, 0.5); g.fillRect(-W / 2 + cut, -H + rail, W - cut * 2, 1);
g.fillStyle(magenta, 0.35); g.fillRect(-W / 2, -1.5, W, 1.5); // seam
if (typeof divX === 'number' && divH > 0) {
g.fillStyle(toColor(c.border ?? '#22405f'), 0.9);
g.fillRect(divX - 1, -H / 2 - divH / 2, 2, divH); // tabs │ NET
}
}
}
@ -298,27 +347,17 @@ export class ShopView extends Phaser.GameObjects.Container {
content.add?.(sellGrid);
content.add?.(buyGrid);
// center column: NET preview + Confirm + Clear — vertically centered with
// the grids (the NET above, the buttons below), so the whole panel is one
// cohesive block near the top of the screen.
// center column: Confirm + Clear — vertically centered with the grids,
// so the whole panel is one cohesive block near the top of the screen.
// (The live NET preview now rides on the tab sub-bar — see _buildTabs.)
const cx = leftX + sellGrid.width + centerW / 2;
const cy = this.topY + gridH / 2;
this._previewLabel = this._makeText(cx, cy - 52, config.get('shopui.preview.label', 'NET'), {
fontSize: `${config.get('shopui.preview.labelFontSize', 13)}px`,
color: config.get('shopui.preview.labelColor', '#7d92c4'), align: 'center',
});
this._previewText = this._makeText(cx, cy - 14, 'NET 0 cr', {
fontSize: `${config.get('shopui.preview.valueFontSize', 34)}px`,
color: config.get('shopui.preview.zeroColor', '#7d92c4'), align: 'center',
});
content.add?.(this._previewLabel);
content.add?.(this._previewText);
// Confirm / Clear — the same cyberpunk MenuButton as the tab bar.
const c = config.section('save.colors', {});
const btnCfg = config.section('save.subBar.button', {});
this._confirmBtn = new MenuButton(
this.scene, cx, cy + 34, config.get('shopui.buttons.confirmLabel', 'Confirm'), () => this.confirm(),
this.scene, cx, cy + 16, config.get('shopui.buttons.confirmLabel', 'Confirm'), () => this.confirm(),
{
screenFixed: true,
fontSize: btnCfg.fontSize ?? 13, paddingX: 26, paddingY: 12,
@ -331,7 +370,7 @@ export class ShopView extends Phaser.GameObjects.Container {
content.add?.(this._confirmBtn);
this._clearBtn = new MenuButton(
this.scene, cx, cy + 104, config.get('shopui.buttons.clearLabel', 'Clear'), () => this.clear(),
this.scene, cx, cy + 86, config.get('shopui.buttons.clearLabel', 'Clear'), () => this.clear(),
{
screenFixed: true,
fontSize: btnCfg.fontSize ?? 13, paddingX: 26, paddingY: 12,
@ -390,16 +429,11 @@ export class ShopView extends Phaser.GameObjects.Container {
}
const cy = y + 24;
this._previewLabel = this._makeText(cx, cy - 8, config.get('shopui.preview.label', 'NET'), {
fontSize: `${config.get('shopui.preview.labelFontSize', 13)}px`, color: config.get('shopui.preview.labelColor', '#7d92c4'), align: 'center' });
content.add?.(this._previewLabel);
this._previewText = this._makeText(cx, cy + 22, 'NET 0 cr', {
fontSize: `${config.get('shopui.preview.valueFontSize', 34)}px`, color: config.get('shopui.preview.zeroColor', '#7d92c4'), align: 'center' });
content.add?.(this._previewText);
// (The live NET readout rides on the tab sub-bar — see _buildTabs.)
const bw = config.get('shopui.buttons.w', 158), bh = config.get('shopui.buttons.h', 54);
this._confirmBtn = this._makeButton(config.get('shopui.buttons.confirmLabel', 'Confirm'), bw, bh, config.get('shopui.buttons.confirmAccent', '#7ce8a4'));
this._confirmBtn.setPosition(cx, cy + 92);
this._confirmBtn.setPosition(cx, cy + 40);
this._confirmBtn.tap(() => this.confirm());
this._confirmBtn.setAlpha(0);
content.add?.(this._confirmBtn.container);
@ -531,13 +565,16 @@ export class ShopView extends Phaser.GameObjects.Container {
_syncPreview() {
const pv = this.preview();
const anySel = this._hasAnySelection();
if (this._previewText && typeof this._previewText.setText === 'function') {
const sign = pv.net >= 0 ? '+' : '';
this._previewText.setText(`NET ${sign}${Math.round(pv.net)} cr`);
const col = pv.net === 0
// The live NET readout lives on the tab sub-bar (this._netValue) — one
// glance sees the tabs AND the pending trade's credit delta. Right-
// aligned, so a growing number never shifts the "NET" label.
if (this._netValue && typeof this._netValue.setText === 'function') {
const n = Math.round(pv.net);
this._netValue.setText(n === 0 ? '0 cr' : `${n > 0 ? '+' : ''}${n} cr`);
const col = n === 0
? config.get('shopui.preview.zeroColor', '#7d92c4')
: (pv.net > 0 ? config.get('shopui.preview.posColor', '#7ce8a4') : config.get('shopui.preview.negColor', '#ff2d6f'));
if (typeof this._previewText.setColor === 'function') this._previewText.setColor(col);
: (n > 0 ? config.get('shopui.preview.posColor', '#7ce8a4') : config.get('shopui.preview.negColor', '#ff2d6f'));
if (typeof this._netValue.setColor === 'function') this._netValue.setColor(col);
}
const confirmOn = anySel;
const confirmEnabled = anySel && pv.valid;
@ -584,8 +621,6 @@ export class ShopView extends Phaser.GameObjects.Container {
this.buyGrid?.destroy?.();
this.sellGrid = null;
this.buyGrid = null;
this._previewText = null;
this._previewLabel = null;
this._confirmBtn = null;
this._clearBtn = null;
}
@ -594,6 +629,10 @@ export class ShopView extends Phaser.GameObjects.Container {
this._destroyContent();
this._tabTrade?.destroy?.();
this._tabBuyback?.destroy?.();
this._netLabel?.destroy?.();
this._netValue?.destroy?.();
this._netLabel = null;
this._netValue = null;
if (typeof super.destroy === 'function') super.destroy();
}