diff --git a/assets/videos/ss-takeoff-02.mp4 b/assets/videos/ss-takeoff-02.mp4 new file mode 100644 index 0000000..228ba4e Binary files /dev/null and b/assets/videos/ss-takeoff-02.mp4 differ diff --git a/data/map.json b/data/map.json index 44aab3b..7e2324a 100644 --- a/data/map.json +++ b/data/map.json @@ -281,8 +281,10 @@ "label": "ENGAGE" }, "systemConfirm": { - "_comment": "The SYSTEM tab's SET DESTINATION dialog (same shared ConfirmOverlay — the clicked object on a CHARTED star's chart; the ship isn't in that system, so no distance row / autopilot). The destination model is still being defined — CONFIRM acknowledges the pick through onSetDestination (systemId + objectId), CANCEL / scrim / ESC leave the map as it was.", + "_comment": "The SYSTEM tab's SET DESTINATION dialog (same shared ConfirmOverlay — the clicked object on a CHARTED star's chart; the ship isn't in that system, so no distance row / autopilot). The destination model is still being defined — CONFIRM acknowledges the pick through onSetDestination (systemId + objectId), CANCEL / scrim / ESC leave the map as it was. width/height: wider than the AUTOPILOT dialog — the SET DESTINATION button is a long label, so the window gives the button room and the button grows to fit its text (ConfirmOverlay.show sizes it per label).", "title": "SET DESTINATION", - "label": "SET DESTINATION" + "label": "SET DESTINATION", + "width": 520, + "height": 176 } } \ No newline at end of file diff --git a/js/ui/ConfirmOverlay.js b/js/ui/ConfirmOverlay.js index 7ea53e6..7636b8f 100644 --- a/js/ui/ConfirmOverlay.js +++ b/js/ui/ConfirmOverlay.js @@ -120,6 +120,11 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container { ); this.confirmBtn.setAlpha(0); this.cancelBtn.setAlpha(0); + // the natural widths (show() grows a button only as wide as its label + // needs — and never leaves it grown for the NEXT dialog, which may + // have a shorter label) + this.confirmBtn.style.baseWidth = this.confirmBtn.style.width; + this.cancelBtn.style.baseWidth = this.cancelBtn.style.width; this.add([this.cancelBtn, this.confirmBtn]); // Hidden dialog = inert buttons (same v4 `input.enabled` rule as the // scrim — otherwise the invisible CONFIRM/CANCEL swallow centre- @@ -130,7 +135,49 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container { this._titleDec = null; this._btnsUp = false; this._confirmUp = false; + this._layout(); // size/label land in show(); positions derive from w/h } + + /** + * Place the title, body lines and the two buttons for the CURRENT + * w/h + button sizes: the buttons sit in the panel's bottom band + * (CANCEL left, CONFIRM right — each kept inside the panel edge with + * a 20px margin, whatever its label grows it to). + */ + _layout() { + this.title.setPosition(0, -this.h / 2 + 24); + this.bodyTexts.forEach((t, i) => t.setPosition(0, -this.h / 2 + 52 + i * 17)); + const margin = 20; + const cw = this.cancelBtn.style.width; + const bw = this.confirmBtn.style.width; + const bh = this.confirmBtn.style.height; + const cy = this.h / 2 - margin - bh / 2; + this.cancelBtn.setPosition(-this.w / 2 + margin + cw / 2, cy); + this.confirmBtn.setPosition(this.w / 2 - margin - bw / 2, cy); + } + + /** + * Grow a button's panel to FIT its label (the SET DESTINATION label is + * wider than the fixed 148px the CONFIRM button was built for — the + * text used to run past the panel edge). Re-measures with the button's + * own text style, then repaints + resets the hit area. + */ + _fitBtn(btn, label, minWidth) { + const s = btn.style; + const m = this.scene.add.text(0, 0, String(label), s.textStyle); + const w = Math.max(s.baseWidth ?? s.width, minWidth, Math.ceil(m.width) + 28); + m.destroy(); + if (w === s.width) return; + s.width = w; + btn.setSize(w, s.height); + btn.panel.setInteractive({ + useHandCursor: true, + hitArea: new Phaser.Geom.Rectangle(-w / 2, -s.height / 2, w, s.height), + hitAreaCallback: (p, px, py) => Phaser.Geom.Rectangle.Contains(p, px, py), + }); + btn.paint('base'); + } + // ------------------------------------------------------------------ // Showing // ------------------------------------------------------------------ @@ -139,6 +186,9 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container { * spec: { * title, body: string[], accent?: number, * confirmLabel?, cancelLabel?, + * width?, height? — override the dialog's size for THIS show (the + * SYSTEM tab's SET DESTINATION is wider than AUTOPILOT's ENGAGE — + * its confirm label is longer); the layout re-flows for it, * onConfirm?, onCancel? — fired after the close animation lands * time — the engine time (for the open animation) * } @@ -148,6 +198,11 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container { this.onConfirm = typeof spec.onConfirm === 'function' ? spec.onConfirm : null; this.onCancel = typeof spec.onCancel === 'function' ? spec.onCancel : null; + // Per-show size (the SET DESTINATION dialog is wider than ENGAGE). + if (Number(spec.width) > 0) this.w = Number(spec.width); + if (Number(spec.height) > 0) this.h = Number(spec.height); + this.setSize(this.w, this.h); + const setBtn = (btn, label) => { const s = String(label ?? '').toUpperCase(); btn.labelText.setText(s); @@ -156,6 +211,12 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container { }; setBtn(this.confirmBtn, spec.confirmLabel ?? 'CONFIRM'); setBtn(this.cancelBtn, spec.cancelLabel ?? 'CANCEL'); + // The confirm button must FIT its label (the longest one is + // SET DESTINATION) — grow the panel, then place both in the bottom + // band, inside the panel edges. + this._fitBtn(this.confirmBtn, String(spec.confirmLabel ?? 'CONFIRM').toUpperCase(), 110); + this._fitBtn(this.cancelBtn, String(spec.cancelLabel ?? 'CANCEL').toUpperCase(), 88); + this._layout(); // Accent the confirm button's edge + hover glow. this.confirmBtn.style.stroke = this.accent; this.confirmBtn.style.neon = this.accent; diff --git a/js/ui/MapWindow.js b/js/ui/MapWindow.js index 68c3e94..1b71327 100644 --- a/js/ui/MapWindow.js +++ b/js/ui/MapWindow.js @@ -2530,6 +2530,11 @@ export class MapWindow extends Phaser.GameObjects.Container { body: [`DESTINATION — ${label}`, sysName ? `SYSTEM — ${sysName}` : ''].filter(Boolean), accent: C.neon, confirmLabel: config.get('map.systemConfirm.label', 'SET DESTINATION'), + // wider than AUTOPILOT's dialog — the SET DESTINATION label is + // long, and it gets the room (button fits the text, text fits + // the panel) + width: config.get('map.systemConfirm.width', 520), + height: config.get('map.systemConfirm.height', 176), onConfirm: () => { // the pick ripples once while the window fades out this._flash = { x: o.x + this.geo.mapX, y: o.y + this.geo.mapY, r: o.r, t0: this.scene.time.now }; @@ -2548,6 +2553,10 @@ export class MapWindow extends Phaser.GameObjects.Container { body, accent: C.amber, confirmLabel: config.get('map.confirm.label', 'ENGAGE'), + // its own (narrower) size — the dialog instance is shared with the + // wider SET DESTINATION one, so each call states its own footprint + width: config.get('map.confirm.width', 440), + height: config.get('map.confirm.height', 168), onConfirm: () => { // the pick ripples once while the window fades out this._flash = { x: o.x + this.geo.mapX, y: o.y + this.geo.mapY, r: o.r, t0: this.scene.time.now };