diff --git a/data/shopui.json b/data/shopui.json index 747d7f8..61c4a4c 100644 --- a/data/shopui.json +++ b/data/shopui.json @@ -1,5 +1,5 @@ { - "_comment": "SHOP UI — the buy/sell (and buyback) screen over the shop clip (data/shopui.json). LAYOUT: the SELL grid (the player's cargo bays) sits on the LEFT, the BUY list (the shop's stock, as a scrollable list of wide rows) RIGHT NEXT TO it with just a bit of padding between (grid.centerW) — the panel is pure selection. The trade ACTIONS (Confirm/Clear) + the live NET-credits readout ride on the TAB SUB-BAR (→ tabs/preview/buttons), 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. SELL CELLS show the good's ICON only — the name + price appear in a hover TOOLTIP (→ tooltip) instead of over the icon. BUY ROWS carry their name + description (wraps to two lines, never over the price/stepper) + price INLINE (no tooltip — the row is wide enough). GEOMETRY (slot/gap/grid/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 SELL grid (the player's cargo bays) sits on the LEFT, the BUY list (the shop's stock, as a scrollable list of wide rows) RIGHT NEXT TO it with just a bit of padding between (grid.centerW) — the panel is pure selection. The panel SPANS the screen: the SELL grid's left edge is flush with the planet text (data/landing.json → landing.hud.marginX), and the BUY list stretches to the same right padding. The trade ACTIONS (Confirm/Clear) + the live NET-credits readout ride on the TAB SUB-BAR (→ tabs/preview/buttons), 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. SELL CELLS show the good's ICON only — the name + price appear in a hover TOOLTIP (→ tooltip) instead of over the icon. BUY ROWS carry their name + description (wraps to two lines, never over the price/stepper) + price INLINE (no tooltip — the row is wide enough). GEOMETRY (slot/gap/grid/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": { @@ -40,7 +40,7 @@ }, "buyList": { - "_comment": "The BUY side (js/ui/shop/BuyList.js): the shop's stock as a SCROLLABLE LIST of WIDE ROWS — [icon] NAME / description (the good's flavor, data/goods.json, wraps to up to desc.maxLines lines and NEVER over the price/stepper) … PRICE, − qty +. Sized to the SELL grid's height so the panels align top AND bottom; the list scrolls (drag on the body / mouse wheel) when the stock outgrows the viewport — rows outside it are hidden (row-based clipping; this Phaser build has no camera/mask clip). A row in the pending trade (qty > 0) gets the accent border + lit qty. `rowH` is tall enough for the two-line description. `pad` insets rows from the panel edge; the title bar reuses panel.title.", + "_comment": "The BUY side (js/ui/shop/BuyList.js): the shop's stock as a SCROLLABLE LIST of WIDE ROWS — [icon] NAME / description (the good's flavor, data/goods.json, wraps to up to desc.maxLines lines and NEVER over the price/stepper) … PRICE, − qty +. Live, ShopView STRETCHES the list from just past the SELL grid out to the right padding (data/landing.json → landing.hud.marginX) — `w` is only the fallback when no width is passed (headless/dev). Sized to the SELL grid's height so the panels align top AND bottom; the list scrolls (drag on the body / mouse wheel) when the stock outgrows the viewport — rows outside it are hidden (row-based clipping; this Phaser build has no camera/mask clip). A row in the pending trade (qty > 0) gets the accent border + lit qty. `rowH` is tall enough for the two-line description. `pad` insets rows from the panel edge; the title bar reuses panel.title.", "w": 560, "pad": 14, "rowH": 72, diff --git a/dev/shop-ui.test.mjs b/dev/shop-ui.test.mjs index f7c7579..b81ff11 100644 --- a/dev/shop-ui.test.mjs +++ b/dev/shop-ui.test.mjs @@ -266,14 +266,23 @@ console.log('\n== INTERACTION — icons bound per good + taps carry a hitAreaCal assert.equal(typeof view.buyList.rows[0].objs.plus?._interactive?.hitAreaCallback, 'function', 'buy row + tap has a hitAreaCallback'); // The two panels must settle at DIFFERENT layout positions — the slide-in - // tween targets each grid's set position (the ShopView centers - // [SELL] [center] [BUY]). The old bug tweened to x:0, stacking both panels - // at the left edge so the cargo/sell panel sat hidden behind the buy panel. + // tween targets each grid's set position. The old bug tweened to x:0, + // stacking both panels at the left edge so the cargo/sell panel sat hidden + // behind the buy panel. assert.ok(view.sellGrid.x > 0, `sell panel settles at a positive layout x (got ${view.sellGrid.x})`); assert.ok(view.buyList.x > view.sellGrid.x, `buy panel is to the right of the sell panel (sell ${view.sellGrid.x} < buy ${view.buyList.x})`); assert.notEqual(view.sellGrid.x, view.buyList.x, 'the panels do not overlap at the origin'); assert.ok(view.buyList.width > view.sellGrid.width, 'the BUY list is the wide panel (rows, not 4×4 cells)'); + // The panel spans the screen like the HUD: the SELL grid's LEFT edge is + // the planet text's left edge (landing.hud.marginX), and the BUY list's + // RIGHT edge is the SAME padding in from the screen's right edge. + const leftPad = Number(config.get('landing.hud.marginX')) || 24; + const W = 1280; // the stub scene's width + assert.equal(view.sellGrid.x, leftPad, `SELL's left edge is flush with the planet text's left edge (x=${view.sellGrid.x}, hud margin=${leftPad})`); + const rightEdge = view.buyList.x + view.buyList.width; + assert.ok(Math.abs(rightEdge - (W - leftPad)) < 1, `BUY's right edge is the same padding in from the right (rightEdge=${rightEdge}, want ${W - leftPad})`); + // The two panels sit NEXT TO each other with just a bit of padding between // (grid.centerW — the actions moved onto the sub-bar, so there's no center // column anymore). diff --git a/docs/ECONOMY.md b/docs/ECONOMY.md index 23a1b4c..575bde6 100644 --- a/docs/ECONOMY.md +++ b/docs/ECONOMY.md @@ -308,7 +308,10 @@ placement as the deck's **Menu** sub-bar (`js/ui/MenuSubBar.js`). It rests **abo 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 **Buy/Sell** reveals the trade panel in the middle of the screen: the **SELL grid** and the -**BUY list** side by side with just a bit of padding between (the panels slide in). +**BUY list** side by side with just a bit of padding between (the panels slide in). The +panel **spans the screen** — the SELL grid's left edge is **flush with the planet name** +(the HUD's left margin), and the BUY list stretches to the **same padding** in from the +right edge. ``` # first click — shop video + the tab sub-bar (rests above the deck): diff --git a/js/ui/shop/ShopView.js b/js/ui/shop/ShopView.js index 77e5f17..a17814d 100644 --- a/js/ui/shop/ShopView.js +++ b/js/ui/shop/ShopView.js @@ -345,6 +345,12 @@ export class ShopView extends Phaser.GameObjects.Container { // see _buildTabs — so the panel is pure selection). const centerW = grid.centerW ?? 16; const W = this._W(); + // The panel's LEFT edge = the surface HUD's planet text's LEFT edge — + // the same margin key (data/landing.json → landing.hud.marginX) the + // name/line are drawn at (origin 0,0), so the cargo grid lines up + // vertically with the planet name above it. The BUY list then STRETCHES + // to the right, ending at the SAME padding from the screen's right edge. + const leftPad = Math.max(8, Number(config.get('landing.hud.marginX')) || 24); const sellGrid = new TradeGrid(this.scene, { side: 'sell', maxSlots: this.maxSlots, capacity: this.capacity, @@ -355,26 +361,26 @@ export class ShopView extends Phaser.GameObjects.Container { // name + description + price + −/qty/+ stepper). Sized to the SELL // grid's height so the two panels align top AND bottom; the list scrolls // when the stock outgrows the viewport (it always does — 8–16 rows). - const buyW = Math.min( - Number(config.get('shopui.buyList.w')) || 560, - Math.max(440, W - 24 - sellGrid.width - centerW), - ); + // Width: from just past the SELL grid out to the right padding — the + // wide row gets all the room (the 440 floor is the narrow-window + // fallback, where stretching to the right edge isn't possible). + const buyW = Math.max(440, W - leftPad - (leftPad + sellGrid.width + centerW)); const buyList = new BuyList(this.scene, { width: buyW, height: sellGrid.height }); this.sellGrid = sellGrid; this.buyList = buyList; // Layout: [SELL grid] [gap] [BUY list]. The panel's TOP edge sits at - // this.topY — just under the surface HUD's planet text (upper-left) — so - // the whole panel reads as one unit near the top of the screen (not - // hanging down to the deck/sub-bar). + // this.topY — just under the surface HUD's planet text (upper-left) — + // so the whole panel reads as one unit near the top of the screen (not + // hanging down to the deck/sub-bar); its LEFT edge is the planet text's + // left edge (leftPad), and the BUY list ends at the right padding. // // IMPORTANT: a TradeGrid/BuyList lays its plate out in local (0,0)→(w,h) // and a Container's (x,y) is a pure TRANSLATION of that local space // (verified with dev/cbox-test.html) — so the panel's position IS its // top-left corner. Treating it as a centered origin (topY + h/2) drops // the panel half a grid lower and clips the bottom rows. - const totalW = sellGrid.width + centerW + buyList.width; - const leftX = Math.max(8, (W - totalW) / 2); + const leftX = leftPad; sellGrid.setPosition(leftX, this.topY); const rightX = leftX + sellGrid.width + centerW; buyList.setPosition(rightX, this.topY);