diff --git a/data/actionbar.json b/data/actionbar.json index 7483ed0..7dc48de 100644 --- a/data/actionbar.json +++ b/data/actionbar.json @@ -20,9 +20,9 @@ { "id": "menu", "label": "Menu", "accent": "#ff2d6f" } ], "surface": { - "_comment": "Deck layout for the surface (SurfaceScene): `research` is replaced by `shop` (no-op for now) and `takeoff` sits to the left of `menu`. SurfaceScene passes this list to ActionBar via its `buttons` option; everything else (height, colors, animation) is shared with the flight deck.", + "_comment": "Deck layout for the surface (SurfaceScene): `research` is replaced by `shop` (toggles the world's shop clip — while the clip is up the slot relabels to `labelActive`, and pressing it swaps back) and `takeoff` sits to the left of `menu`. SurfaceScene passes this list to ActionBar via its `buttons` option; everything else (height, colors, animation) is shared with the flight deck.", "buttons": [ - { "id": "shop", "label": "Shop", "accent": "#00e5ff" }, + { "id": "shop", "label": "Shop", "labelActive": "Leave Shop", "accent": "#00e5ff" }, { "id": "build", "label": "Build", "accent": "#ffc94d" }, { "id": "ship", "label": "Ship", "accent": "#7ce8a4" }, { "id": null, "label": null }, diff --git a/data/landing.json b/data/landing.json index 2a5f952..7230eee 100644 --- a/data/landing.json +++ b/data/landing.json @@ -6,6 +6,9 @@ "", "`land` — the full-screen one-shot clip played when the ship lands.", "`surface` — the looping clip shown on the surface deck behind the action bar.", + "`shop` — the looping clip the SHOP button swaps in over the surface", + " clip (press SHOP again to swap back). null/missing = no shop", + " on that world (a console note instead of a clip).", "`takeoff` — the full-screen one-shot clip played on TAKE OFF, before the", " flight world resumes. A null/missing entry takes off instantly.", "", @@ -37,11 +40,11 @@ "lineStrokeWidth": 2 }, "videos": [ - { "land": "terran-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-01.mp4" }, - { "land": "terran-land-02.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-02.mp4" }, - { "land": "terran-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-01.mp4" }, - { "land": "gasgiant-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-01.mp4" }, - { "land": "gasgiant-land-02.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-02.mp4" }, - { "land": "gasgiant-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-01.mp4" } + { "land": "terran-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-01.mp4", "shop": "terran-shop-01.mp4" }, + { "land": "terran-land-02.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-02.mp4", "shop": "terran-shop-01.mp4" }, + { "land": "terran-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-01.mp4", "shop": "terran-shop-01.mp4" }, + { "land": "gasgiant-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-01.mp4", "shop": "terran-shop-01.mp4" }, + { "land": "gasgiant-land-02.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-02.mp4", "shop": "terran-shop-01.mp4" }, + { "land": "gasgiant-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-01.mp4", "shop": "terran-shop-01.mp4" } ] } diff --git a/js/scenes/SurfaceScene.js b/js/scenes/SurfaceScene.js index 38aeb0b..8b22ddb 100644 --- a/js/scenes/SurfaceScene.js +++ b/js/scenes/SurfaceScene.js @@ -18,8 +18,9 @@ import { SaveManager } from '../save/SaveManager.js'; * plate. No UI during the flight down. * 2. SURFACE — the clip is swapped for the looping world clip (`surface`), * and the command deck appears: the GameScene deck with Research - * replaced by SHOP (a seam for now) and TAKE OFF left of MENU - * (data/actionbar.json → surface.buttons). Menu keeps the GameScene's + * replaced by SHOP (swaps the loop for the world's `shop` clip — + * e.g. terran-shop-01.mp4 — press again to swap back) and TAKE OFF + * left of MENU (data/actionbar.json → surface.buttons). Menu keeps the GameScene's * save sub-bar — Save/Load operate on the paused GameScene's state * (SavePanel `stateScene`). While the clip loops, the planet's name * decodes in at the upper-left (Ethnocentric, the menu title's shade, @@ -36,7 +37,9 @@ import { SaveManager } from '../save/SaveManager.js'; * giants, per data/planets.json → frames), so a planet always lands with * the clip matching the art it is drawn with. Missing clips fall back to * the existing stubs in data/landing.json; a broken/missing file skips - * straight to the surface instead of stranding the player. + * straight to the surface instead of stranding the player. SHOP swaps the + * surface loop for the frame's `shop` clip (null on worlds without one — + * a console note instead). */ export class SurfaceScene extends Phaser.Scene { constructor() { @@ -67,6 +70,10 @@ export class SurfaceScene extends Phaser.Scene { this.takeoffKey = null; this.takeoffVideo = null; this.takeoffDone = false; // finishTakeoff idempotency ('complete' + a guard can both fire) + this.shopKey = null; + this.shopVideo = null; // the SHOP clip (created on first press, then paused/resumed) + this.shopMode = false; // true while the shop clip is on screen (SHOP toggles it) + this.noteG = null; // console-note glyphs (consoleNote) } /** Pick this world's clips (data/landing.json) and queue them. */ @@ -75,12 +82,15 @@ export class SurfaceScene extends Phaser.Scene { this.landKey = `__surf_land_${this.planetFrame}`; this.surfaceKey = `__surf_loop_${this.planetFrame}`; this.takeoffKey = `__surf_takeoff_${this.planetFrame}`; + this.shopKey = `__surf_shop_${this.planetFrame}`; this.landUrl = this.resolveUrl(entry.land ?? null); this.surfaceUrl = this.resolveUrl(entry.surface ?? null); this.takeoffUrl = this.resolveUrl(entry.takeoff ?? null); + this.shopUrl = this.resolveUrl(entry.shop ?? null); if (this.landUrl) this.load.video(this.landKey, this.landUrl); if (this.surfaceUrl) this.load.video(this.surfaceKey, this.surfaceUrl); if (this.takeoffUrl) this.load.video(this.takeoffKey, this.takeoffUrl); + if (this.shopUrl) this.load.video(this.shopKey, this.shopUrl); // The surface's music loop (data/music.json → frames[planets.png frame]) // — the same key as the videos, so a world hums the track matching the @@ -190,6 +200,141 @@ export class SurfaceScene extends Phaser.Scene { this.buildDeck(); } + // ------------------------------------------------------------------ + // Stage 2b — the shop clip (SHOP swaps the loop, and back) + // ------------------------------------------------------------------ + + /** + * SHOP — swap the looping surface clip for this world's shop clip + * (data/landing.json → videos[frame].shop, e.g. terran-shop-01.mp4); + * the next press swaps back. The surface clip is PAUSED, not + * destroyed, so it resumes exactly where it left off; the shop clip + * is created on the first press (the file was preloaded) and then + * just paused/resumed, and the deck's SHOP slot relabels to + * `labelActive` ("Leave Shop", data/actionbar.json → surface.buttons) + * and back. The upper-left HUD stays up in both states — the shop is + * on the same world — and the surface hum keeps humming. + * A world whose frame has no shop clip (data/landing.json → shop null) + * gets a console note instead. + */ + toggleShop() { + if (this.phase !== 'surface') return; // no deck, no shop + if (this.shopMode) { + this.leaveShop(); + return; + } + if (!this.hasVideo(this.shopKey)) { + this.consoleNote('NO SHOP ON THIS WORLD'); + return; + } + if (!this.shopVideo) { + // First press of this stay: bring the clip up (only the