diff --git a/src/main.js b/src/main.js index 37f90c9..af0747d 100644 --- a/src/main.js +++ b/src/main.js @@ -32,54 +32,47 @@ window.addEventListener('appinstalled', () => { installWrap.hidden = true }) -const WIDTH = 960 +const WIDTH = 1122 +const HEIGHT = 1402 -// Match the Phaser logical height to the usable browser/PWA viewport. -// This keeps the same 960-wide room geometry while allowing the five -// floors to expand vertically on phones instead of letterboxing a 4:3 game. -const VIEWPORT_W = Math.max( - 320, - Math.round(window.visualViewport?.width ?? window.innerWidth) -) -const VIEWPORT_H = Math.max( - 480, - Math.round(window.visualViewport?.height ?? window.innerHeight) -) -const HEIGHT = Math.max( - 720, - Math.min(1600, Math.round(WIDTH * VIEWPORT_H / VIEWPORT_W)) -) +// Native geometry measured from the approved 1122×1402 house shell. +// Phaser fits this coordinate system to the device; the shell is no longer +// stretched to a device-dependent room grid. +const LEFT_X = 88 +const BANK_W = 379 +const RIGHT_X = 654 + +const TOWER_X = 438 +const TOWER_W = 244 +const TOWER_CENTER_X = TOWER_X + TOWER_W / 2 + +// Functional lift lives inside the wider decorative tower. +const ELEVATOR_X = 507 +const ELEVATOR_W = 108 + +const FLOOR_SLOTS = [ + { y: 234, h: 179 }, + { y: 451, h: 186 }, + { y: 675, h: 184 }, + { y: 896, h: 180 }, + { y: 1112, h: 169 } +] +const FLOOR_COUNT = FLOOR_SLOTS.length +const SHELL_BEAM_Y = [432, 656, 877, 1094] + +const HOUSE_TOP = FLOOR_SLOTS[0].y +const HOUSE_BOTTOM = FLOOR_SLOTS[FLOOR_COUNT - 1].y + FLOOR_SLOTS[FLOOR_COUNT - 1].h +const STATUS_Y = 1352 -const HOUSE_TOP = 74 -const BOTTOM_UI_H = 72 -const HOUSE_BOTTOM_PAD = 28 -const LEFT_X = 50 -const BANK_W = 350 -const ELEVATOR_GAP = 20 -const ELEVATOR_W = 120 -const ELEVATOR_X = LEFT_X + BANK_W + ELEVATOR_GAP -const RIGHT_X = ELEVATOR_X + ELEVATOR_W + ELEVATOR_GAP -const FLOOR_GAP = 8 -const FLOOR_COUNT = 5 -const FLOOR_H = Math.floor( - ( - HEIGHT - - HOUSE_TOP - - BOTTOM_UI_H - - HOUSE_BOTTOM_PAD - - FLOOR_GAP * (FLOOR_COUNT - 1) - ) / FLOOR_COUNT -) -const STATUS_Y = HEIGHT - 36 const ELEVATOR_CAR_W = 82 -const ELEVATOR_CAR_H = Math.min(104, Math.max(74, Math.round(FLOOR_H * 0.36))) -const ELEVATOR_OPENING_W = 72 -const ELEVATOR_OPENING_H = Math.min(96, Math.max(68, Math.round(FLOOR_H * 0.34))) +const ELEVATOR_CAR_H = 90 +const ELEVATOR_OPENING_W = 78 +const ELEVATOR_OPENING_H = 82 const PLAYER_DRAG_FOLLOW = 0.18 -const PLAYER_DRAG_OFFSET_Y = 42 +const PLAYER_DRAG_OFFSET_Y = 50 const ROOM_HOLD_MS = 520 -const ROOM_HOLD_MOVE_TOLERANCE = 18 +const ROOM_HOLD_MOVE_TOLERANCE = 21 const PLAYER_STATE = { IDLE: 'idle', @@ -148,12 +141,17 @@ const ROOM_SKINS = { } function floorY(floor) { - return HOUSE_TOP + floor * (FLOOR_H + FLOOR_GAP) + return FLOOR_SLOTS[floor].y +} + +function floorHeight(floor) { + return FLOOR_SLOTS[floor].h } function makeRoom(id, name, floor, side, color) { const x = side === 'left' ? LEFT_X : RIGHT_X const y = floorY(floor) + const height = floorHeight(floor) return { id, @@ -164,11 +162,11 @@ function makeRoom(id, name, floor, side, color) { x, y, width: BANK_W, - height: FLOOR_H, + height, walkBand: { - minY: y + Math.round(FLOOR_H * 0.62), - maxY: y + FLOOR_H - 10, - homeY: y + Math.round(FLOOR_H * 0.82) + minY: y + Math.round(height * 0.60), + maxY: y + height - 10, + homeY: y + Math.round(height * 0.82) } } } @@ -189,7 +187,7 @@ const ROOMS = [ const ROOM_BY_ID = new Map(ROOMS.map((room) => [room.id, room])) function elevatorStopY(room) { - return room.y + Math.round(FLOOR_H / 2) + return room.y + Math.round(room.height / 2) } function elevatorEntry(room) { @@ -224,9 +222,9 @@ function cropTexture(scene, sourceKey, key, rect) { } function buildElevatorSvg() { - const shaftHeight = FLOOR_COUNT * FLOOR_H + (FLOOR_COUNT - 1) * FLOOR_GAP - const openings = Array.from({ length: FLOOR_COUNT }, (_, floor) => { - const centerY = floor * (FLOOR_H + FLOOR_GAP) + FLOOR_H / 2 + const shaftHeight = HOUSE_BOTTOM - HOUSE_TOP + const openings = FLOOR_SLOTS.map((slot) => { + const centerY = slot.y + slot.h / 2 - HOUSE_TOP const y = centerY - ELEVATOR_OPENING_H / 2 return ` @@ -246,7 +244,7 @@ function buildElevatorSvg() { text-anchor="middle" dominant-baseline="middle" font-family="Arial, sans-serif" - font-size="28" + font-size="26" fill="#f28fb3" >♥ ` @@ -260,30 +258,31 @@ function buildElevatorSvg() { viewBox="0 0 ${ELEVATOR_W} ${shaftHeight}" > ${openings} ` } + class HouseScene extends Phaser.Scene { constructor() { super('House') @@ -403,21 +402,15 @@ class HouseScene extends Phaser.Scene { const graphics = this.add.graphics().setDepth(0) graphics.fillStyle(0xfffbfd, 1) - graphics.fillRoundedRect( - 28, - HOUSE_TOP - 18, - 904, - FLOOR_COUNT * FLOOR_H + (FLOOR_COUNT - 1) * FLOOR_GAP + 36, - 24 - ) + graphics.fillRect(36, 220, 1050, HOUSE_BOTTOM - 220) for (const room of ROOMS) { const skin = ROOM_SKINS[room.id] const inset = 7 const sideInset = Math.round(room.width * 0.20) const wallTop = room.y + 9 - const backTop = room.y + Math.max(14, Math.round(FLOOR_H * 0.08)) - const horizonY = room.y + Math.round(FLOOR_H * 0.60) + const backTop = room.y + Math.max(14, Math.round(room.height * 0.08)) + const horizonY = room.y + Math.round(room.height * 0.60) const bottomY = room.y + room.height - 8 const left = room.x + inset const right = room.x + room.width - inset @@ -550,106 +543,73 @@ class HouseScene extends Phaser.Scene { } } - this.add.text( - WIDTH / 2, - 48, - 'Drag her to walk • press & hold a room to ride the lift', - { - fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif', - fontSize: '15px', - color: '#6d4c61' - } - ).setOrigin(0.5).setDepth(100) } buildHouseShell() { const shellDepth = 7 const towerDepth = 8 - const stackHeight = FLOOR_COUNT * FLOOR_H + (FLOOR_COUNT - 1) * FLOOR_GAP - const stackBottom = HOUSE_TOP + stackHeight - const leftOuterX = LEFT_X - 10 - const rightOuterX = RIGHT_X + BANK_W + 10 - const leftBankCenter = LEFT_X + BANK_W / 2 - const rightBankCenter = RIGHT_X + BANK_W / 2 - const towerCenterX = ELEVATOR_X + ELEVATOR_W / 2 - - // Full roof artwork keeps its native proportions instead of being cropped - // from a stretched master sheet. - this.add.image(WIDTH / 2, HOUSE_TOP + 8, 'shell-roof') - .setOrigin(0.5, 1) - .setDisplaySize(1000, 116) + this.add.image(WIDTH / 2, 0, 'shell-roof') + .setOrigin(0.5, 0) + .setDisplaySize(1122, 242) .setDepth(shellDepth) - // The center tower is independent from the moving elevator shaft/car. - // The functional shaft renders above this trim so animation remains clear. - this.add.image(towerCenterX, HOUSE_TOP - 34, 'shell-tower') + this.add.image(TOWER_CENTER_X, 0, 'shell-tower') .setOrigin(0.5, 0) - .setDisplaySize(206, stackHeight + 92) + .setDisplaySize(TOWER_W, HEIGHT) .setDepth(towerDepth) - // Build the outside frame from one clean repeatable post segment per room. + const leftOuterX = 60.5 + const rightOuterX = WIDTH - 60.5 + for (let floor = 0; floor < FLOOR_COUNT; floor += 1) { - const segmentY = floorY(floor) + FLOOR_H / 2 + const slot = FLOOR_SLOTS[floor] + const segmentY = slot.y + slot.h / 2 this.add.image(leftOuterX, segmentY, 'shell-post-seg') - .setDisplaySize(34, FLOOR_H + 8) + .setDisplaySize(65, slot.h) .setDepth(shellDepth) this.add.image(rightOuterX, segmentY, 'shell-post-seg') - .setDisplaySize(34, FLOOR_H + 8) + .setDisplaySize(65, slot.h) .setFlipX(true) .setDepth(shellDepth) } - // Joints and beams stay at natural floor boundaries regardless of viewport. - for (let floor = 0; floor < FLOOR_COUNT - 1; floor += 1) { - const beamY = floorY(floor) + FLOOR_H + FLOOR_GAP / 2 - const bracketY = floorY(floor) + FLOOR_H - 1 + for (let floor = 0; floor < SHELL_BEAM_Y.length; floor += 1) { + const beamY = SHELL_BEAM_Y[floor] - this.add.image(leftOuterX, beamY, 'shell-post-joint') - .setDisplaySize(42, 28) + this.add.image(273, beamY, 'shell-beam-left') + .setDisplaySize(410, 66) .setDepth(shellDepth + 1) - this.add.image(rightOuterX, beamY, 'shell-post-joint') - .setDisplaySize(42, 28) + this.add.image(843, beamY, 'shell-beam-right') + .setDisplaySize(414, 66) + .setDepth(shellDepth + 1) + + this.add.image(62.5, beamY - 12, 'shell-post-joint') + .setDisplaySize(77, 55) + .setDepth(shellDepth + 2) + + this.add.image(WIDTH - 62.5, beamY - 12, 'shell-post-joint') + .setDisplaySize(77, 55) .setFlipX(true) - .setDepth(shellDepth + 1) - - this.add.image(leftBankCenter, beamY, 'shell-beam-left') - .setDisplaySize(BANK_W + 24, 27) - .setDepth(shellDepth + 1) - - this.add.image(rightBankCenter, beamY, 'shell-beam-right') - .setDisplaySize(BANK_W + 24, 27) - .setDepth(shellDepth + 1) - - this.add.image(LEFT_X + BANK_W + 3, bracketY, 'shell-bracket-left') - .setOrigin(1, 0) - .setDisplaySize(20, 22) - .setDepth(shellDepth + 1) - - this.add.image(RIGHT_X - 3, bracketY, 'shell-bracket-left') - .setOrigin(0, 0) - .setFlipX(true) - .setDisplaySize(20, 22) - .setDepth(shellDepth + 1) + .setDepth(shellDepth + 2) } - // Split foundations leave the tower/plinth free to define the centre. - this.add.image(leftBankCenter, stackBottom + 7, 'shell-foundation-left') - .setOrigin(0.5, 0) - .setDisplaySize(BANK_W + 54, 48) + this.add.image(0, 1240, 'shell-foundation-left') + .setOrigin(0, 0) + .setDisplaySize(462, 162) .setDepth(shellDepth + 1) - this.add.image(rightBankCenter, stackBottom + 7, 'shell-foundation-right') - .setOrigin(0.5, 0) - .setDisplaySize(BANK_W + 54, 48) + this.add.image(660, 1240, 'shell-foundation-right') + .setOrigin(0, 0) + .setDisplaySize(462, 162) .setDepth(shellDepth + 1) } buildElevator() { - const shaftHeight = FLOOR_COUNT * FLOOR_H + (FLOOR_COUNT - 1) * FLOOR_GAP + const shaftHeight = HOUSE_BOTTOM - HOUSE_TOP this.add.image(ELEVATOR_X + ELEVATOR_W / 2, HOUSE_TOP, 'elevator-shaft') .setOrigin(0.5, 0) @@ -690,7 +650,7 @@ class HouseScene extends Phaser.Scene { padding: { x: 16, y: 8 } }).setOrigin(0.5).setDepth(1000) - this.resetButton = this.add.text(892, STATUS_Y, 'Reset', { + this.resetButton = this.add.text(1044, STATUS_Y, 'Reset', { fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif', fontSize: '14px', color: '#7a526b', @@ -951,7 +911,7 @@ class HouseScene extends Phaser.Scene { 1 ) - this.player.setScale(Phaser.Math.Linear(0.29, 0.34, t)) + this.player.setScale(Phaser.Math.Linear(0.34, 0.40, t)) this.player.setDepth(300 + room.floor) } @@ -1237,13 +1197,13 @@ class HouseScene extends Phaser.Scene { dy /= length this.player.x = Phaser.Math.Clamp( - this.player.x + dx * 190 * delta / 1000, + this.player.x + dx * 220 * delta / 1000, room.x + 26, room.x + room.width - 26 ) this.player.y = Phaser.Math.Clamp( - this.player.y + dy * 72 * delta / 1000, + this.player.y + dy * 84 * delta / 1000, room.walkBand.minY, room.walkBand.maxY ) @@ -1268,7 +1228,7 @@ const game = new Phaser.Game({ }, scale: { mode: Phaser.Scale.FIT, - autoCenter: Phaser.Scale.CENTER_BOTH, + autoCenter: Phaser.Scale.CENTER_HORIZONTALLY, width: WIDTH, height: HEIGHT }, @@ -1279,7 +1239,7 @@ const game = new Phaser.Game({ }) window.__DOLLHOUSE__ = { - version: '0.7.1', + version: '0.8.0', game, async resetAll() { await clearGameState()