From a404e42ca8cb14b46518216f45c69512ef2696b7 Mon Sep 17 00:00:00 2001 From: spongebob Date: Sat, 17 Oct 2026 18:01:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E8=A5=BF?= =?UTF-8?q?=E5=AE=89=E6=97=85=E8=A1=8C=E8=AE=B0=E5=BF=86=E4=BA=92=E5=8A=A8?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E6=B8=B8=E6=88=8F=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成项目基础搭建:配置 Vite 开发构建环境,集成 Phaser 3 与虚拟摇杆插件,实现地图漫游、景点触发、拍立得弹窗、进度存档等核心功能,添加占位资源与批量生成脚本,配置项目元数据与忽略规则。 --- .gitignore | 51 + CHANGELOG.md | 18 + docs/overview.md | 612 ++++++++++++ docs/spec.md | 339 +++++++ index.html | 29 + package-lock.json | 1248 ++++++++++++++++++++++++ package.json | 19 + public/assets/maps/map-base.svg | 164 ++++ public/assets/photos/bingmayong_01.svg | 16 + public/assets/photos/bingmayong_02.svg | 16 + public/assets/photos/buyecheng_01.svg | 16 + public/assets/photos/buyecheng_02.svg | 16 + public/assets/photos/chengqiang_01.svg | 16 + public/assets/photos/daminggong_01.svg | 16 + public/assets/photos/dayanta_01.svg | 16 + public/assets/photos/dayanta_02.svg | 16 + public/assets/photos/huaqingchi_01.svg | 16 + public/assets/photos/huiminjie_01.svg | 16 + public/assets/photos/huiminjie_02.svg | 16 + public/assets/photos/shanbwy_01.svg | 16 + public/assets/photos/shanbwy_02.svg | 16 + public/assets/photos/xiaoyanta_01.svg | 16 + public/assets/photos/zhonglou_01.svg | 16 + public/assets/textures/paper.jpg | Bin 0 -> 176626 bytes scripts/gen-placeholder-photos.mjs | 116 +++ src/data/spots.js | 156 +++ src/event-bus.js | 97 ++ src/game-state.js | 175 ++++ src/main.js | 54 + src/objects/GuideArrow.js | 120 +++ src/objects/Player.js | 296 ++++++ src/objects/PoiMarker.js | 134 +++ src/scenes/MapScene.js | 243 +++++ src/scenes/PreloadScene.js | 147 +++ src/ui/CompleteOverlay.js | 87 ++ src/ui/Hud.js | 57 ++ src/ui/PhotoModal.js | 175 ++++ src/ui/TitleOverlay.js | 118 +++ src/ui/dom-layer.js | 66 ++ src/ui/rotate-hint.js | 52 + src/ui/styles.css | 651 ++++++++++++ vite.config.js | 25 + 42 files changed, 5489 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 docs/overview.md create mode 100644 docs/spec.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/assets/maps/map-base.svg create mode 100644 public/assets/photos/bingmayong_01.svg create mode 100644 public/assets/photos/bingmayong_02.svg create mode 100644 public/assets/photos/buyecheng_01.svg create mode 100644 public/assets/photos/buyecheng_02.svg create mode 100644 public/assets/photos/chengqiang_01.svg create mode 100644 public/assets/photos/daminggong_01.svg create mode 100644 public/assets/photos/dayanta_01.svg create mode 100644 public/assets/photos/dayanta_02.svg create mode 100644 public/assets/photos/huaqingchi_01.svg create mode 100644 public/assets/photos/huiminjie_01.svg create mode 100644 public/assets/photos/huiminjie_02.svg create mode 100644 public/assets/photos/shanbwy_01.svg create mode 100644 public/assets/photos/shanbwy_02.svg create mode 100644 public/assets/photos/xiaoyanta_01.svg create mode 100644 public/assets/photos/zhonglou_01.svg create mode 100644 public/assets/textures/paper.jpg create mode 100644 scripts/gen-placeholder-photos.mjs create mode 100644 src/data/spots.js create mode 100644 src/event-bus.js create mode 100644 src/game-state.js create mode 100644 src/main.js create mode 100644 src/objects/GuideArrow.js create mode 100644 src/objects/Player.js create mode 100644 src/objects/PoiMarker.js create mode 100644 src/scenes/MapScene.js create mode 100644 src/scenes/PreloadScene.js create mode 100644 src/ui/CompleteOverlay.js create mode 100644 src/ui/Hud.js create mode 100644 src/ui/PhotoModal.js create mode 100644 src/ui/TitleOverlay.js create mode 100644 src/ui/dom-layer.js create mode 100644 src/ui/rotate-hint.js create mode 100644 src/ui/styles.css create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eee94a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# ── 依赖与构建产物 ── +node_modules/ +dist/ +.vite/ +.cache/ +.parcel-cache/ +coverage/ + +# ── Capacitor / Android(M6 打包阶段) ── +android/.gradle/ +android/app/build/ +android/build/ +android/capacitor-cordova-android-plugins/build/ +android/local.properties +android/app/src/main/assets/public/ +*.keystore +!android/app/debug.keystore + +# ── 头脑风暴视觉伴侣会话(本地临时) ── +.superpowers/ + +# ── 环境与密钥 ── +.env +.env.* +*.pem + +# ── 日志 ── +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# ── 编辑器与系统文件 ── +.vscode/ +.idea/ +*.swp +*.swo +*.sublime-workspace +.history/ +.DS_Store +Thumbs.db +desktop.ini + +# ── 临时与备份文件 ── +tmp/ +*~ +*.tmp +*.bak +.~lock.* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..db87116 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# 变更记录(CHANGELOG) + +本项目所有显著变更均记录于此。 +版本号:`vYYYY.MM.DD-XX`;类型标签:[Add] / [Change] / [Fix] / [Remove] / [Doc] / [Chore] + +## v2026.10.16-01 + +首次完整实现(M1–M3):可玩核心 + 完整体验 + 手账美化(全占位资源)。 + +- [Doc] 新增 `docs/spec.md`:经需求澄清与设计确认的细化技术规格(12 项决策记录、分层架构、数据模型、验收标准、M1–M6 交付路线、照片替换指南) +- [Add] 项目脚手架:Vite 5 + Phaser 3 + phaser3-rex-plugins;`base: './'` 相对路径,适配 GitHub Pages / Vercel 静态托管 +- [Add] 混合架构落地:Phaser 地图层(PreloadScene / MapScene 双场景)+ DOM UI 层,经 event-bus 事件桥解耦通信 +- [Add] 手账风西安地图:3000×2100 SVG 底图(明城墙 + 护城河、渭/灞/浐/沣水系、主干路网、罗盘、装饰边框),叠加 AI 纸张纹理(multiply 混合) +- [Add] 玩家角色:程序生成贴纸小人(4 方向 × 2 帧弹性行走动画),AI 精灵表就绪后可整体替换 +- [Add] 玩法系统:WASD/方向键 + 移动端虚拟摇杆;景点触发冷却防连弹;localStorage 进度持久化(隐私模式内存降级);屏幕边缘方向引导箭头(目标入视野自动淡出) +- [Add] DOM UI 组件:手账封面页(胶带/拍立得装饰、继续旅行与重新开始)、拍立得照片弹窗(叠沓翻看、手写文案、日期角标、照片缺失降级占位卡)、HUD 收集进度徽章、全解锁纪念页(照片九宫格 + 旅程统计)、竖屏旋转提示 +- [Add] 数据驱动内容源:`src/data/spots.js` 十景点配置(坐标/照片/文案);`scripts/gen-placeholder-photos.mjs` 占位照片生成脚本(本次生成 15 张 4:3 手账风占位 SVG) +- [Chore] 新增 `.gitignore`:依赖与构建产物、Capacitor/Android 打包产物、环境密钥、日志、编辑器与系统临时文件 diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..7aa2891 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,612 @@ +# 西安旅行记忆互动地图游戏 — AI 开发指导文档 + +> **技术栈**:Phaser 3 + Vite + Capacitor +> **目标平台**:PC 浏览器、Android App +> **核心玩法**:玩家控制角色在西安旅游地图上移动,抵达景点触发照片弹窗 +> **文档用途**:可直接作为 AI 编程工具(Cursor / Copilot / Claude Code 等)的项目开发规格说明 + +--- + +## 1. 项目概述 + +开发一款 2D 俯视角互动地图游戏,将国庆期间在西安拍摄的照片与地图景点绑定。玩家通过键盘(PC)或虚拟摇杆(移动端)控制角色移动,当角色进入景点触发区域时,弹出对应照片和描述文字。项目需支持跨平台运行,并方便通过链接或 APK 分享。 + +--- + +## 2. 技术选型 + +| 模块 | 技术 | 说明 | +|---|---|---| +| 游戏引擎 | Phaser 3 | HTML5 2D 游戏框架,内置物理、输入、场景管理 | +| 构建工具 | Vite | 快速开发与构建,支持 ES 模块 | +| 移动端打包 | Capacitor | 将 Web 应用包装为原生 Android/iOS 应用 | +| UI 插件 | phaser3-rex-plugins | 提供对话框、虚拟摇杆等组件 | +| 地图编辑(可选) | Tiled Map Editor | 导出 JSON 瓦片地图,Phaser 直接加载 | +| 部署托管 | GitHub Pages / Vercel / Netlify | 免费静态托管,支持 HTTPS | + +--- + +## 3. 项目结构 + +``` +xian-travel-game/ +├── public/ +│ ├── assets/ +│ │ ├── maps/ # 西安地图图片(xian-map.png) +│ │ ├── photos/ # 景点照片(按景点命名) +│ │ ├── sprites/ # 角色精灵图(player.png) +│ │ └── audio/ # 背景音乐/音效(可选) +│ └── index.html +├── src/ +│ ├── main.js # 游戏入口与全局配置 +│ ├── scenes/ +│ │ ├── BootScene.js # 资源预加载 +│ │ ├── MapScene.js # 地图主场景 +│ │ └── PhotoScene.js # 照片弹窗场景 +│ ├── objects/ +│ │ ├── Player.js # 玩家角色类 +│ │ └── POI.js # 景点触发点类(可选) +│ └── data/ +│ └── spots.js # 景点坐标、照片、描述配置 +├── capacitor.config.ts +├── vite.config.js +├── package.json +└── index.html +``` + +--- + +## 4. 环境搭建与依赖安装 + +```bash +# 创建 Vite 项目 +npm create vite@latest xian-travel-game -- --template vanilla +cd xian-travel-game + +# 安装 Phaser 与 RexUI 插件 +npm install phaser phaser3-rex-plugins + +# 安装 Capacitor(用于 Android 打包) +npm install @capacitor/core @capacitor/cli @capacitor/android + +# 初始化 Capacitor +npx cap init +``` + +`vite.config.js` 基础配置: + +```js +import { defineConfig } from 'vite'; + +export default defineConfig({ + base: './', // 相对路径,便于部署到子目录 + build: { + outDir: 'dist', + assetsInlineLimit: 0 // 避免大图片被内联 + }, + server: { + host: true, // 允许局域网访问,方便手机调试 + port: 3000 + } +}); +``` + +--- + +## 5. 核心代码实现 + +### 5.1 入口与全局配置(`src/main.js`) + +```js +import Phaser from 'phaser'; +import RexUIPlugin from 'phaser3-rex-plugins/templates/ui/ui-plugin.js'; +import VirtualJoystickPlugin from 'phaser3-rex-plugins/plugins/virtualjoystick-plugin.js'; + +import BootScene from './scenes/BootScene.js'; +import MapScene from './scenes/MapScene.js'; +import PhotoScene from './scenes/PhotoScene.js'; + +const config = { + type: Phaser.AUTO, + scale: { + mode: Phaser.Scale.FIT, // 自适应屏幕,保持宽高比 + autoCenter: Phaser.Scale.CENTER_BOTH, + width: 1280, + height: 720, + parent: 'game-container' + }, + physics: { + default: 'arcade', + arcade: { + gravity: { y: 0 }, + debug: false + } + }, + plugins: { + scene: [ + { key: 'rexUI', plugin: RexUIPlugin, mapping: 'rexUI' }, + { key: 'rexVirtualJoystick', plugin: VirtualJoystickPlugin, mapping: 'rexVirtualJoystick' } + ] + }, + scene: [BootScene, MapScene, PhotoScene] +}; + +new Phaser.Game(config); +``` + +`index.html` 需包含游戏容器和移动端 meta: + +```html + + + + + + 西安旅行记忆 + + + +
+ + + +``` + +### 5.2 资源加载(`src/scenes/BootScene.js`) + +```js +import Phaser from 'phaser'; +import { SPOTS } from '../data/spots.js'; + +export default class BootScene extends Phaser.Scene { + constructor() { + super({ key: 'BootScene' }); + } + + preload() { + // 显示加载进度 + const width = this.scale.width; + const height = this.scale.height; + const progressBar = this.add.graphics(); + const progressBox = this.add.graphics(); + progressBox.fillStyle(0x222222, 0.8); + progressBox.fillRect(width / 2 - 160, height / 2 - 25, 320, 50); + + this.load.on('progress', (value) => { + progressBar.clear(); + progressBar.fillStyle(0xffffff, 1); + progressBar.fillRect(width / 2 - 150, height / 2 - 15, 300 * value, 30); + }); + + // 加载地图 + this.load.image('xianMap', 'assets/maps/xian-map.png'); + + // 加载角色精灵图(假设为 4 方向行走图,每帧 32x48) + this.load.spritesheet('player', 'assets/sprites/player.png', { + frameWidth: 32, + frameHeight: 48 + }); + + // 加载所有景点照片 + SPOTS.forEach(spot => { + spot.photos.forEach((photoPath, index) => { + this.load.image(`${spot.id}_${index}`, photoPath); + }); + }); + + // 可选:加载背景音乐 + // this.load.audio('bgm', 'assets/audio/bgm.mp3'); + } + + create() { + // 创建角色动画(示例:向下走) + this.anims.create({ + key: 'walk-down', + frames: this.anims.generateFrameNumbers('player', { start: 0, end: 2 }), + frameRate: 8, + repeat: -1 + }); + // 其他方向动画按需添加... + + this.scene.start('MapScene'); + } +} +``` + +### 5.3 地图主场景(`src/scenes/MapScene.js`) + +```js +import Phaser from 'phaser'; +import Player from '../objects/Player.js'; +import { SPOTS } from '../data/spots.js'; + +export default class MapScene extends Phaser.Scene { + constructor() { + super({ key: 'MapScene' }); + } + + create() { + // 1. 添加地图背景 + this.map = this.add.image(0, 0, 'xianMap').setOrigin(0, 0); + + // 2. 设置物理世界边界为地图尺寸 + this.physics.world.setBounds(0, 0, this.map.width, this.map.height); + + // 3. 创建玩家 + this.player = new Player(this, 400, 300); + + // 4. 摄像机跟随玩家 + this.cameras.main.startFollow(this.player); + this.cameras.main.setBounds(0, 0, this.map.width, this.map.height); + + // 5. 创建虚拟摇杆(移动端) + this.createVirtualJoystick(); + + // 6. 创建景点触发区域 + this.createSpotTriggers(); + + // 7. 可选:播放背景音乐 + // this.bgm = this.sound.add('bgm', { loop: true, volume: 0.5 }); + // this.bgm.play(); + } + + createVirtualJoystick() { + // 使用 rexVirtualJoystick 插件 + this.joystick = this.rexVirtualJoystick.add(this, { + x: 120, + y: this.scale.height - 120, + radius: 60, + base: this.add.circle(0, 0, 60, 0x888888, 0.3), + thumb: this.add.circle(0, 0, 30, 0xcccccc, 0.6), + enable: true + }); + + // 将摇杆传递给玩家 + this.player.setJoystick(this.joystick); + } + + createSpotTriggers() { + SPOTS.forEach(spot => { + // 添加景点标记(半透明圆圈 + 名称) + this.add.circle(spot.x, spot.y, spot.radius, 0xffaa00, 0.2); + this.add.text(spot.x, spot.y - spot.radius - 20, spot.name, { + fontSize: '16px', + color: '#ffffff', + backgroundColor: '#00000088', + padding: { x: 6, y: 3 } + }).setOrigin(0.5); + + // 创建触发区域(Zone + 静态物理体) + const zone = this.add.zone(spot.x, spot.y, spot.radius * 2, spot.radius * 2); + this.physics.add.existing(zone, true); // static body + + // 重叠检测:玩家进入区域时弹出照片 + this.physics.add.overlap(this.player, zone, () => { + if (!this.photoOpen) { + this.photoOpen = true; + this.scene.pause(); + this.scene.launch('PhotoScene', { spot }); + } + }); + }); + + // 监听照片场景关闭,恢复主场景 + this.events.on('resume', () => { + this.photoOpen = false; + }); + } + + update() { + this.player.update(); + } +} +``` + +### 5.4 玩家类(`src/objects/Player.js`) + +```js +import Phaser from 'phaser'; + +export default class Player extends Phaser.Physics.Arcade.Sprite { + constructor(scene, x, y) { + super(scene, x, y, 'player'); + scene.add.existing(this); + scene.physics.add.existing(this); + + this.speed = 200; + this.setCollideWorldBounds(true); + + // 键盘输入 + this.cursors = scene.input.keyboard.createCursorKeys(); + this.wasd = scene.input.keyboard.addKeys('W,A,S,D'); + + // 虚拟摇杆(由外部注入) + this.joystick = null; + } + + setJoystick(joystick) { + this.joystick = joystick; + } + + update() { + let vx = 0; + let vy = 0; + + // 键盘输入 + if (this.cursors.left.isDown || this.wasd.A.isDown) vx = -1; + else if (this.cursors.right.isDown || this.wasd.D.isDown) vx = 1; + if (this.cursors.up.isDown || this.wasd.W.isDown) vy = -1; + else if (this.cursors.down.isDown || this.wasd.S.isDown) vy = 1; + + // 虚拟摇杆输入(优先级高于键盘) + if (this.joystick && this.joystick.isPressed) { + vx = this.joystick.forceX; + vy = this.joystick.forceY; + } + + // 归一化速度,避免斜向移动过快 + const len = Math.sqrt(vx * vx + vy * vy); + if (len > 0) { + this.setVelocity((vx / len) * this.speed, (vy / len) * this.speed); + // 播放行走动画(根据方向切换) + // this.anims.play('walk-down', true); // 示例 + } else { + this.setVelocity(0, 0); + this.anims.stop(); + } + } +} +``` + +### 5.5 景点数据(`src/data/spots.js`) + +```js +export const SPOTS = [ + { + id: 'dayanta', + name: '大雁塔', + x: 820, + y: 450, + radius: 60, + photos: [ + 'assets/photos/dayanta_01.jpg', + 'assets/photos/dayanta_02.jpg' + ], + description: '国庆第3天,夕阳下的大雁塔格外壮观。' + }, + { + id: 'bingmayong', + name: '兵马俑', + x: 1500, + y: 200, + radius: 70, + photos: [ + 'assets/photos/bingmayong_01.jpg' + ], + description: '世界第八大奇迹,震撼到说不出话。' + }, + { + id: 'zhonglou', + name: '钟楼', + x: 600, + y: 350, + radius: 50, + photos: [ + 'assets/photos/zhonglou_01.jpg' + ], + description: '夜晚的钟楼灯火辉煌,车水马龙。' + } + // ... 按实际照片添加更多景点 +]; +``` + +> **坐标说明**:`x, y` 为地图图片上的像素坐标。可用图片编辑工具或在线取点工具获取。 + +### 5.6 照片弹窗场景(`src/scenes/PhotoScene.js`) + +```js +import Phaser from 'phaser'; + +export default class PhotoScene extends Phaser.Scene { + constructor() { + super({ key: 'PhotoScene' }); + } + + init(data) { + this.spot = data.spot; + } + + create() { + const width = this.scale.width; + const height = this.scale.height; + + // 半透明遮罩,阻止点击穿透 + const overlay = this.add.rectangle(0, 0, width, height, 0x000000, 0.85) + .setOrigin(0, 0) + .setInteractive(); + + // 照片显示(取第一张照片,可扩展为轮播) + const photoKey = `${this.spot.id}_0`; + const photo = this.add.image(width / 2, height / 2 - 40, photoKey); + // 等比例缩放至合适大小 + const maxW = width * 0.7; + const maxH = height * 0.6; + const scale = Math.min(maxW / photo.width, maxH / photo.height, 1); + photo.setScale(scale); + + // 景点名称 + this.add.text(width / 2, height / 2 - maxH / 2 - 60, this.spot.name, { + fontSize: '32px', + color: '#ffffff', + fontStyle: 'bold' + }).setOrigin(0.5); + + // 描述文字 + this.add.text(width / 2, height / 2 + maxH / 2 + 30, this.spot.description, { + fontSize: '18px', + color: '#cccccc', + wordWrap: { width: width * 0.7 }, + align: 'center' + }).setOrigin(0.5); + + // 点击任意位置关闭 + overlay.on('pointerdown', () => { + this.scene.stop(); + this.scene.resume('MapScene'); + }); + + // 也支持按 ESC / 空格关闭(PC) + this.input.keyboard.once('keydown-ESC', () => { + this.scene.stop(); + this.scene.resume('MapScene'); + }); + this.input.keyboard.once('keydown-SPACE', () => { + this.scene.stop(); + this.scene.resume('MapScene'); + }); + } +} +``` + +--- + +## 6. 资源准备规范 + +### 6.1 地图底图 + +- 格式:PNG 或 JPG,宽度建议 2000–4000px +- 来源:陕西省测绘地理信息局标准地图、城市吧旅游地图,或自行风格化处理 +- 存放:`public/assets/maps/xian-map.png` + +### 6.2 景点照片 + +- 按景点分类,每个景点 1–5 张 +- 统一压缩至宽度 1200px 以内,推荐 WebP 格式 +- 命名:`景点id_序号.jpg`(如 `dayanta_01.jpg`) +- 存放:`public/assets/photos/` + +### 6.3 角色精灵图 + +- 格式:PNG 精灵图,每帧 32×48 或 48×64 +- 至少包含 4 方向行走帧 +- 存放:`public/assets/sprites/player.png` + +--- + +## 7. 移动端适配 + +- **缩放模式**:`Phaser.Scale.FIT` + `autoCenter: CENTER_BOTH` +- **禁止滚动**:CSS `touch-action: none; overflow: hidden;` +- **虚拟摇杆**:使用 `rexVirtualJoystick`,位于屏幕左下角 +- **屏幕旋转**:监听 `orientationchange` 并调用 `game.scale.refresh()` +- **安全区域**:可使用 `viewport-fit=cover` 和 CSS `env(safe-area-inset-*)` 处理刘海屏 + +--- + +## 8. 打包 Android App(Capacitor) + +1. 构建 Web 版本: + ```bash + npm run build + ``` + +2. 配置 `capacitor.config.ts`: + ```ts + import { CapacitorConfig } from '@capacitor/cli'; + + const config: CapacitorConfig = { + appId: 'com.yourname.xianTravel', + appName: '西安旅行记忆', + webDir: 'dist', + server: { androidScheme: 'https' } + }; + export default config; + ``` + +3. 添加 Android 平台并同步: + ```bash + npx cap add android + npx cap sync + ``` + +4. 用 Android Studio 打开并生成 APK: + ```bash + npx cap open android + ``` + 在 Android Studio 中点击 **Build → Build Bundle(s) / APK(s) → Build APK(s)**。 + +--- + +## 9. 部署与分享 + +### 9.1 GitHub Pages(免费链接分享) + +```bash +npm run build +npx gh-pages -d dist +``` +在 GitHub 仓库 **Settings → Pages** 中,将 Source 设为 `gh-pages` 分支。 +若部署在子路径,需在 `vite.config.js` 中设置 `base: '/仓库名/'`。 + +### 9.2 Vercel / Netlify(自动部署 + 自定义域名) + +- Build command:`npm run build` +- Publish directory:`dist` +- 连接 GitHub 仓库后自动部署,分配 `https://项目名.vercel.app` 域名。 + +### 9.3 APK 直接分享 + +通过 Capacitor 生成的 APK 可通过微信、QQ 直接发送安装。 + +--- + +## 10. 性能优化清单 + +| 优化项 | 做法 | +|---|---| +| 图片压缩 | 照片转 WebP,宽度 ≤1200px | +| 按需加载 | 照片在 BootScene 中统一加载,或进入景点时动态加载 | +| 对象池 | 弹窗场景复用,避免频繁创建销毁 | +| 缓存引用 | 将 `this.player` 等存为类属性,避免每帧查找 | +| 选择性更新 | 仅当玩家移动时更新摇杆逻辑 | +| 渲染模式 | 低端设备可尝试 `Phaser.CANVAS` 替代 `Phaser.AUTO` | +| 世界边界 | 设置 `physics.world.setBounds` 避免角色跑出地图 | + +--- + +## 11. 开发时间估算 + +| 阶段 | 内容 | 预计时间 | +|---|---|---| +| 项目搭建 | Vite + Phaser + Capacitor 初始化 | 0.5 天 | +| 地图与角色 | 地图加载、角色移动、边界碰撞 | 1 天 | +| 景点系统 | 触发区域、照片弹窗、描述文字 | 1 天 | +| 移动端适配 | 虚拟摇杆、缩放适配、横屏处理 | 0.5–1 天 | +| 打包与部署 | Capacitor 打包 APK、GitHub Pages 部署 | 0.5 天 | +| 测试调优 | 真机测试、性能优化、Bug 修复 | 1 天 | +| **合计** | | **4.5–5 天** | + +--- + +## 12. AI 编程工具使用建议 + +- 将本文件作为项目根目录的 `README.md` 或 `SPEC.md`,让 AI 工具读取上下文。 +- 按章节逐步实现,每完成一个模块运行 `npm run dev` 验证。 +- 遇到具体错误时,将错误信息与相关代码段一并提供给 AI 工具。 +- 地图坐标和照片路径需根据实际资源手动调整,AI 工具无法自动获取。 +- 虚拟摇杆的 `rexVirtualJoystick` 用法可参考 [phaser3-rex-plugins 官方文档](https://rexrainbow.github.io/phaser3-rex-notes/docs/site/virtualjoystick/)。 + +--- diff --git a/docs/spec.md b/docs/spec.md new file mode 100644 index 0000000..e8cbe23 --- /dev/null +++ b/docs/spec.md @@ -0,0 +1,339 @@ +# 西安旅行记忆互动地图游戏 · 技术规格说明(细化版) + +> 本文档是 `docs/overview.md`(原始需求)经过需求澄清与设计确认后的细化规格,作为后续实现的唯一依据。 +> 日期:2026-10-16 · 状态:已与需求方逐节确认 + +--- + +## 1. 项目概述 + +一款 2D 俯视角互动地图游戏:玩家控制 Q 版小人在手绘手账风的西安地图上漫游,走进景点触发区即弹出"拍立得照片"回忆卡。集齐全部景点后解锁"旅行完成"纪念页。 + +- **一句话玩法**:走地图 → 触发景点 → 翻拍立得照片 → 集齐进度 → 纪念页 +- **目标平台**:手机/PC 浏览器(Web 优先交付);Android APK 为第二阶段 +- **分享形态**:URL 链接即玩;后期 APK 微信分发 +- **情感定位**:一本"可以走进去的旅行手账",暖色调、手写感、记忆感 + +--- + +## 2. 已确认决策记录 + +| # | 决策项 | 结论 | 备注 | +|---|---|---|---| +| 1 | 照片资源 | 暂未准备 | 占位图先行开发,后期按《照片替换指南》换真实照片 | +| 2 | 地图范围 | 主城区 + 经典景点 | 约 8–12 个景点,地图 3000×2100 viewBox | +| 3 | 玩法深度 | 收集进度 + 方向引导 | 屏幕边缘箭头指向最近未解锁景点 | +| 4 | 地图视觉 | 手绘旅行手账风 | 羊皮纸底色 + 棕褐线条 + 手绘图标 | +| 5 | 底图制作 | AI 素材 + SVG 路网混合 | 位置准确性归 SVG,氛围归 AI 生成素材 | +| 6 | 角色形象 | Q 版小人精灵图 | AI 生成 4 方向 × 3 帧精灵表,帧格 48×64 | +| 7 | 照片弹窗 | 拍立得照片风 | 白边相纸 + 手写描述 + 轻微旋转,多照片叠沓翻看 | +| 8 | 进度存档 | localStorage 持久化 | 可重置 | +| 9 | 开场页 | 手账风封面页 | "开始/继续旅行" + 进度概览 | +| 10 | 声音 | 暂不做 | 后期想加再加(预留 audio 目录) | +| 11 | 交付顺序 | Web 优先,APK 后置 | M1–M5 为 Web,M6 为 Capacitor 打包 | +| 12 | 架构 | Phaser 地图层 + DOM UI 层混合 | 取代原 overview.md 的纯 Phaser 场景方案 | + +--- + +## 3. 架构设计 + +### 3.1 分层 + +``` +index.html +├── #game → Phaser Canvas 层(下层):游戏世界 +├── #ui → DOM + CSS UI 层(上层,pointer-events 按需开合) +└── 横切模块:event-bus.js(事件桥)、game-state.js(存档)、data/spots.js(内容配置) +``` + +**Phaser 层**(仅两个场景,较原方案砍掉 PhotoScene): + +- `PreloadScene`:进度条 + 资源预加载,完成后自动进入 MapScene +- `MapScene`:地图渲染、相机跟随、角色精灵与动画、键盘/摇杆输入、景点触发检测(Zone overlap)、方向引导箭头渲染 + +**DOM UI 层**(纯 DOM 组件,零 Phaser 依赖): + +- `TitleOverlay`:手账封面页(开始/继续旅行、进度概览、重新开始) +- `PhotoModal`:拍立得弹窗(照片叠沓、翻看、手写描述、关闭) +- `Hud`:收集进度徽章(如 `5/12`)+ 已解锁数量 +- `CompleteOverlay`:100% 纪念页(照片九宫格 + 旅行统计 + 继续漫游) +- `dom-layer.js`:层管理器,负责显示/隐藏/互斥/覆盖 + +### 3.2 事件桥(EventBus) + +自写轻量事件模块(约 20 行,`on/once/off/emit`)。跨层事件协议: + +| 事件名 | 方向 | 载荷 | 语义 | +|---|---|---|---| +| `poi:reached` | Phaser → UI | `spot` 配置对象 | 玩家进入景点触发区,请求弹窗 | +| `ui:closed` | UI → Phaser | 无 | 弹窗/封面关闭,恢复游戏输入 | +| `state:changed` | game-state → 两层 | 进度快照 | 解锁变化,刷新 HUD 与地图标记 | +| `game:complete` | game-state → UI | 进度快照 | 全部解锁,弹出纪念页 | + +### 3.3 输入互斥(弹窗打开时) + +双保险: + +1. DOM 层全屏遮罩拦截所有触摸/点击(`pointer-events: auto`) +2. `game.input.enabled = false` 禁用 Phaser 输入(摇杆同时失效) + +关闭弹窗后反向恢复。弹窗期间 MapScene 不 pause(`scene.pause` 也不再需要,简化状态机)。 + +### 3.4 数据流 + +``` +spots.js(静态配置) ──┐ + ├→ MapScene(渲染地图/标记/触发) ──poi:reached→ PhotoModal(展示照片) +game-state.js ──state:changed──┴→ Hud(进度徽章) / PoiMarker(亮色标记) / TitleOverlay(进度概览) + ↑ localStorage 读写 +``` + +--- + +## 4. 游戏流程(状态流转) + +``` +打开页面 + → PreloadScene(加载进度条) + → TitleOverlay(手账封面) + ├─ 首次: "开始旅行" + └─ 有存档: "继续旅行 (5/12)" + "重新开始" + → MapScene 漫游(WASD/方向键/摇杆) + ├─ 进入未解锁景点触发区 → PhotoModal(拍立得, 首张) + │ → 关闭 → 解锁标记盖章动效 + HUD +1 → state:changed → 存档 + ├─ 进入已解锁景点触发区 → PhotoModal(重看, 不重复计数) + └─ 屏幕边缘箭头始终指向最近未解锁景点 + → 全部解锁 → CompleteOverlay(纪念页: 照片九宫格+统计) + └─ "继续漫游" → 回到地图(引导箭头消失, 标记全亮) +``` + +**触发防抖规则**:关闭弹窗后,玩家必须离开该景点触发区,才能再次触发该景点(防止原地连弹)。实现:记录 `activePoi` 与 `cooldown` 标记,overlap 回调中判断。 + +--- + +## 5. 数据模型 + +### 5.1 景点配置 `src/data/spots.js`(唯一内容源) + +```js +/** + * 景点清单:坐标为 SVG 底图 viewBox 坐标系单位。 + * 替换真实照片时:同名覆盖文件 + 更新 photos[].caption / story / date。 + */ +export const SPOTS = [ + { + id: 'dayanta', // 唯一 ID,存档键使用 + name: '大雁塔', + x: 1820, y: 1450, // 地图像素坐标(SVG viewBox 单位) + radius: 70, // 触发半径 + icon: '🗼', // 地图标记图标 + photos: [ // 1–5 张,弹窗叠沓翻看 + { src: 'assets/photos/dayanta_01.jpg', caption: '夕阳下的大雁塔' }, + ], + story: '国庆第 3 天,我们在塔下等灯光亮起。', + date: '10月3日', // 拍立得右下角手写日期 + }, + // ... 共 8–12 个景点;M1 内置 10 个景点占位数据完整跑通,后期随真实照片增删 +]; +``` + +### 5.2 存档结构 `src/game-state.js` + +- localStorage key:`xian-travel-progress` +- 结构:`{ unlocked: string[], updatedAt: number }` +- API:`loadProgress()` / `unlock(spotId)` / `resetProgress()` / `isUnlocked(spotId)` / `getSnapshot()` +- 隐私模式降级:localStorage 不可用时回退内存 Map,会话内有效,封面页不显示"继续" + +### 5.3 占位资源规范 + +| 资源 | 占位形态 | 替换路径 | +|---|---|---| +| 照片 | 4:3 渐变 SVG 色块(每个景点不同色相,含景点名水印) | 同名覆盖 + 更新 caption | +| 底图 | 简化 SVG 路网(城墙环/主干道/水系/POI 点) | 路网不动,叠加 AI 纹理层 | +| 角色 | 程序绘制方向指示标记 | AI 精灵表替换加载 key | +| 纪念页九宫格 | 复用照片占位色块 | 随照片自动生效 | + +--- + +## 6. 地图与美术制作管线 + +1. **参照层**:高德/OSM 截图仅作绘制参照,不进仓库(规避版权) +2. **SVG 路网层**(一次性人工绘制):明城墙环 + 护城河、二环、主干道网、渭河水系、POI 圆点;viewBox `0 0 3000 2100`;景点坐标在 SVG 内直接读取 +3. **AI 素材层**(文生图 API 生成):纸张纹理底图、罗盘装饰、地图边框、区域淡彩;作为 Phaser 叠加 image 层 +4. **角色精灵表**:AI 生成 Q 版小人,4 方向(上下左右)× 3 帧(idle/迈步),帧格 48×64,导出单张 spritesheet PNG +5. **POI 标记**:图标 + 状态样式——未解锁为灰调轮廓,已解锁为亮色 + 解锁瞬间"盖章"缩放动效(手账贴纸语义) +6. **视觉基调**:羊皮纸 `#f5ecd7` 底、棕褐 `#8a6a42` 线条、暖橙 `#e67e22` 强调色;中文字体优先 `Kaiti SC`/手写体回退 + +--- + +## 7. 玩法系统细节 + +### 7.1 触发 + +- Phaser `physics.add.overlap(player, poiZone)` 检测 +- 弹窗关闭后设 `cooldown[spotId] = true`,玩家离开该 Zone 的 `overlap` 消失事件清除 +- 已解锁景点重复进入仍可重看(不重复计数) + +### 7.2 进度 + +- 解锁即 `game-state.unlock(id)` → `state:changed` → HUD 徽章 `n/total` + 标记亮色 +- 达到 `total` 时额外广播 `game:complete` → CompleteOverlay + +### 7.3 方向引导 + +- 每帧计算:玩家位置 → 最近未解锁景点向量 +- 渲染屏幕边缘箭头(贴边固定距离 + 轻微浮动动画 + 箭头根部显示目标景点名标签,不显示数字距离——viewBox 单位无真实米义) +- 触发条件补充:目标已在相机视野内时箭头淡出(避免遮住玩家要看的内容) +- 全解锁后移除 + +### 7.4 重置 + +- 封面页"重新开始" → 原生 `confirm` 确认 → `resetProgress()` → 重载页面 + +### 7.5 纪念页 + +- 手账风全屏页:已解锁景点照片九宫格(点击可放大重看)+ 景点数/旅行天数统计(天数口径 = spots 配置中出现的不重复 `date` 数量)+ "继续漫游"按钮 + +--- + +## 8. UI 设计规格(DOM 层) + +### 8.1 拍立得弹窗 PhotoModal + +- 结构:全屏半透明遮罩 + 居中拍立得卡(白边相纸:照片区 4:3,下边加宽放手写描述) +- 旋转:卡片 `-2.5°` 随机 ±2°,投影 `0 6px 18px rgba(0,0,0,.35)` +- 多照片:向右错位叠沓(每张偏移 12px 旋转 +2°),点击当前卡片翻至下一张(CSS transform 过渡 300ms) +- 描述:手写体(`Kaiti SC` 回退),右下角日期角标 +- 关闭:遮罩点击 / ESC / 空格(PC);关闭按钮常显(移动端单手区) +- 照片缺失降级:显示"照片待补"占位卡(兼作换图核对清单) + +### 8.2 封面页 TitleOverlay + +- 手账封面质感:纸张底纹 + 标题"西安旅行记忆" + 装饰(罗盘/胶带贴纸) +- 首次进入:单一"开始旅行"大按钮 +- 有存档:显示"继续旅行 (n/12)" + 次级"重新开始" +- 进入游戏时整页翻页/淡出过渡(600ms) + +### 8.3 HUD + +- 右上角进度徽章:`🗺️ 5/12`,解锁时数字跳动动效 +- 移动端安全区内渲染 + +### 8.4 竖屏提示 + +- 检测 `window.innerHeight > window.innerWidth` 且为触摸设备时全屏显示"请旋转手机"插画页;回到横屏自动消失 + +--- + +## 9. 移动端适配 + +| 项 | 方案 | +|---|---| +| 缩放 | `Phaser.Scale.FIT` + `autoCenter: CENTER_BOTH`,设计分辨率 **1600×900** | +| 屏幕方向 | Web 竖屏显示旋转提示页;M6 Capacitor 阶段锁横屏 | +| 摇杆 | `rexVirtualJoystick` 左下角,半径 60,仅触摸设备显示,安全区偏移 | +| 触控 | CSS `touch-action: none`、`user-select: none`、`viewport-fit=cover` + `env(safe-area-inset-*)` | +| 字号 | DOM UI 层 `clamp()` 响应式字号 | +| 调试 | `vite --host` 局域网真机调试 | + +--- + +## 10. 项目结构 + +``` +xian-travel-game/ +├── public/assets/ +│ ├── maps/ # map-base.svg(路网) map-texture.png(AI 纸纹) 等分层素材 +│ ├── photos/ # 占位 SVG → 真实照片(景点id_序号.jpg) +│ ├── sprites/ # player.png(Q版精灵表) +│ └── audio/ # 预留(本期不实现) +├── src/ +│ ├── main.js # Phaser 配置与启动(FIT/1600×900/插件注册) +│ ├── event-bus.js # 自写事件桥 on/once/off/emit +│ ├── game-state.js # localStorage 存档模块 +│ ├── data/spots.js # 景点配置(唯一内容源) +│ ├── scenes/ +│ │ ├── PreloadScene.js # 进度条 + 资源加载 + 失败降级 +│ │ └── MapScene.js # 地图/角色/摇杆/触发/引导箭头/标记状态 +│ ├── objects/ +│ │ ├── Player.js # 角色移动与动画(键盘+摇杆,斜向归一化) +│ │ └── PoiMarker.js # 景点标记(图标/状态样式/盖章动效) +│ └── ui/ # DOM UI 层 +│ ├── dom-layer.js # 层管理(挂载/互斥/过渡) +│ ├── TitleOverlay.js +│ ├── PhotoModal.js +│ ├── Hud.js +│ ├── CompleteOverlay.js +│ └── rotate-hint.js # 竖屏提示 +├── index.html # #game + #ui 容器 + 移动端 meta +├── vite.config.js # base './' / host:true / assetsInlineLimit:0 +└── package.json # phaser + phaser3-rex-plugins(Capacitor 依赖 M6 再装) +``` + +**编码规范**:每个文件顶部文件头注释(用途/职责/主要流程);函数级 Google 风格注释(中文);ESM 模块。 + +--- + +## 11. 错误处理 + +| 场景 | 处理 | +|---|---| +| 资源加载失败 | PreloadScene `load.on('loaderror')` 记录并以内联占位图降级,不中断 | +| localStorage 不可用 | game-state 内存降级;封面页隐藏"继续旅行" | +| 照片文件缺失 | PhotoModal"照片待补"占位卡 | +| resize / orientationchange | `scale.refresh()` + DOM 层重排 | +| 触发风暴 | 冷却机制(见 7.1) | + +--- + +## 12. 测试与验收标准 + +| # | 验收项 | 通过标准 | +|---|---|---| +| 1 | PC 移动 | WASD/方向键八方向,斜向速度归一化不超速 | +| 2 | 触发弹窗 | 进入景点弹拍立得;ESC/空格/点击遮罩/关闭按钮可关;无原地连弹 | +| 3 | 移动端 | 摇杆可移动;弹窗可关;竖屏出现旋转提示,回横屏恢复 | +| 4 | 进度持久化 | 刷新/重开浏览器进度保留;重新开始可用并清档 | +| 5 | 引导 | 边缘箭头指向最近未解锁景点;目标进入视野时箭头淡出;全解锁后消失 | +| 6 | 纪念页 | n/n 全解锁出现;九宫格可点开放大;继续漫游可回地图 | +| 7 | 占位降级 | 断照片/断资源不白屏,显示占位卡 | +| 8 | 性能 | 首屏 < 3s(WiFi);底图 < 1.5MB;单照片 < 300KB;60fps 漫游(中端手机) | + +验收方式:M1/M2 结束各跑一遍 PC + 真机清单;`npm run build` 后本地 preview 冒烟。 + +--- + +## 13. 交付路线(里程碑) + +| 阶段 | 内容 | 出口标准 | +|---|---|---| +| M1 可玩核心 | 项目脚手架 + 占位地图/角色 + 移动 + 触发 + 拍立得弹窗 | 验收 1/2/7 | +| M2 完整体验 | 封面页 + 存档/进度 + 引导箭头 + 纪念页 + 摇杆 + 竖屏提示 | 验收 3/4/5/6 | +| M3 手账美化 | AI 纸纹/罗盘素材 + Q 版精灵表 + 盖章动效 + 封面精化 | 视觉走查 | +| M4 真实内容 | 照片替换(附《照片替换指南》)+ 坐标微调 | 验收 7/8 | +| M5 Web 发布 | build + Vercel/GitHub Pages(`base: './'`) | 手机打开链接可玩 | +| M6 APK(后置) | 安装 Capacitor 依赖 + init + 打包(锁横屏) | 真机安装可玩 | + +> M1–M5 为本期承诺范围;M6 在 Web 版验收后单独启动。 + +--- + +## 14. 照片替换指南(后期运营,随 M4 交付) + +1. **命名规范**:`public/assets/photos/<景点id>_<两位序号>.jpg`(如 `dayanta_01.jpg`) +2. **压缩规范**:宽度 ≤ 1200px,单张 ≤ 300KB,优先 WebP(文件名可保留 .jpg 后缀或统一改并同步 spots.js) +3. **配置更新**:在 `spots.js` 对应景点的 `photos[]` 增删条目、写 caption;`story`/`date` 按手账文案填写 +4. **自检**:跑一遍游戏,缺失照片会显示"照片待补"占位卡,逐个补齐即可 + +--- + +## 15. 与原 overview.md 的差异说明 + +| 原方案 | 细化后 | 原因 | +|---|---|---| +| PhotoScene 独立场景 + scene.pause/launch | DOM 层 PhotoModal + 输入禁用 | 拍立得/中文排版/动画是 CSS 强项,Phaser UI 弱项 | +| 三场景 | 两场景(Preload/Map) | 弹窗职责外移,简化状态机 | +| 图片地图 + 手动取坐标 | SVG 路网 + viewBox 坐标 | 位置准确、可分层叠加 AI 素材、坐标在源文件中直接可读 | +| 1280×720 | 1600×900 | 地图比例 3000×2100 匹配 10:7,横屏视野更舒展 | +| 无开场/进度/引导/纪念页 | 封面页 + 进度徽章 + 边缘箭头 + 纪念页 | 需求澄清新增的游戏化决策 | +| 一次性全部依赖安装 | Capacitor 依赖 M6 再装 | Web 优先交付,减少前期装机量 | diff --git a/index.html b/index.html new file mode 100644 index 0000000..c37f6c2 --- /dev/null +++ b/index.html @@ -0,0 +1,29 @@ + + + + + + + 西安旅行记忆 + + + + +
+ + +
+ + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9cebd37 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1248 @@ +{ + "name": "xian-travel-game", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "xian-travel-game", + "version": "0.1.0", + "dependencies": { + "phaser": "^3.80.0", + "phaser3-rex-plugins": "^1.80.0" + }, + "devDependencies": { + "vite": "^5.4.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.10", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.7.tgz", + "integrity": "sha512-GFF2tHk0NqYVObrK0+/ps/LEXsV/v2q6Lyiy7mZr+Ej24fpQScvqK2ziz9NHeEQYnKWKym38Ml7dZbPtC9tuAw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.7.tgz", + "integrity": "sha512-BOymcXJFYYXqluDidj6sLelNjXruC3u1BqVxLn2OPCXH9xaPSOTH+MS4uychsGI1NU7tJoiv9KvqijDHFe+hhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.7.tgz", + "integrity": "sha512-jgVpGweIbls2H5jfBB70tnX52abMd5pJr3xmtoDILx6JdmGl+Un+tMb9R+MbHPVB46tTBpY8emtn7M71SnQhEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.7.tgz", + "integrity": "sha512-AVMpt94mrXH7WjyWKmd9nT4RC5yM0AN3Lt6A89xVIsuLDi3dDX6dxMytLbW36FlppY9JsoqUoS3LUUpUrYxiMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.7.tgz", + "integrity": "sha512-Nje29c/MyajxRNFGU05oyeTDS6SWdMg/wWMSlZSBTBju5BKz2NVc0kFiFSQGzRELNiB+CBk/ZvAVsWsENgjGag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.7.tgz", + "integrity": "sha512-xxgN5/kMzCNz/NYJ4t49B0/WS6InNlWv6xvgwUQqNnWM9WJMMwMUGWrAxdNEgYWdHL0sMnuh8I87StGrIMTA+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.7.tgz", + "integrity": "sha512-5U/9gVn/X657I8+NHoHO9Wnfyd0lEXwqbslobRoB9TC8SwYzOitcIr6TLE7531Q/NWs3L4mb+YOGzlf3vHIeYw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.7.tgz", + "integrity": "sha512-hcDS0a/Y8ggM3dcOfzH7UpSaogCViWTtNDSl0EXpWIj8g8/r86ITqptjil2hayTXS0Umzb+Yt3mjgpQVFVf2+A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.7.tgz", + "integrity": "sha512-awbAr4N7Qd/yjZAQBEEPzgcPveYG2Gi5L6gq1/Bp+3SMRjn5F68Wv1zKsntzvvoioBR6yKvhLtWqu11am1TsxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.7.tgz", + "integrity": "sha512-Ut7xQJfeLhzu2yN3rHzVvL9ccyPJvN4aCdP1V7hJ5lGxX/eCzgB9SiMk/Md3J+rWWZgvoYsDW4BUFggJr32pBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.7.tgz", + "integrity": "sha512-trqoni/vHkuM5oo5iQhoKPh6fjjD7E80pMgCXWgGxU8vSRPUlwvG8GeYvEEcB6eJAxNa99WH9iVHPHelgtKiHA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.7.tgz", + "integrity": "sha512-gvlUy0XFIwtUWiQmhc+4GT2Knm3KosinpUVhr7CtTFA78gFDnE5NWwMPzHrJsYpKkh2LlY7yvBIboN1FAV6Xeg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.7.tgz", + "integrity": "sha512-8yS/kSXTJ/47Z62JW+C2AJ3vd/8zZw016vFjtNpZwmZlwJumLua4s1i78dkYuSSqQ+0aIMxJcBp/4uIwp2ehjA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.7.tgz", + "integrity": "sha512-mxVSKvvty0hOP1AFo/l4Kpv3i3HK0zx4rYUrirZDToYlWhS3xzmIkq3vUJ7rtrM2cPyXveDyDT6M1FfXZ9hTRA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.7.tgz", + "integrity": "sha512-lEse1zra1CagM+uHH2bl28GInDsZZ45NJzHei2OMaXQFzZWKBO1hFo07rS0xB6ayV4WU360UrSmPDSyCMpYsUQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.7.tgz", + "integrity": "sha512-d/cqCoiG9WMZ5VuJZ5Qdlv9g97QyoGwnmvHccvGhKSacTy7xFrAS+jMcJSBdMNOw1oacSkl6p7K6O/DRYTKL3g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.7.tgz", + "integrity": "sha512-kVEA//CeDOZZP1XCH2JBgqXb9+ALlwuA/4civaazMVdgzaSQC9WM38w65zG/LLk0lA7F9Od19CSBZW7J9mCbNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.7.tgz", + "integrity": "sha512-14oLfDsOSr0TSntT/34jRg/V8B2XW/Jomh9zX9CjIM9gwwNlI1OCymS/GVn0DV32h67eRf7U0GGem0rvfXgNwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.7.tgz", + "integrity": "sha512-dFVtPfjZqAnUPpPY4Bfg+87yo1IBW9HiY8iCFtg49Pn46MfZTebSHZh8PL6SOsgyt9LQP8Nj/sE6jm3aWpySmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.7.tgz", + "integrity": "sha512-Dm9CZ2SM3u2NgvBj4awW/gGqkOwzH8f6MSYIJE6+bKcKLI+T0+LhpD6vtetTfrIIx+rLXDf5RJqBP6v7V5O+wg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.7.tgz", + "integrity": "sha512-oaxsvS2mL6+78741CT2HCORBvy1USg3Wqpt5qvZwy3aMXRMl4zxyDo8maeFRWJkF3dp0Wbi71fKH5Gr9YI3vYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.7.tgz", + "integrity": "sha512-UKZz1Y4mxUqkWh60uH7B1aZn6UcAimPk4ipRktpb3wfZq7JrzZYyAXTwiafkb3T+kd5P3Sf+vzcRdsDks4WtjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.7.tgz", + "integrity": "sha512-WWkSuC1J+mZJnsDh0mRaCAFSWfQdl6y3fDjZeWElI6zHo8CcOSG5lY3yurKJjEq+FrX0WD60sFrVVpbHqzMmrw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.7.tgz", + "integrity": "sha512-u9bp2yXCI3/BUjUxY6uWknAY32pfvvDCPysyUTySotx0dj3ydlFHrRQYfn/b/kZgnfjyEwySMtI7Ga054uiysQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.7.tgz", + "integrity": "sha512-EhX6N3XPMEnVU5cRfNgzPOKcSucsbu3nNNWuoAaEuKIqmddBKHN5b9d4rPe65T1I+FaOT176JKiZNsUBp3rqbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/graphology": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/graphology/-/graphology-0.25.4.tgz", + "integrity": "sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==", + "license": "MIT", + "dependencies": { + "events": "^3.3.0", + "obliterator": "^2.0.2" + }, + "peerDependencies": { + "graphology-types": ">=0.24.0" + } + }, + "node_modules/graphology-types": { + "version": "0.24.8", + "resolved": "https://registry.npmmirror.com/graphology-types/-/graphology-types-0.24.8.tgz", + "integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==", + "license": "MIT", + "peer": true + }, + "node_modules/i18next": { + "version": "22.5.1", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-22.5.1.tgz", + "integrity": "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.6" + } + }, + "node_modules/i18next-http-backend": { + "version": "2.7.3", + "resolved": "https://registry.npmmirror.com/i18next-http-backend/-/i18next-http-backend-2.7.3.tgz", + "integrity": "sha512-FgZxrXdRA5u44xfYsJlEBL4/KH3f2IluBpgV/7riW0YW2VEyM8FzVt2XHAOi6id0Ppj7vZvCZVpp5LrGXnc8Ig==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/obliterator": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/obliterator/-/obliterator-2.0.5.tgz", + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", + "license": "MIT" + }, + "node_modules/papaparse": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/papaparse/-/papaparse-5.7.0.tgz", + "integrity": "sha512-qBGxg/7Q3Kl9Wfhrz2Z74UnvnHTXLNG6jmKJFeBvP2+y4lV7So+7SR62+Zd47JvdrCkX+nDcnr0ObPzek/+6RA==", + "license": "MIT" + }, + "node_modules/phaser": { + "version": "3.90.0", + "resolved": "https://registry.npmmirror.com/phaser/-/phaser-3.90.0.tgz", + "integrity": "sha512-/cziz/5ZIn02uDkC9RzN8VF9x3Gs3XdFFf9nkiMEQT3p7hQlWuyjy4QWosU802qqno2YSLn2BfqwOKLv/sSVfQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1" + } + }, + "node_modules/phaser3-rex-plugins": { + "version": "1.80.20", + "resolved": "https://registry.npmmirror.com/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.20.tgz", + "integrity": "sha512-XZLUy74r1jUaRWpGplRhhA+wEeOvnNb0qlhlyMtwdwZwIlCA7C7WtgvynOB/PyVrzS09cUBwEFyG+A3P5fL+Uw==", + "license": "MIT", + "dependencies": { + "dagre": "^0.8.5", + "eventemitter3": "^3.1.2", + "graphology": "^0.25.4", + "i18next": "^22.5.1", + "i18next-http-backend": "^2.5.2", + "js-yaml": "^4.1.0", + "mustache": "^4.2.0", + "papaparse": "^5.4.1", + "webfontloader": "^1.6.28" + } + }, + "node_modules/phaser3-rex-plugins/node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.30", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.64.7", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.64.7.tgz", + "integrity": "sha512-aCiJONoZp0lNGJ6sYqspLkh6UXaWEey/7Ko2TZyXQ4kgzAhDUxAgbt/oulXKmD+zFHXw1k+RF6ZZWOrjp7DZnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.7", + "@rollup/rollup-android-arm64": "4.64.7", + "@rollup/rollup-darwin-arm64": "4.64.7", + "@rollup/rollup-darwin-x64": "4.64.7", + "@rollup/rollup-freebsd-arm64": "4.64.7", + "@rollup/rollup-freebsd-x64": "4.64.7", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.7", + "@rollup/rollup-linux-arm-musleabihf": "4.64.7", + "@rollup/rollup-linux-arm64-gnu": "4.64.7", + "@rollup/rollup-linux-arm64-musl": "4.64.7", + "@rollup/rollup-linux-loong64-gnu": "4.64.7", + "@rollup/rollup-linux-loong64-musl": "4.64.7", + "@rollup/rollup-linux-ppc64-gnu": "4.64.7", + "@rollup/rollup-linux-ppc64-musl": "4.64.7", + "@rollup/rollup-linux-riscv64-gnu": "4.64.7", + "@rollup/rollup-linux-riscv64-musl": "4.64.7", + "@rollup/rollup-linux-s390x-gnu": "4.64.7", + "@rollup/rollup-linux-x64-gnu": "4.64.7", + "@rollup/rollup-linux-x64-musl": "4.64.7", + "@rollup/rollup-openbsd-x64": "4.64.7", + "@rollup/rollup-openharmony-arm64": "4.64.7", + "@rollup/rollup-win32-arm64-msvc": "4.64.7", + "@rollup/rollup-win32-ia32-msvc": "4.64.7", + "@rollup/rollup-win32-x64-gnu": "4.64.7", + "@rollup/rollup-win32-x64-msvc": "4.64.7", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/webfontloader": { + "version": "1.6.28", + "resolved": "https://registry.npmmirror.com/webfontloader/-/webfontloader-1.6.28.tgz", + "integrity": "sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8d3d65f --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "xian-travel-game", + "private": true, + "version": "0.1.0", + "description": "西安旅行记忆互动地图游戏 —— Phaser 地图层 + DOM UI 层混合架构", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "phaser": "^3.80.0", + "phaser3-rex-plugins": "^1.80.0" + }, + "devDependencies": { + "vite": "^5.4.0" + } +} diff --git a/public/assets/maps/map-base.svg b/public/assets/maps/map-base.svg new file mode 100644 index 0000000..f945ba2 --- /dev/null +++ b/public/assets/maps/map-base.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 大明宫遗址公园 + + + 兴庆宫公园 + + + + + + 渭 河 + + + + 灞河 + + + + 浐河 + + + + 沣河 + + + + 曲江池 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 明城墙 + + + + 临 潼 + 曲江新区 + 高新区 + 灞桥 + + + + + + + + + + 北 + + + + + 西安 · 旅行手账地图 + XI'AN TRAVEL JOURNAL MAP + + + + + + —— 走进景点,翻开你的旅行手账 —— + + + + + + + + + + + + diff --git a/public/assets/photos/bingmayong_01.svg b/public/assets/photos/bingmayong_01.svg new file mode 100644 index 0000000..fabca3c --- /dev/null +++ b/public/assets/photos/bingmayong_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + ⚔️ + 一号坑的军阵全景 + · 占位照片 · + diff --git a/public/assets/photos/bingmayong_02.svg b/public/assets/photos/bingmayong_02.svg new file mode 100644 index 0000000..1908eec --- /dev/null +++ b/public/assets/photos/bingmayong_02.svg @@ -0,0 +1,16 @@ + + + + + + + + + + ⚔️ + 跪射俑的侧脸 + · 占位照片 · + diff --git a/public/assets/photos/buyecheng_01.svg b/public/assets/photos/buyecheng_01.svg new file mode 100644 index 0000000..9733cf9 --- /dev/null +++ b/public/assets/photos/buyecheng_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🏮 + 华灯初上的步行街 + · 占位照片 · + diff --git a/public/assets/photos/buyecheng_02.svg b/public/assets/photos/buyecheng_02.svg new file mode 100644 index 0000000..71dcd97 --- /dev/null +++ b/public/assets/photos/buyecheng_02.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🏮 + 不倒翁小姐姐 + · 占位照片 · + diff --git a/public/assets/photos/chengqiang_01.svg b/public/assets/photos/chengqiang_01.svg new file mode 100644 index 0000000..08169fd --- /dev/null +++ b/public/assets/photos/chengqiang_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🧱 + 城墙上骑行的傍晚 + · 占位照片 · + diff --git a/public/assets/photos/daminggong_01.svg b/public/assets/photos/daminggong_01.svg new file mode 100644 index 0000000..d6b2bb2 --- /dev/null +++ b/public/assets/photos/daminggong_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🏛️ + 含元殿的巨大夯土台 + · 占位照片 · + diff --git a/public/assets/photos/dayanta_01.svg b/public/assets/photos/dayanta_01.svg new file mode 100644 index 0000000..abe3012 --- /dev/null +++ b/public/assets/photos/dayanta_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🗼 + 夕阳下的大雁塔 + · 占位照片 · + diff --git a/public/assets/photos/dayanta_02.svg b/public/assets/photos/dayanta_02.svg new file mode 100644 index 0000000..d151439 --- /dev/null +++ b/public/assets/photos/dayanta_02.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🗼 + 音乐喷泉开场的瞬间 + · 占位照片 · + diff --git a/public/assets/photos/huaqingchi_01.svg b/public/assets/photos/huaqingchi_01.svg new file mode 100644 index 0000000..117171b --- /dev/null +++ b/public/assets/photos/huaqingchi_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + ♨️ + 骊山脚下的温泉古苑 + · 占位照片 · + diff --git a/public/assets/photos/huiminjie_01.svg b/public/assets/photos/huiminjie_01.svg new file mode 100644 index 0000000..2f0dfc7 --- /dev/null +++ b/public/assets/photos/huiminjie_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🥟 + 滋滋冒油的烤肉串 + · 占位照片 · + diff --git a/public/assets/photos/huiminjie_02.svg b/public/assets/photos/huiminjie_02.svg new file mode 100644 index 0000000..591cc2c --- /dev/null +++ b/public/assets/photos/huiminjie_02.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🥟 + 排长队的甑糕摊 + · 占位照片 · + diff --git a/public/assets/photos/shanbwy_01.svg b/public/assets/photos/shanbwy_01.svg new file mode 100644 index 0000000..6e27758 --- /dev/null +++ b/public/assets/photos/shanbwy_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🏺 + 排队两小时的入口 + · 占位照片 · + diff --git a/public/assets/photos/shanbwy_02.svg b/public/assets/photos/shanbwy_02.svg new file mode 100644 index 0000000..6b74611 --- /dev/null +++ b/public/assets/photos/shanbwy_02.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🏺 + 唐代鎏金舞马衔杯银壶 + · 占位照片 · + diff --git a/public/assets/photos/xiaoyanta_01.svg b/public/assets/photos/xiaoyanta_01.svg new file mode 100644 index 0000000..cc458f9 --- /dev/null +++ b/public/assets/photos/xiaoyanta_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🛕 + 晨钟里的古塔剪影 + · 占位照片 · + diff --git a/public/assets/photos/zhonglou_01.svg b/public/assets/photos/zhonglou_01.svg new file mode 100644 index 0000000..772f423 --- /dev/null +++ b/public/assets/photos/zhonglou_01.svg @@ -0,0 +1,16 @@ + + + + + + + + + + 🏯 + 夜晚的钟楼灯火辉煌 + · 占位照片 · + diff --git a/public/assets/textures/paper.jpg b/public/assets/textures/paper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e7b4c3d2234d84cf9b644f911bc359293e489741 GIT binary patch literal 176626 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�^)SSZRZi9~!H(ZntK906lFSzIfMJI3!1otROjH zdyy#6^c*7=VuL`E>BbZ))-wevwg_g~q2&_TkZzH{4C?(76wY_Gsxg(rFnE&y`asc- zWXBSjAA8USO18KMTH|0qgqb?x`*@8R*|hsnL=UEgRc;N(VHJ$`(^o?dkoE}X0D$q! z5&YJvL*={1W=f7R`FqnIfYkPpXp|i?#=)?SF021q?qCCkd;qx-0BzjTKA=Z@ePzy% z$ssCHCJF84864Ke>_Ie19|=_C4n;m_)ebWwtRz(#o7qe?CSBPr7Sfp_S4Rj?z4l;Q z1EZX?qyfV2Hi-crka5(c%fT9H2 zj%G`Zc<=}$l5N|p=oG?}Yng6g>9LN38F=l7Wi2?>wjLls?B8z}lf;{gRLNHiEBV9T8YNIjrw z8`>0YNUVP)2Z{@0pU&J+mFAYBF`)XPZNLjaikd(P_jM+4wE8G3Z=8XUPUf<_dYY7a zhu`N#GOiF@Wc_kD5^1%^XF zSy(e!oXi{)Dnxb@#A=UaNe7mAlmkqvHW_0+6kf}dT_P|YW1YE~_oh7#8T6^ihf18( zoj^(tqCX8KiS@jGoJy5h;N}X2|2eJEIwM(=Bc<^q1B>wp#vMy3B|T$sg8&Mn(bZje z(DVjsoOMkxM#M|ylbfG5x;wIzB0zzj6e2F;JqL9NJ1Mp4L?E^56JygPapf*mGt)eD zj6s<`COQM_ac^2frUYdruP@4I8nBdm7zn+Y+zj5JJv$tX^zY;E1+9^EN&*^2%y~FO zS>+ynYz(@nhUBHhOOOs}`$1kk9-iXSXMPkGszA^cnma5cGIAM=%8Ceg586x64n-k3 z*0ZC+jEC#%U|lhFFJWedj3x_}t}4*XM&}~YfyUgCi1PMgvmfC80&;DAKpEP7pJ&8X z5Vz5z0%kUPl*{OhJTWk06qqV6)CvPxyd%1ToO_BFQpB z2)UN7;v=Hy9D%A*i9}-hKr=+9eM0T?e0q)=ZDQPa4=2{cGj!g9iryYG9p~IUBP?$e zyhtWCK|3m;5K;LGDO|+JWQziPIV-{-;(pM)C>jkNp-~rQ$e$A>VEV2ibwnw%$-G3Y zE~KLOyaKI`#1AmQ_6+XzjGT}LYg|;H2+lIgO@?9w85uv#v}QVByi275^@|||H$@D4 zOGQ-@n7}I1U#@H`XVK5ii{MHSlu?u=<4R4@;5|lJJ4eUJY?4X{fD#ymumvRAp-?FE4#>R1hXQ1UmzHXgQ9EqNzX?TUI%a+cxiRk=x=;lQ zm2fkA0yFeY_EG&1v5<2^*sbT#I5M{VhAw`pQ1k#4USC*%FUA7XG3q1}qe|x5APfDt ziIE;6lo=kk`gc$-NnT%ukkWOxX-dqQa(*L))z=iP3z9HOX=R`t^a^EJyT67S;wcB~ zFg$HM9@Qq`m~ok$Jf96Yf>Ka;pH4bs%}5F{un)J=q{^yDT}&oZ&0&iL zWJ}OI90KW+K0tAe$mK*)3SRKL$sN$Df=Cu`2sEv?2|?$TcUHlE7(mpz8fMXvVZB}| zBELx>T@SJ-7f?K1kD=gVQ|9h5Za6-uNo0yseZ2J)c7#C(EW0!l;K1!1ZA@PWLnaj1+>7BC%Fpz#hKBI!US z675v}E^O^S%7)ebhD!ogqyJ(tO0ls8+Rn~}Expn&8G`kgpV!1lG#m6m9g%}qxcxburWSB$3bIb; zlPbbnE{@hot&k&jK~imgxd5O+r}jkjSh36Da585H?0#*kyry^`c8+HmlUQ^S6wyXQ z$wOSpgFgP11d78PBt$3&9x#D@L#zLsX(!oNzg~;iQmq(z#$8UzYBpz8MHe66+|A%> z>sx;Y*)&B(v!#?IJxi)6>VD0J*IQ>2p;CBl1B1at9|FO`s$e0*Nv2*hVNVzuI+ING zQ=pyep?N#k%3WW9T9`2wZB1tCd`z!~PAB8Hk_Bo!0?)xo;=~+ee<+G6r7lcsN8c>`Ei&W+}cL|s%FN7~B zOi)4U0`BiZbtH0N%v~$vu)=`HIg5pLQI5IizfXJi5Zc2Md3NwKh7bWE!Ns)HvZ|aO boGEGNPCox95P)O<0IIDU+n25R(ENV@li08c literal 0 HcmV?d00001 diff --git a/scripts/gen-placeholder-photos.mjs b/scripts/gen-placeholder-photos.mjs new file mode 100644 index 0000000..4c29413 --- /dev/null +++ b/scripts/gen-placeholder-photos.mjs @@ -0,0 +1,116 @@ +/** + * @fileoverview 占位照片生成脚本。 + * + * 文件用途: + * - 依据 src/data/spots.js 的照片清单,批量生成 4:3 手账风占位 SVG, + * 供照片未就绪期的开发与演示使用。 + * + * 核心职责: + * 1. 读取景点配置,为每张照片生成 `public/assets/photos/_<序号>.svg`; + * 2. 占位视觉:暖色渐变 + 景点图标 + 名称与 caption 水印 + 虚线相框; + * 3. 文件已存在时跳过(--force 强制重生成)。 + * + * 主要流程: + * node scripts/gen-placeholder-photos.mjs [--force] → 遍历 spots → + * 写出 SVG → 控制台汇总生成结果。 + * + * 用法: + * node scripts/gen-placeholder-photos.mjs # 仅生成缺失文件 + * node scripts/gen-placeholder-photos.mjs --force # 强制重新生成全部 + */ + +import { mkdirSync, writeFileSync, existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import { SPOTS } from '../src/data/spots.js'; + +/** 脚本所在目录。 */ +const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)); +/** 项目根目录。 */ +const PROJECT_ROOT = resolve(SCRIPT_DIR, '..'); +/** 照片输出目录。 */ +const OUT_DIR = resolve(PROJECT_ROOT, 'public/assets/photos'); + +/** 占位卡宽度(像素)。 */ +const W = 1200; +/** 占位卡高度(像素,4:3)。 */ +const H = 900; + +/** + * 按景点 id 生成稳定的色相(0-360)。 + * + * @param {string} id 景点 id。 + * @returns {number} 色相角度。 + */ +function hueOf(id) { + let hash = 0; + for (const ch of id) { + hash = (hash * 31 + ch.charCodeAt(0)) % 360; + } + return hash; +} + +/** + * 生成单张占位照片 SVG 内容。 + * + * @param {Object} spot 景点配置。 + * @param {Object} photo 照片配置(含 caption)。 + * @returns {string} SVG 文本。 + */ +function buildSvg(spot, photo) { + const hue = hueOf(spot.id); + const icon = spot.icon; + const escapedCaption = photo.caption + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>'); + return ` + + + + + + + + + ${icon} + ${escapedCaption} + · 占位照片 · + +`; +} + +/** + * 脚本入口:遍历景点生成占位照片。 + * + * @returns {Promise} 完成后输出汇总。 + */ +async function main() { + const force = process.argv.includes('--force'); + mkdirSync(OUT_DIR, { recursive: true }); + + let created = 0; + let skipped = 0; + for (const spot of SPOTS) { + spot.photos.forEach((photo, index) => { + const fileName = photo.src.split('/').pop(); + const outPath = resolve(OUT_DIR, fileName); + if (!force && existsSync(outPath)) { + skipped += 1; + return; + } + writeFileSync(outPath, buildSvg(spot, photo), 'utf8'); + created += 1; + console.log(`[photos] 生成 ${fileName}`); + }); + } + console.log(`[photos] 完成:新建 ${created} 张,跳过已存在 ${skipped} 张`); +} + +main().catch((err) => { + console.error('[photos] 生成失败:', err); + process.exit(1); +}); diff --git a/src/data/spots.js b/src/data/spots.js new file mode 100644 index 0000000..2a77b82 --- /dev/null +++ b/src/data/spots.js @@ -0,0 +1,156 @@ +/** + * @fileoverview 景点配置(唯一内容源)。 + * + * 文件用途: + * - 声明全部景点的坐标、照片、文案等静态配置,供 Phaser 层与 DOM UI 层共同消费。 + * + * 核心职责: + * 1. 定义 10 个西安景点的触发坐标(与 public/assets/maps/map-base.svg 的 + * viewBox 坐标系一致:0 0 3000 2100); + * 2. 声明每个景点的占位照片路径与手账文案(story / date / caption); + * 3. 替换真实照片时只需更新本文件与照片文件,不改任何代码逻辑。 + * + * 主要流程: + * PreloadScene 读取 photos[].src 加载图片 → MapScene 按 x/y/radius 创建 + * 触发区与标记 → PhotoModal 读取 name/story/date 渲染拍立得卡片。 + */ + +/** + * 景点清单。坐标单位 = SVG 底图 viewBox 单位(约 8.3 米/单位)。 + * + * @type {Array} 景点配置数组。 + */ +export const SPOTS = [ + { + id: 'zhonglou', + name: '钟楼', + x: 1300, + y: 1150, + radius: 60, + icon: '🏯', + photos: [{ src: 'assets/photos/zhonglou_01.svg', caption: '夜晚的钟楼灯火辉煌' }], + story: '站在钟楼下,车流绕着转盘缓缓流淌,灯光把飞檐勾成金色。', + date: '10月1日', + }, + { + id: 'huiminjie', + name: '回民街', + x: 1140, + y: 1060, + radius: 60, + icon: '🥟', + photos: [ + { src: 'assets/photos/huiminjie_01.svg', caption: '滋滋冒油的烤肉串' }, + { src: 'assets/photos/huiminjie_02.svg', caption: '排长队的甑糕摊' }, + ], + story: '从街头吃到街尾:泡馍、烤肉、甑糕、石榴汁,一样都没落下。', + date: '10月1日', + }, + { + id: 'chengqiang', + name: '明城墙 · 永宁门', + x: 1300, + y: 1370, + radius: 65, + icon: '🧱', + photos: [{ src: 'assets/photos/chengqiang_01.svg', caption: '城墙上骑行的傍晚' }], + story: '租了辆自行车在城墙上绕圈,晚风把护城河的波光吹进了回忆里。', + date: '10月2日', + }, + { + id: 'daminggong', + name: '大明宫遗址', + x: 1330, + y: 780, + radius: 65, + icon: '🏛️', + photos: [{ src: 'assets/photos/daminggong_01.svg', caption: '含元殿的巨大夯土台' }], + story: '站在含元殿遗址上,想象千年前万国来朝的场面,只剩下风声。', + date: '10月2日', + }, + { + id: 'xiaoyanta', + name: '小雁塔', + x: 960, + y: 1410, + radius: 60, + icon: '🛕', + photos: [{ src: 'assets/photos/xiaoyanta_01.svg', caption: '晨钟里的古塔剪影' }], + story: '比大雁塔安静太多,塔影落在草地上,时间好像慢了半拍。', + date: '10月3日', + }, + { + id: 'shanbwy', + name: '陕西历史博物馆', + x: 1680, + y: 1560, + radius: 65, + icon: '🏺', + photos: [ + { src: 'assets/photos/shanbwy_01.svg', caption: '排队两小时的入口' }, + { src: 'assets/photos/shanbwy_02.svg', caption: '唐代鎏金舞马衔杯银壶' }, + ], + story: '国宝多到看不过来,讲解器里传来一千三百年的回声。', + date: '10月3日', + }, + { + id: 'dayanta', + name: '大雁塔', + x: 1900, + y: 1650, + radius: 70, + icon: '🗼', + photos: [ + { src: 'assets/photos/dayanta_01.svg', caption: '夕阳下的大雁塔' }, + { src: 'assets/photos/dayanta_02.svg', caption: '音乐喷泉开场的瞬间' }, + ], + story: '国庆第 3 天,我们在塔下等灯光亮起,喷泉和人群一起欢呼。', + date: '10月3日', + }, + { + id: 'buyecheng', + name: '大唐不夜城', + x: 2060, + y: 1820, + radius: 70, + icon: '🏮', + photos: [ + { src: 'assets/photos/buyecheng_01.svg', caption: '华灯初上的步行街' }, + { src: 'assets/photos/buyecheng_02.svg', caption: '不倒翁小姐姐' }, + ], + story: '整条街都是灯火和汉服,恍惚间不知道今夕是何年。', + date: '10月4日', + }, + { + id: 'huaqingchi', + name: '华清宫', + x: 2560, + y: 470, + radius: 65, + icon: '♨️', + photos: [{ src: 'assets/photos/huaqingchi_01.svg', caption: '骊山脚下的温泉古苑' }], + story: '杨贵妃泡过的温泉早已冷却,骊山的缆车却把整座西安看了一遍。', + date: '10月5日', + }, + { + id: 'bingmayong', + name: '兵马俑', + x: 2820, + y: 240, + radius: 70, + icon: '⚔️', + photos: [ + { src: 'assets/photos/bingmayong_01.svg', caption: '一号坑的军阵全景' }, + { src: 'assets/photos/bingmayong_02.svg', caption: '跪射俑的侧脸' }, + ], + story: '世界第八大奇迹,震撼到说不出话,千人千面是真的。', + date: '10月5日', + }, +]; + +/** + * 玩家出生点(城墙内西南角附近,远离首个引导目标)。 + * + * @type {{x: number, y: number}} 出生坐标。 + */ +export const SPAWN = { x: 1120, y: 1240 }; diff --git a/src/event-bus.js b/src/event-bus.js new file mode 100644 index 0000000..d93b486 --- /dev/null +++ b/src/event-bus.js @@ -0,0 +1,97 @@ +/** + * @fileoverview 跨层事件桥(EventBus)。 + * + * 文件用途: + * - 提供 Phaser 游戏层与 DOM UI 层之间的解耦通信通道。 + * + * 核心职责: + * 1. 极简发布订阅实现(on / once / off / emit); + * 2. 约定事件协议(见 docs/spec.md 3.2 节): + * - poi:reached Phaser → UI 玩家进入景点触发区,请求弹窗 + * - ui:closed UI → Phaser 弹窗/封面关闭,恢复游戏输入 + * - state:changed 存档 → 两层 解锁进度变化,刷新 HUD 与地图标记 + * - game:complete 存档 → UI 全部解锁,弹出纪念页 + * + * 主要流程: + * main.js 引入单例 → 各模块 import 同一实例 → 通过事件名通信。 + */ + +/** + * 创建事件总线实例。 + * + * @returns {{on: Function, once: Function, off: Function, emit: Function}} 事件总线 API。 + */ +function createEventBus() { + /** @type {Map>} 事件名 → 监听器集合。 */ + const listeners = new Map(); + + return { + /** + * 注册事件监听器。 + * + * @param {string} event 事件名。 + * @param {Function} handler 监听函数。 + * @returns {Function} 取消注册函数。 + */ + on(event, handler) { + if (!listeners.has(event)) { + listeners.set(event, new Set()); + } + listeners.get(event).add(handler); + return () => this.off(event, handler); + }, + + /** + * 注册一次性监听器(触发一次后自动移除)。 + * + * @param {string} event 事件名。 + * @param {Function} handler 监听函数。 + */ + once(event, handler) { + const wrapped = (...args) => { + this.off(event, wrapped); + handler(...args); + }; + this.on(event, wrapped); + }, + + /** + * 移除事件监听器。 + * + * @param {string} event 事件名。 + * @param {Function} handler 要移除的监听函数。 + */ + off(event, handler) { + const set = listeners.get(event); + if (set) { + set.delete(handler); + if (set.size === 0) { + listeners.delete(event); + } + } + }, + + /** + * 发布事件(同步调用所有监听器)。 + * + * @param {string} event 事件名。 + * @param {...any} args 事件载荷。 + */ + emit(event, ...args) { + const set = listeners.get(event); + if (set) { + [...set].forEach((handler) => { + try { + handler(...args); + } catch (err) { + // 单个监听器异常不阻断其他监听器与游戏主流程。 + console.error(`[event-bus] "${event}" 监听器执行出错:`, err); + } + }); + } + }, + }; +} + +/** 全局共享事件总线单例。 */ +export const eventBus = createEventBus(); diff --git a/src/game-state.js b/src/game-state.js new file mode 100644 index 0000000..a338d72 --- /dev/null +++ b/src/game-state.js @@ -0,0 +1,175 @@ +/** + * @fileoverview 旅行进度存档模块(game-state)。 + * + * 文件用途: + * - 管理已解锁景点的持久化状态,独立于 Phaser 层与 DOM UI 层之外。 + * + * 核心职责: + * 1. localStorage 读写进度(key: xian-travel-progress); + * 2. 提供 loadProgress / unlock / resetProgress / isUnlocked / getSnapshot API; + * 3. 隐私模式降级:localStorage 不可用时回退内存 Map,会话内有效; + * 4. 进度变化时通过 eventBus 广播 state:changed / game:complete。 + * + * 主要流程: + * 启动时 loadProgress → 玩家解锁景点时 unlock → 写档并广播 → + * 达到总数时广播 game:complete → 封面页"重新开始"调用 resetProgress。 + */ + +import { eventBus } from './event-bus.js'; +import { SPOTS } from './data/spots.js'; + +/** localStorage 存档键名。 */ +const STORAGE_KEY = 'xian-travel-progress'; + +/** 内存降级存储(localStorage 不可用时使用)。 */ +const memoryStore = { unlocked: [] }; + +/** + * 检测 localStorage 是否可用(隐私模式 / 被禁用时返回 false)。 + * + * @returns {boolean} 可用返回 true。 + */ +function isStorageAvailable() { + try { + const probe = '__xian_travel_probe__'; + window.localStorage.setItem(probe, '1'); + window.localStorage.removeItem(probe); + return true; + } catch { + return false; + } +} + +/** @type {boolean} 是否使用 localStorage 持久化。 */ +const persistent = isStorageAvailable(); + +/** @type {Set} 当前已解锁景点 id 集合。 */ +const unlockedSet = new Set(); + +/** @type {boolean} 模块是否已初始化(避免重复读取)。 */ +let initialized = false; + +/** + * 从存储读取存档并初始化解锁集合(仅首次调用生效)。 + * + * @returns {void} + */ +function ensureLoaded() { + if (initialized) { + return; + } + initialized = true; + try { + let raw = null; + if (persistent) { + raw = window.localStorage.getItem(STORAGE_KEY); + } + const saved = raw ? JSON.parse(raw) : memoryStore; + if (saved && Array.isArray(saved.unlocked)) { + // 只保留 spots.js 中仍存在的 id,容错配置变更后的脏数据。 + saved.unlocked + .filter((id) => SPOTS.some((spot) => spot.id === id)) + .forEach((id) => unlockedSet.add(id)); + } + } catch { + // 存档损坏时视为无存档,不影响游戏启动。 + console.warn('[game-state] 存档解析失败,按无存档处理'); + } +} + +/** + * 将当前解锁集合写回存储。 + * + * @returns {void} + */ +function persist() { + const payload = JSON.stringify({ + unlocked: [...unlockedSet], + updatedAt: Date.now(), + }); + if (persistent) { + try { + window.localStorage.setItem(STORAGE_KEY, payload); + } catch { + // 写入失败(配额满等)时静默降级,游戏仍可运行。 + console.warn('[game-state] 存档写入失败'); + } + } else { + memoryStore.unlocked = [...unlockedSet]; + } +} + +/** + * 读取当前进度(幂等,首次调用时从存储加载)。 + * + * @returns {{unlocked: string[], total: number, updatedAt: number}} 进度快照。 + */ +export function loadProgress() { + ensureLoaded(); + return getSnapshot(); +} + +/** + * 解锁指定景点:写档并广播进度变化。 + * + * @param {string} spotId 景点唯一 id。 + * @returns {boolean} 本次调用是否真正解锁(已解锁时返回 false)。 + */ +export function unlock(spotId) { + ensureLoaded(); + if (unlockedSet.has(spotId)) { + return false; + } + unlockedSet.add(spotId); + persist(); + eventBus.emit('state:changed', getSnapshot()); + if (unlockedSet.size >= SPOTS.length) { + eventBus.emit('game:complete', getSnapshot()); + } + return true; +} + +/** + * 查询景点是否已解锁。 + * + * @param {string} spotId 景点唯一 id。 + * @returns {boolean} 已解锁返回 true。 + */ +export function isUnlocked(spotId) { + ensureLoaded(); + return unlockedSet.has(spotId); +} + +/** + * 获取进度快照。 + * + * @returns {{unlocked: string[], total: number, updatedAt: number}} 快照数据。 + */ +export function getSnapshot() { + return { + unlocked: [...unlockedSet], + total: SPOTS.length, + updatedAt: Date.now(), + }; +} + +/** + * 清空存档并广播进度变化(封面页"重新开始"使用)。 + * + * @returns {void} + */ +export function resetProgress() { + unlockedSet.clear(); + persist(); + eventBus.emit('state:changed', getSnapshot()); +} + +/** + * 是否有历史存档(决定封面页显示"继续旅行"还是"开始旅行")。 + * + * @returns {boolean} 存在未空存档返回 true。 + */ +export function hasSave() { + ensureLoaded(); + return persistent ? window.localStorage.getItem(STORAGE_KEY) !== null : unlockedSet.size > 0; +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..9f13256 --- /dev/null +++ b/src/main.js @@ -0,0 +1,54 @@ +/** + * @fileoverview 游戏入口与全局配置。 + * + * 文件用途: + * - 创建 Phaser.Game 实例并装配虚拟摇杆插件,随后启动 DOM UI 层。 + * + * 核心职责: + * 1. Phaser 全局配置:1600×900 设计分辨率、FIT 缩放、arcade 物理; + * 2. 注册 rexVirtualJoystick 场景插件(移动端摇杆); + * 3. 场景清单:PreloadScene → MapScene; + * 4. 启动 DOM UI 层(封面页 / 弹窗 / HUD / 纪念页 / 竖屏提示)。 + * + * 主要流程: + * 页面加载 → new Phaser.Game(config) → 同时初始化 TitleOverlay(封面 + * 覆盖在 Canvas 之上,点击"开始旅行"后通过 ui:closed 放行游戏输入)。 + */ + +import Phaser from 'phaser'; +import VirtualJoystickPlugin from 'phaser3-rex-plugins/plugins/virtualjoystick-plugin.js'; + +import PreloadScene from './scenes/PreloadScene.js'; +import MapScene from './scenes/MapScene.js'; +import { mountUi } from './ui/dom-layer.js'; + +/** Phaser 全局配置对象。 */ +const config = { + type: Phaser.AUTO, + parent: 'game', + backgroundColor: '#f5ecd7', + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + width: 1600, + height: 900, + }, + physics: { + default: 'arcade', + arcade: { gravity: { y: 0 }, debug: false }, + }, + plugins: { + scene: [ + { + key: 'rexVirtualJoystick', + plugin: VirtualJoystickPlugin, + mapping: 'rexVirtualJoystick', + }, + ], + }, + scene: [PreloadScene, MapScene], +}; + +new Phaser.Game(config); + +mountUi(); diff --git a/src/objects/GuideArrow.js b/src/objects/GuideArrow.js new file mode 100644 index 0000000..753c628 --- /dev/null +++ b/src/objects/GuideArrow.js @@ -0,0 +1,120 @@ +/** + * @fileoverview 方向引导箭头(GuideArrow)。 + * + * 文件用途: + * - 在屏幕边缘渲染指向"最近未解锁景点"的浮动箭头,防止玩家在大地图上迷路。 + * + * 核心职责: + * 1. 每帧根据玩家 → 目标的方向角计算箭头贴边位置与旋转角; + * 2. 目标进入相机视野时淡出(不遮挡玩家正要看的内容),离开视野再淡入; + * 3. 附带正弦浮动动画与目标景点名标签(不显示数字距离)。 + * + * 主要流程: + * MapScene 创建 GuideArrow → update() 每帧传入玩家与目标 → + * 全部解锁后 MapScene 调用 hide() 移除。 + */ + +import Phaser from 'phaser'; + +/** 箭头距屏幕边缘的安全内缩(避开 HUD 与摇杆)。 */ +const EDGE_MARGIN = 110; +/** 浮动动画幅度(像素)。 */ +const FLOAT_AMPLITUDE = 7; + +/** + * 屏幕边缘引导箭头(scrollFactor 固定为 0,不随相机移动)。 + * + * @extends Phaser.GameObjects.Container + */ +export default class GuideArrow extends Phaser.GameObjects.Container { + /** + * 创建引导箭头。 + * + * @param {Phaser.Scene} scene 所属场景。 + */ + constructor(scene) { + super(scene, 0, 0); + scene.add.existing(this); + this.setScrollFactor(0).setDepth(50); + + // 箭头图形:橙色三角 + 白描边(指向 +x 方向,随 rotation 旋转)。 + /** @type {Phaser.GameObjects.Graphics} */ + this.arrow = scene.add.graphics(); + this.arrow.fillStyle(0xe67e22, 1); + this.arrow.lineStyle(3, 0xfff8ec, 1); + this.arrow.fillTriangle(34, 0, -10, -17, -10, 17); + this.arrow.strokeTriangle(34, 0, -10, -17, -10, 17); + this.arrow.strokePath(); + + // 目标景点名标签(跟随在箭头尾部)。 + /** @type {Phaser.GameObjects.Text} */ + this.label = scene.add + .text(-26, 0, '', { + fontFamily: '"Kaiti SC", "KaiTi", serif', + fontSize: '18px', + color: '#6b4e2e', + backgroundColor: '#f5ecd7e6', + padding: { x: 8, y: 3 }, + }) + .setOrigin(1, 0.5); + + this.add([this.arrow, this.label]); + this.setVisible(false); + } + + /** + * 每帧更新箭头位置、朝向与可见性。 + * + * @param {Phaser.Physics.Arcade.Sprite} player 玩家精灵。 + * @param {Object|null} target 最近未解锁景点(null 表示全部解锁,箭头隐藏)。 + * @param {number} time 场景时间(毫秒,用于浮动动画)。 + * @returns {void} + */ + update(player, target, time) { + if (!target) { + this.setVisible(false); + return; + } + const camera = this.scene.cameras.main; + const view = camera.worldView; + + // 目标已在相机视野内:淡出等待。 + const inView = view.contains(target.x, target.y); + const targetAlpha = inView ? 0 : 1; + this.setAlpha(Phaser.Math.Linear(this.alpha, targetAlpha, 0.12)); + this.setVisible(this.alpha > 0.05); + if (!this.visible) { + return; + } + + // 玩家 → 目标的世界方向。 + const dx = target.x - player.x; + const dy = target.y - player.y; + const angle = Math.atan2(dy, dx); + + // 屏幕中心沿方向推出,并 clamp 到屏幕边缘内缩矩形。 + const cx = camera.width / 2; + const cy = camera.height / 2; + const halfW = cx - EDGE_MARGIN; + const halfH = cy - EDGE_MARGIN - 20; + // 方向在屏幕半平面的延伸系数(取 x/y 分量中先触及边界者)。 + const tx = Math.abs(Math.cos(angle)) > 1e-6 ? halfW / Math.abs(Math.cos(angle)) : Infinity; + const ty = Math.abs(Math.sin(angle)) > 1e-6 ? halfH / Math.abs(Math.sin(angle)) : Infinity; + const extend = Math.min(tx, ty, Math.hypot(halfW, halfH)); + + // 正弦浮动:沿法线方向轻摆。 + const floatOffset = Math.sin(time / 380) * FLOAT_AMPLITUDE; + this.setPosition( + cx + Math.cos(angle) * extend - Math.sin(angle) * floatOffset, + cy + Math.sin(angle) * extend + Math.cos(angle) * floatOffset + ); + this.arrow.setRotation(angle); + this.label.setText(target.name); + // 标签始终水平可读:独立于箭头旋转,向屏幕中心回退放置。 + this.label.setRotation(0); + this.label.setPosition( + -Math.cos(angle) * 96 - Math.sin(angle) * 4, + -Math.sin(angle) * 96 + Math.cos(angle) * 4 + ); + } +} diff --git a/src/objects/Player.js b/src/objects/Player.js new file mode 100644 index 0000000..b8e2800 --- /dev/null +++ b/src/objects/Player.js @@ -0,0 +1,296 @@ +/** + * @fileoverview 玩家角色类(Player)。 + * + * 文件用途: + * - 定义可在手账地图上漫游的 Q 版贴纸小人(占位形态,AI 精灵表就绪后可整体替换)。 + * + * 核心职责: + * 1. 用离屏 Canvas 程序绘制 4 方向 × 2 帧的贴纸小人精灵表(48×64 帧格); + * 2. 管理物理移动:键盘(WASD / 方向键)与虚拟摇杆输入,斜向速度归一化; + * 3. 按移动方向切换行走动画,静止时停在当前方向的 idle 帧。 + * + * 主要流程: + * MapScene 创建 Player → 每帧调用 update() → 汇总输入向量 → + * 归一化乘以速度 → setVelocity + 播放对应方向动画。 + */ + +import Phaser from 'phaser'; + +/** 帧格宽度(像素)。 */ +const FRAME_W = 48; +/** 帧格高度(像素)。 */ +const FRAME_H = 64; +/** 每方向帧数。 */ +const FRAMES_PER_DIR = 2; +/** 移动速度(像素/秒)。 */ +const MOVE_SPEED = 260; + +/** + * 生成贴纸小人精灵表纹理并注册到 Phaser 纹理管理器。 + * + * 帧布局(8 帧横排):[down×2, up×2, left×2, right×2]。 + * + * @param {Phaser.Scene} scene 目标场景(用于注册纹理)。 + * @returns {void} + */ +function buildPlayerTexture(scene) { + const dirs = ['down', 'up', 'left', 'right']; + const totalFrames = dirs.length * FRAMES_PER_DIR; + const canvasTexture = scene.textures.createCanvas( + 'player', + totalFrames * FRAME_W, + FRAME_H + ); + const ctx = canvasTexture.getContext(); + + dirs.forEach((dir, dirIndex) => { + for (let frame = 0; frame < FRAMES_PER_DIR; frame += 1) { + const ox = (dirIndex * FRAMES_PER_DIR + frame) * FRAME_W; + drawStickFigure(ctx, ox, 0, dir, frame, dir === 'right'); + // 手动注册帧索引,使 sprite 可按 frame 序号取帧。 + canvasTexture.add(dirIndex * FRAMES_PER_DIR + frame, 0, ox, 0, FRAME_W, FRAME_H); + } + }); + canvasTexture.refresh(); +} + +/** + * 在指定偏移处绘制单帧贴纸小人。 + * + * @param {CanvasRenderingContext2D} ctx 画布上下文。 + * @param {number} ox 帧左上角 x 偏移。 + * @param {number} oy 帧左上角 y 偏移。 + * @param {string} dir 朝向(down / up / left / right)。 + * @param {number} frame 行走帧序号(0 静止姿态,1 迈步姿态)。 + * @param {boolean} mirror 是否水平镜像(right 复用 left 绘制)。 + * @returns {void} + */ +function drawStickFigure(ctx, ox, oy, dir, frame, mirror) { + ctx.save(); + ctx.translate(ox, oy); + if (mirror) { + ctx.translate(FRAME_W, 0); + ctx.scale(-1, 1); + } + + const walkLift = frame === 1 ? 2 : 0; // 迈步帧整体上抬,产生弹跳感。 + const cy = 2 + walkLift; + + // 脚下阴影。 + ctx.beginPath(); + ctx.ellipse(24, 58, 13, 4.5, 0, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(80, 60, 40, 0.18)'; + ctx.fill(); + + // 腿:静止并拢,迈步时前后错开。 + ctx.fillStyle = '#6b4e2e'; + if (frame === 0) { + ctx.fillRect(18, 50 - walkLift, 4.5, 7); + ctx.fillRect(26, 50 - walkLift, 4.5, 7); + } else { + ctx.fillRect(15, 50 - walkLift, 4.5, 9); + ctx.fillRect(28, 50 - walkLift, 4.5, 5); + } + + // 背包(仅背面与侧面可见,强化旅行者身份)。 + if (dir !== 'down') { + ctx.fillStyle = '#8a6a42'; + ctx.strokeStyle = '#5c4630'; + ctx.lineWidth = 1.5; + roundRect(ctx, 15, 31 + cy, 18, 14, 4); + ctx.fill(); + ctx.stroke(); + } + + // 身体(暖橙色圆角胶囊)。 + ctx.fillStyle = '#c96f4a'; + ctx.strokeStyle = '#6b4e2e'; + ctx.lineWidth = 2; + roundRect(ctx, 13, 32 + cy, 22, 20, 9); + ctx.fill(); + ctx.stroke(); + + // 头部。 + ctx.beginPath(); + ctx.arc(24, 18 + cy, 13, 0, Math.PI * 2); + ctx.fillStyle = '#f8dcc0'; + ctx.fill(); + ctx.stroke(); + + // 头发(深棕,按朝向变化覆盖区域)。 + ctx.fillStyle = '#4a3a28'; + ctx.beginPath(); + if (dir === 'up') { + // 背面:满头头发。 + ctx.arc(24, 18 + cy, 12.4, 0, Math.PI * 2); + } else if (dir === 'down') { + // 正面:头顶 + 两侧刘海。 + ctx.arc(24, 18 + cy, 12.4, Math.PI, Math.PI * 2); + ctx.moveTo(11.6, 18 + cy); + ctx.arc(24, 18 + cy, 12.4, Math.PI, Math.PI * 1.5); + ctx.rect(11.6, 12 + cy, 5, 7); + ctx.rect(31.4, 12 + cy, 5, 7); + } else { + // 侧面:后脑勺 + 单侧刘海。 + ctx.arc(24, 18 + cy, 12.4, Math.PI * 0.4, Math.PI * 1.6); + ctx.rect(30, 12 + cy, 6, 9); + } + ctx.fill(); + + // 表情(up 无表情细节)。 + if (dir !== 'up') { + ctx.fillStyle = '#33251a'; + if (dir === 'down') { + dot(ctx, 19, 21 + cy, 1.9); + dot(ctx, 29, 21 + cy, 1.9); + // 腮红。 + ctx.fillStyle = 'rgba(232, 122, 108, 0.45)'; + dot(ctx, 14.5, 25 + cy, 2.6); + dot(ctx, 33.5, 25 + cy, 2.6); + } else { + dot(ctx, 17, 21 + cy, 1.9); + ctx.fillStyle = 'rgba(232, 122, 108, 0.45)'; + dot(ctx, 13, 25 + cy, 2.4); + } + } + ctx.restore(); +} + +/** + * 绘制圆角矩形路径(兼容 ctx.roundRect 缺失的环境)。 + * + * @param {CanvasRenderingContext2D} ctx 画布上下文。 + * @param {number} x 左上角 x。 + * @param {number} y 左上角 y。 + * @param {number} w 宽。 + * @param {number} h 高。 + * @param {number} r 圆角半径。 + * @returns {void} + */ +function roundRect(ctx, x, y, w, h, r) { + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.arcTo(x + w, y, x + w, y + h, r); + ctx.arcTo(x + w, y + h, x, y + h, r); + ctx.arcTo(x, y + h, x, y, r); + ctx.arcTo(x, y, x + w, y, r); + ctx.closePath(); +} + +/** + * 在指定圆心画实心圆点。 + * + * @param {CanvasRenderingContext2D} ctx 画布上下文。 + * @param {number} x 圆心 x。 + * @param {number} y 圆心 y。 + * @param {number} r 半径。 + * @returns {void} + */ +function dot(ctx, x, y, r) { + ctx.beginPath(); + ctx.arc(x, y, r, 0, Math.PI * 2); + ctx.fill(); +} + +/** + * 玩家角色精灵:输入汇总、物理移动与行走动画。 + * + * @extends Phaser.Physics.Arcade.Sprite + */ +export default class Player extends Phaser.Physics.Arcade.Sprite { + /** + * 创建玩家。 + * + * @param {Phaser.Scene} scene 所属场景。 + * @param {number} x 出生 x 坐标。 + * @param {number} y 出生 y 坐标。 + */ + constructor(scene, x, y) { + buildPlayerTexture(scene); + super(scene, x, y, 'player', 0); + scene.add.existing(this); + scene.physics.add.existing(this); + + this.speed = MOVE_SPEED; + this.setCollideWorldBounds(true); + this.setDepth(10); + this.currentDir = 'down'; + + // 注册四方向行走动画。 + [ + ['walk-down', 0], + ['walk-up', 2], + ['walk-left', 4], + ['walk-right', 6], + ].forEach(([key, base]) => { + scene.anims.create({ + key, + frames: [ + { key: 'player', frame: base }, + { key: 'player', frame: base + 1 }, + ], + frameRate: 7, + repeat: -1, + }); + }); + + /** @type {Phaser.Types.Input.Keyboard.CursorKeys} 方向键。 */ + this.cursors = scene.input.keyboard.createCursorKeys(); + /** @type {Object} WASD 键映射。 */ + this.wasd = scene.input.keyboard.addKeys('W,A,S,D'); + /** @type {Object|null} 虚拟摇杆(由 MapScene 注入,PC 端为 null)。 */ + this.joystick = null; + } + + /** + * 注入虚拟摇杆(移动端)。 + * + * @param {Object} joystick rexVirtualJoystick 实例。 + * @returns {void} + */ + setJoystick(joystick) { + this.joystick = joystick; + } + + /** + * 每帧更新:汇总键盘与摇杆输入,归一化后驱动移动与动画。 + * + * @returns {void} + */ + update() { + let vx = 0; + let vy = 0; + + if (this.cursors.left.isDown || this.wasd.A.isDown) vx = -1; + else if (this.cursors.right.isDown || this.wasd.D.isDown) vx = 1; + if (this.cursors.up.isDown || this.wasd.W.isDown) vy = -1; + else if (this.cursors.down.isDown || this.wasd.S.isDown) vy = 1; + + // 摇杆输入优先级高于键盘(forceX/Y ∈ [-1, 1])。 + if (this.joystick && this.joystick.forceX !== 0 && this.joystick.forceY !== 0) { + vx = this.joystick.forceX; + vy = this.joystick.forceY; + } + + const len = Math.hypot(vx, vy); + if (len > 0.01) { + // 归一化,保证斜向移动不超速。 + this.setVelocity((vx / len) * this.speed, (vy / len) * this.speed); + // 按主分量决定朝向并播放行走动画。 + const dir = + Math.abs(vx) > Math.abs(vy) + ? vx > 0 + ? 'right' + : 'left' + : vy > 0 + ? 'down' + : 'up'; + this.currentDir = dir; + this.anims.play(`walk-${dir}`, true); + } else { + this.setVelocity(0, 0); + this.anims.stop(); + this.setFrame({ down: 0, up: 2, left: 4, right: 6 }[this.currentDir]); + } + } +} diff --git a/src/objects/PoiMarker.js b/src/objects/PoiMarker.js new file mode 100644 index 0000000..fc37992 --- /dev/null +++ b/src/objects/PoiMarker.js @@ -0,0 +1,134 @@ +/** + * @fileoverview 景点标记类(PoiMarker)。 + * + * 文件用途: + * - 在地图上渲染单个景点的可视化标记(触发圈 + 图标 + 名称 + 到访印章)。 + * + * 核心职责: + * 1. 双状态渲染:未解锁(虚线棕圈、半透明)/ 已解锁(实线橙圈、淡填充、印章); + * 2. unlock() 提供手账"盖章"切换动效(弹性缩放 + 印章浮现); + * 3. 标记整体半透明,保证玩家走入时仍可读。 + * + * 主要流程: + * MapScene 为每个景点创建 PoiMarker → state:changed 时批量调用 + * setUnlocked() → 解锁瞬间由 PhotoModal 关闭回调触发盖章动效。 + */ + +import Phaser from 'phaser'; + +/** 标记渲染深度(高于玩家,保证走进触发圈时仍可见)。 */ +const MARKER_DEPTH = 12; + +/** + * 景点标记容器:触发圈可视化 + 图标 + 名称 + 状态。 + * + * @extends Phaser.GameObjects.Container + */ +export default class PoiMarker extends Phaser.GameObjects.Container { + /** + * 创建景点标记。 + * + * @param {Phaser.Scene} scene 所属场景。 + * @param {Object} spot 景点配置(见 src/data/spots.js)。 + * @param {boolean} unlocked 初始是否已解锁(读档恢复)。 + */ + constructor(scene, spot, unlocked) { + super(scene, spot.x, spot.y); + scene.add.existing(this); + + /** @type {Object} 景点配置。 */ + this.spot = spot; + this.setDepth(MARKER_DEPTH); + + const r = spot.radius; + + // 触发圈。 + /** @type {Phaser.GameObjects.Arc} */ + this.ring = scene.add.circle(0, 0, r, 0xe67e22, unlocked ? 0.16 : 0.08); + this.ring.setStrokeStyle(3.5, unlocked ? 0xe67e22 : 0x8a6a42, unlocked ? 0.95 : 0.55); + this.ring.isStroked = true; + if (!unlocked) { + // 未解锁用虚线圈,弱化存在感。 + this.ring.setStrokeStyle(3, 0x8a6a42, 0.5); + } + + // 中心图标。 + /** @type {Phaser.GameObjects.Text} */ + this.iconText = scene.add + .text(0, 0, spot.icon, { fontSize: '38px' }) + .setOrigin(0.5); + + // 名称标签(纸片底 + 棕字)。 + /** @type {Phaser.GameObjects.Text} */ + this.nameText = scene.add + .text(0, -r - 16, spot.name, { + fontFamily: '"Kaiti SC", "KaiTi", serif', + fontSize: '22px', + color: '#5c4630', + backgroundColor: '#f5ecd7cc', + padding: { x: 10, y: 4 }, + }) + .setOrigin(0.5); + + // 已到访印章(初始隐藏,解锁时浮现)。 + /** @type {Phaser.GameObjects.Text} */ + this.stampText = scene.add + .text(r * 0.55, r * 0.55, '✓ 已到访', { + fontFamily: '"Kaiti SC", "KaiTi", serif', + fontSize: '20px', + color: '#c0392b', + backgroundColor: '#f8e8d8e0', + padding: { x: 6, y: 3 }, + }) + .setOrigin(0.5) + .setAngle(-12) + .setVisible(false); + + this.add([this.ring, this.iconText, this.nameText, this.stampText]); + if (unlocked) { + this.applyUnlockedStyle(false); + } + } + + /** + * 应用已解锁视觉样式。 + * + * @param {boolean} animate 是否播放盖章弹跳动效。 + * @returns {void} + */ + applyUnlockedStyle(animate) { + this.ring.setFillStyle(0xe67e22, 0.16); + this.ring.setStrokeStyle(4, 0xe67e22, 0.95); + this.iconText.setAlpha(1); + this.stampText.setVisible(true); + if (animate) { + this.setScale(1.45); + this.scene.tweens.add({ + targets: this, + scale: 1, + duration: 420, + ease: 'Back.easeOut', + }); + // 印章单独淡入,营造"盖章落印"节奏。 + this.stampText.setAlpha(0); + this.scene.tweens.add({ + targets: this.stampText, + alpha: 1, + scale: { from: 2.2, to: 1 }, + duration: 300, + delay: 120, + ease: 'Cubic.easeOut', + }); + } + } + + /** + * 外部入口:切换到已解锁状态。 + * + * @param {boolean} animate 是否播放动效(读档恢复时为 false)。 + * @returns {void} + */ + setUnlocked(animate = true) { + this.applyUnlockedStyle(animate); + } +} diff --git a/src/scenes/MapScene.js b/src/scenes/MapScene.js new file mode 100644 index 0000000..4ca8204 --- /dev/null +++ b/src/scenes/MapScene.js @@ -0,0 +1,243 @@ +/** + * @fileoverview 地图主场景(MapScene)。 + * + * 文件用途: + * - 承载手账地图漫游的全部游戏世界逻辑(唯一游戏场景)。 + * + * 核心职责: + * 1. 渲染分层地图:SVG 路网底图 + 纸张纹理叠加(multiply); + * 2. 玩家移动(键盘 + 触摸设备虚拟摇杆)与相机跟随; + * 3. 景点触发:Zone overlap 检测 + 弹窗互斥 + 离开冷却(防原地连弹); + * 4. POI 标记状态管理(读档恢复 / 解锁盖章动效)与屏幕边缘引导箭头; + * 5. 与 DOM UI 层通过 eventBus 通信(poi:reached / ui:closed)。 + * + * 主要流程: + * create() 搭建世界 → update() 每帧驱动玩家与引导 → + * overlap → poi:reached → PhotoModal 展示 → ui:closed → + * unlock + 冷却 + 恢复输入 → 玩家离开触发圈解除冷却。 + */ + +import Phaser from 'phaser'; +import Player from '../objects/Player.js'; +import PoiMarker from '../objects/PoiMarker.js'; +import GuideArrow from '../objects/GuideArrow.js'; +import { SPOTS, SPAWN } from '../data/spots.js'; +import { eventBus } from '../event-bus.js'; +import * as gameState from '../game-state.js'; +import { hasPaperTexture } from './PreloadScene.js'; +import { inputReleased } from '../ui/dom-layer.js'; + +/** 地图世界尺寸(与 SVG viewBox 一致)。 */ +const WORLD_W = 3000; +const WORLD_H = 2100; +/** 相机缩放(视野约 1280×720 世界单位)。 */ +const CAMERA_ZOOM = 1.25; +/** 触发圈外扩距离:玩家离开 radius+该值后解除冷却。 */ +const COOLDOWN_GAP = 30; + +/** + * 地图主场景。 + * + * @extends Phaser.Scene + */ +export default class MapScene extends Phaser.Scene { + constructor() { + super({ key: 'MapScene' }); + } + + /** + * 搭建地图世界:底图、纹理、标记、玩家、相机、摇杆、事件接线。 + * + * @returns {void} + */ + create() { + // ── 底图与纸张纹理叠加 ── + this.add.image(0, 0, 'mapBase').setOrigin(0, 0); + if (hasPaperTexture && this.textures.exists('paper')) { + const paper = this.add + .image(0, 0, 'paper') + .setOrigin(0, 0) + .setDisplaySize(WORLD_W, WORLD_H) + .setBlendMode(Phaser.BlendModes.MULTIPLY) + .setAlpha(0.5) + .setDepth(1); + paper.setVisible(true); + } + + // ── 物理世界边界 ── + this.physics.world.setBounds(0, 0, WORLD_W, WORLD_H); + + // ── 玩家 ── + this.player = new Player(this, SPAWN.x, SPAWN.y); + this.modalOpen = true; // 初始由封面页持有输入锁。 + this.setGameInputEnabled(false); + + // ── 相机 ── + const camera = this.cameras.main; + camera.setBounds(0, 0, WORLD_W, WORLD_H); + camera.setZoom(CAMERA_ZOOM); + camera.startFollow(this.player, true, 0.12, 0.12); + camera.setBackgroundColor('#f5ecd7'); + + // ── 景点标记与触发区 ── + /** @type {Map} 景点 id → 标记。 */ + this.markers = new Map(); + /** @type {Set} 处于冷却(刚看过、需离开才能重触发)的景点 id。 */ + this.cooldowns = new Set(); + SPOTS.forEach((spot) => { + const marker = new PoiMarker(this, spot, gameState.isUnlocked(spot.id)); + this.markers.set(spot.id, marker); + + const zone = this.add.zone(spot.x, spot.y, spot.radius * 2, spot.radius * 2); + this.physics.add.existing(zone, true); + this.physics.add.overlap(this.player, zone, () => this.onEnterZone(spot)); + }); + + // ── 引导箭头 ── + this.guideArrow = new GuideArrow(this); + + // ── 虚拟摇杆(仅触摸设备) ── + this.createVirtualJoystick(); + + // ── DOM UI 事件接线 ── + eventBus.on('ui:closed', ({ spotId }) => this.onModalClosed(spotId)); + + // 首帧前的进度广播:让 HUD 与封面页拿到初始快照。 + eventBus.emit('state:changed', gameState.getSnapshot()); + + // 封面"开始旅行"可能早于本场景创建(ESM live binding 回查放行标志)。 + if (inputReleased) { + this.modalOpen = false; + this.setGameInputEnabled(true); + } + } + + /** + * 创建虚拟摇杆(存在触摸能力时)。 + * + * @returns {void} + */ + createVirtualJoystick() { + const hasTouch = + 'ontouchstart' in window || (navigator.maxTouchPoints ?? 0) > 0; + if (!hasTouch || !this.rexVirtualJoystick) { + return; + } + const joystick = this.rexVirtualJoystick.add(this, { + x: 130, + y: this.scale.height - 110, + radius: 62, + base: this.add + .circle(0, 0, 62, 0xeadfc2, 0.55) + .setStrokeStyle(2.5, 0x8a6a42, 0.6), + thumb: this.add.circle(0, 0, 30, 0xf5ecd7, 0.9).setStrokeStyle(2, 0xb08a5e, 0.8), + enable: true, + }); + joystick.base.setScrollFactor(0).setDepth(100); + joystick.thumb.setScrollFactor(0).setDepth(101); + this.joystick = joystick; + this.player.setJoystick(joystick); + } + + /** + * 开关游戏输入(弹窗打开时禁用键盘与摇杆)。 + * + * @param {boolean} enabled 是否启用。 + * @returns {void} + */ + setGameInputEnabled(enabled) { + this.input.enabled = enabled; + if (this.joystick) { + this.joystick.setEnable(enabled); + } + } + + /** + * 玩家进入景点触发圈:请求弹窗(互斥 + 冷却过滤)。 + * + * @param {Object} spot 景点配置。 + * @returns {void} + */ + onEnterZone(spot) { + if (this.modalOpen || this.cooldowns.has(spot.id)) { + return; + } + this.modalOpen = true; + this.setGameInputEnabled(false); + eventBus.emit('poi:reached', { spot }); + } + + /** + * 弹窗关闭:解锁景点(首次)、进入冷却、恢复输入。 + * + * @param {string} spotId 关闭的弹窗对应景点 id。 + * @returns {void} + */ + onModalClosed(spotId) { + this.modalOpen = false; + this.setGameInputEnabled(true); + if (!spotId) { + return; // 封面页关闭(无景点上下文)。 + } + if (gameState.isUnlocked(spotId)) { + this.cooldowns.add(spotId); // 重看:同样进入冷却。 + return; + } + gameState.unlock(spotId); // 内部广播 state:changed。 + this.markers.get(spotId)?.setUnlocked(true); + this.cooldowns.add(spotId); + } + + /** + * 每帧更新:玩家移动、冷却解除检测、引导箭头。 + * + * @param {number} time 场景时间(毫秒)。 + * @returns {void} + */ + update(time) { + if (!this.modalOpen) { + this.player.update(); + } + + // 冷却解除:玩家离开触发圈足够远后可再次触发。 + if (this.cooldowns.size > 0) { + this.cooldowns.forEach((id) => { + const spot = SPOTS.find((s) => s.id === id); + if (!spot) { + this.cooldowns.delete(id); + return; + } + const dist = Phaser.Math.Distance.Between( + this.player.x, + this.player.y, + spot.x, + spot.y + ); + if (dist > spot.radius + COOLDOWN_GAP) { + this.cooldowns.delete(id); + } + }); + } + + // 引导目标:最近的未解锁景点。 + const unlocked = new Set(gameState.getSnapshot().unlocked); + let target = null; + let minDist = Infinity; + SPOTS.forEach((spot) => { + if (unlocked.has(spot.id)) { + return; + } + const dist = Phaser.Math.Distance.Between( + this.player.x, + this.player.y, + spot.x, + spot.y + ); + if (dist < minDist) { + minDist = dist; + target = spot; + } + }); + this.guideArrow.update(this.player, target, time); + } +} diff --git a/src/scenes/PreloadScene.js b/src/scenes/PreloadScene.js new file mode 100644 index 0000000..3e13447 --- /dev/null +++ b/src/scenes/PreloadScene.js @@ -0,0 +1,147 @@ +/** + * @fileoverview 资源预加载场景(PreloadScene)。 + * + * 文件用途: + * - 以手账风进度条加载全部游戏资源,并对缺失资源做占位降级。 + * + * 核心职责: + * 1. 加载地图底图(SVG)、纸张纹理(AI 素材)与全部景点照片; + * 2. 渲染加载进度 UI(纸底圆角进度条 + 标题 + 百分比); + * 3. loaderror 处理:照片缺失时生成"照片待补"占位纹理,不中断启动; + * 4. 完成后跳转 MapScene。 + * + * 主要流程: + * preload() 注册资源与进度/失败监听 → create() 收尾并 scene.start('MapScene')。 + */ + +import Phaser from 'phaser'; +import { SPOTS } from '../data/spots.js'; + +/** 纸张纹理是否存在(供 MapScene 决定是否叠加纹理层)。 */ +export let hasPaperTexture = false; + +/** + * 资源预加载场景。 + * + * @extends Phaser.Scene + */ +export default class PreloadScene extends Phaser.Scene { + constructor() { + super({ key: 'PreloadScene' }); + } + + /** + * 注册资源加载与进度 UI。 + * + * @returns {void} + */ + preload() { + const { width, height } = this.scale; + + // ── 加载进度 UI(手账风) ── + this.cameras.main.setBackgroundColor('#f5ecd7'); + this.add + .text(width / 2, height / 2 - 110, '西安旅行记忆', { + fontFamily: '"Kaiti SC", "KaiTi", serif', + fontSize: '58px', + color: '#6b4e2e', + }) + .setOrigin(0.5); + this.add + .text(width / 2, height / 2 - 60, '正在翻开手账…', { + fontFamily: '"Kaiti SC", "KaiTi", serif', + fontSize: '22px', + color: '#b08a5e', + }) + .setOrigin(0.5); + + const barX = width / 2 - 240; + const barY = height / 2 + 20; + const barW = 480; + const track = this.add.graphics(); + track.fillStyle(0xeadfc2, 1); + track.fillRoundedRect(barX - 6, barY - 6, barW + 12, 32, 16); + track.lineStyle(2.5, 0xb08a5e, 1); + track.strokeRoundedRect(barX - 6, barY - 6, barW + 12, 32, 16); + const fill = this.add.graphics(); + const percentText = this.add + .text(width / 2, barY + 70, '0%', { + fontFamily: 'Georgia, serif', + fontSize: '20px', + color: '#8a6a42', + }) + .setOrigin(0.5); + + this.load.on('progress', (value) => { + fill.clear(); + fill.fillStyle(0xe67e22, 1); + fill.fillRoundedRect(barX, barY, Math.max(20, barW * value), 20, 10); + percentText.setText(`${Math.round(value * 100)}%`); + }); + + // ── 缺失资源降级:生成"照片待补"占位纹理 ── + this.load.on('loaderror', (file) => { + console.warn(`[preload] 资源缺失,启用占位降级: ${file.key}`); + if (file.key.startsWith('photo-')) { + this.buildPhotoPlaceholder(file.key); + } else if (file.key === 'paper') { + hasPaperTexture = false; + } + }); + + // ── 地图与纹理 ── + hasPaperTexture = true; + this.load.image('mapBase', 'assets/maps/map-base.svg'); + this.load.image('paper', 'assets/textures/paper.jpg'); + + // ── 景点照片(key 约定:photo-{spotId}-{index}) ── + SPOTS.forEach((spot) => { + spot.photos.forEach((photo, index) => { + this.load.image(`photo-${spot.id}-${index}`, photo.src); + }); + }); + } + + /** + * 为缺失照片生成 4:3 渐变占位纹理(含"照片待补"水印)。 + * + * @param {string} textureKey 缺失的纹理 key。 + * @returns {void} + */ + buildPhotoPlaceholder(textureKey) { + if (this.textures.exists(textureKey)) { + return; + } + const w = 1200; + const h = 900; + const canvas = this.textures.createCanvas(textureKey, w, h); + const ctx = canvas.getContext(); + const grad = ctx.createLinearGradient(0, 0, w, h); + grad.addColorStop(0, '#e8d5b5'); + grad.addColorStop(1, '#cdb891'); + ctx.fillStyle = grad; + ctx.fillRect(0, 0, w, h); + ctx.strokeStyle = 'rgba(138, 106, 66, 0.35)'; + ctx.lineWidth = 3; + ctx.setLineDash([14, 10]); + ctx.strokeRect(24, 24, w - 48, h - 48); + ctx.setLineDash([]); + ctx.fillStyle = 'rgba(107, 78, 46, 0.75)'; + ctx.font = '48px "Kaiti SC", KaiTi, serif'; + ctx.textAlign = 'center'; + ctx.fillText('📷 照片待补', w / 2, h / 2 - 12); + ctx.font = '30px "Kaiti SC", KaiTi, serif'; + ctx.fillText('替换真实照片后自动生效', w / 2, h / 2 + 56); + canvas.refresh(); + } + + /** + * 加载完成后进入主地图场景。 + * + * @returns {void} + */ + create() { + // 略作停顿让进度条走满展示,再转场。 + this.time.delayedCall(250, () => this.scene.start('MapScene')); + } +} diff --git a/src/ui/CompleteOverlay.js b/src/ui/CompleteOverlay.js new file mode 100644 index 0000000..95e2256 --- /dev/null +++ b/src/ui/CompleteOverlay.js @@ -0,0 +1,87 @@ +/** + * @fileoverview 旅行完成纪念页组件(CompleteOverlay)。 + * + * 文件用途: + * - 全部景点解锁后的全屏纪念页:照片九宫格 + 旅行统计 + 继续漫游。 + * + * 核心职责: + * 1. 监听 game:complete 展示纪念页(每个景点一张 mini 拍立得); + * 2. 统计口径:景点数、旅行天数(spots 中不重复 date 数量); + * 3. "继续漫游"关闭页面并交还输入(ui:closed)。 + * + * 主要流程: + * game-state 检测集齐 → game:complete → show() → 用户浏览/关闭 → + * emit('ui:closed', {}) → MapScene 恢复输入。 + */ + +import { eventBus } from '../event-bus.js'; +import { SPOTS } from '../data/spots.js'; + +/** + * 纪念页 DOM 组件。 + */ +export class CompleteOverlay { + /** + * 创建并挂载纪念页 DOM。 + * + * @param {HTMLElement} root #ui 根容器。 + */ + constructor(root) { + /** @type {HTMLElement} 组件根节点。 */ + this.el = document.createElement('section'); + this.el.className = 'complete-overlay hidden'; + this.el.innerHTML = ` +
+
+

🎉 旅行完成

+

这一程,长安的所有回忆都被你收进了手账

+
+
0天旅程
+
0个回忆
+
+
+ +
+
+ `; + root.appendChild(this.el); + this.el.querySelector('.co-continue').addEventListener('click', () => this.hide()); + eventBus.on('game:complete', () => this.show()); + } + + /** + * 展示纪念页:装配九宫格与统计。 + * + * @returns {void} + */ + show() { + const grid = this.el.querySelector('.co-grid'); + grid.innerHTML = ''; + SPOTS.forEach((spot) => { + const card = document.createElement('div'); + card.className = 'co-card'; + card.style.setProperty('--tilt', `${(spot.x % 11) - 5}deg`); + card.innerHTML = ` +
${spot.icon}
+
${spot.name}
+
${spot.date}
+ `; + grid.appendChild(card); + }); + + const days = new Set(SPOTS.map((s) => s.date)).size; + this.el.querySelector('#co-days').textContent = days; + this.el.querySelector('#co-spots').textContent = SPOTS.length; + this.el.classList.remove('hidden'); + } + + /** + * 隐藏纪念页并交还输入。 + * + * @returns {void} + */ + hide() { + this.el.classList.add('hidden'); + eventBus.emit('ui:closed', {}); + } +} diff --git a/src/ui/Hud.js b/src/ui/Hud.js new file mode 100644 index 0000000..0c64028 --- /dev/null +++ b/src/ui/Hud.js @@ -0,0 +1,57 @@ +/** + * @fileoverview 收集进度 HUD 组件。 + * + * 文件用途: + * - 屏幕右上角的常驻进度徽章(n / total),解锁时数字跳动。 + * + * 核心职责: + * 1. 监听 state:changed 刷新进度文本; + * 2. 进度变化时触发 pop 动效(重排 CSS animation)。 + * + * 主要流程: + * init() 挂载并订阅事件 → state:changed → 更新文本 + pop。 + */ + +import { eventBus } from '../event-bus.js'; + +/** + * HUD 进度徽章 DOM 组件。 + */ +export class Hud { + /** + * 创建并挂载徽章 DOM。 + * + * @param {HTMLElement} root #ui 根容器。 + */ + constructor(root) { + /** @type {HTMLElement} 组件根节点。 */ + this.el = document.createElement('div'); + this.el.className = 'hud'; + this.el.innerHTML = ` + 🗺️ + 0 / 0 + `; + root.appendChild(this.el); + /** @type {HTMLElement} 数字节点。 */ + this.countEl = this.el.querySelector('.hud-count'); + } + + /** + * 初始化:读取初始快照并订阅进度事件。 + * + * @returns {void} + */ + init() { + eventBus.on('state:changed', (snapshot) => { + const text = `${snapshot.unlocked.length} / ${snapshot.total}`; + const changed = this.countEl.textContent !== text; + this.countEl.textContent = text; + if (changed) { + // 重排 animation 使数字跳动每次都触发。 + this.el.classList.remove('pop'); + void this.el.offsetWidth; + this.el.classList.add('pop'); + } + }); + } +} diff --git a/src/ui/PhotoModal.js b/src/ui/PhotoModal.js new file mode 100644 index 0000000..444b078 --- /dev/null +++ b/src/ui/PhotoModal.js @@ -0,0 +1,175 @@ +/** + * @fileoverview 拍立得照片弹窗组件(PhotoModal)。 + * + * 文件用途: + * - 景点触发后的全屏弹窗:拍立得照片叠沓翻看 + 手账文案 + 关闭恢复。 + * + * 核心职责: + * 1. 监听 poi:reached 展示景点照片(多张时叠成一沓,点击翻下一张); + * 2. 拍立得视觉:白边相纸 + 手写 caption + 角标日期 + 轻微随机旋转; + * 3. 照片缺失降级:img 加载失败时替换为"照片待补"占位卡; + * 4. 关闭(按钮 / 遮罩 / ESC / 空格)后广播 ui:closed 交还输入。 + * + * 主要流程: + * MapScene overlap → poi:reached → show(spot) → 用户翻看/关闭 → + * emit('ui:closed', { spotId }) → MapScene 解锁并进入冷却。 + */ + +import { eventBus } from '../event-bus.js'; +import * as gameState from '../game-state.js'; + +/** + * 拍立得弹窗 DOM 组件。 + */ +export class PhotoModal { + /** + * 创建并挂载弹窗 DOM,监听 poi:reached。 + * + * @param {HTMLElement} root #ui 根容器。 + */ + constructor(root) { + /** @type {Object|null} 当前展示的景点。 */ + this.spot = null; + /** @type {number} 当前翻到的照片序号。 */ + this.index = 0; + /** @type {boolean} 翻页防抖锁(300ms 动画期间忽略连点)。 */ + this.flipping = false; + /** @type {Function|null} ESC 监听器(展示时绑定,关闭时解绑)。 */ + this.keyHandler = null; + + /** @type {HTMLElement} 组件根节点。 */ + this.el = document.createElement('section'); + this.el.className = 'photo-modal hidden'; + this.el.innerHTML = ` +
+
+
+ + + +
+
+

+

点击照片翻看下一张 · 点击空白处关闭

+
+ + `; + root.appendChild(this.el); + + this.stack = this.el.querySelector('.pm-stack'); + this.el.querySelector('.pm-backdrop').addEventListener('click', () => this.close()); + this.el.querySelector('.pm-close').addEventListener('click', () => this.close()); + eventBus.on('poi:reached', ({ spot }) => this.show(spot)); + } + + /** + * 展示景点拍立得弹窗。 + * + * @param {Object} spot 景点配置。 + * @returns {void} + */ + show(spot) { + this.spot = spot; + this.index = 0; + this.renderStack(); + + this.el.querySelector('.pm-name').textContent = spot.name; + this.el.querySelector('.pm-count').textContent = + spot.photos.length > 1 ? `${this.index + 1} / ${spot.photos.length}` : ''; + this.el.querySelector('.pm-story').textContent = spot.story; + this.el.querySelector('.pm-badge').hidden = gameState.isUnlocked(spot.id); + this.el.classList.remove('hidden'); + + this.keyHandler = (e) => { + if (e.key === 'Escape' || e.key === ' ') { + e.preventDefault(); + this.close(); + } + }; + window.addEventListener('keydown', this.keyHandler); + } + + /** + * 渲染拍立得叠沓:当前张置顶,后续两张错位垫底。 + * + * @returns {void} + */ + renderStack() { + const { photos, date } = this.spot; + this.stack.innerHTML = ''; + const visible = Math.min(3, photos.length - this.index); + for (let i = 0; i < visible; i += 1) { + const photoIdx = this.index + i; + const card = document.createElement('figure'); + card.className = `polaroid pos-${i}`; + // 每张固定随机小角度,营造随手摆放的手账感。 + card.style.setProperty('--tilt', `${(photoIdx * 13) % 9 - 4}deg`); + card.innerHTML = ` +
+ ${photos[photoIdx].caption} +
+
${photos[photoIdx].caption}
+ ${date} + `; + // 照片缺失降级为"照片待补"占位。 + const img = card.querySelector('img'); + img.addEventListener('error', () => { + const fallback = document.createElement('div'); + fallback.className = 'pl-missing'; + fallback.innerHTML = '📷 照片待补'; + img.replaceWith(fallback); + }); + // 点击当前张翻下一张(末张回到第一张循环)。 + card.addEventListener('click', () => { + if (i === 0) { + this.flipNext(); + } + }); + this.stack.appendChild(card); + } + } + + /** + * 翻页:当前张飞出 → 300ms 后切换序号重渲叠沓(带防抖锁防连点)。 + * + * @returns {void} + */ + flipNext() { + if (this.flipping) { + return; + } + const top = this.stack.querySelector('.pos-0'); + if (!top) { + return; + } + this.flipping = true; + top.classList.add('fly'); + const { photos } = this.spot; + const next = (this.index + 1) % photos.length; + setTimeout(() => { + this.index = next; + this.flipping = false; + this.renderStack(); + this.el.querySelector('.pm-count').textContent = + photos.length > 1 ? `${this.index + 1} / ${photos.length}` : ''; + }, 300); + } + + /** + * 关闭弹窗并交还输入给 Phaser 层。 + * + * @returns {void} + */ + close() { + if (this.el.classList.contains('hidden')) { + return; + } + this.el.classList.add('hidden'); + if (this.keyHandler) { + window.removeEventListener('keydown', this.keyHandler); + this.keyHandler = null; + } + eventBus.emit('ui:closed', { spotId: this.spot?.id }); + this.spot = null; + } +} diff --git a/src/ui/TitleOverlay.js b/src/ui/TitleOverlay.js new file mode 100644 index 0000000..0805b7f --- /dev/null +++ b/src/ui/TitleOverlay.js @@ -0,0 +1,118 @@ +/** + * @fileoverview 封面页组件(TitleOverlay)。 + * + * 文件用途: + * - 首屏手账封面:标题、进度概览、开始/继续旅行与重新开始入口。 + * + * 核心职责: + * 1. 渲染手账封面(纸纹底 + 大标题 + 装饰拍立得 + 胶带贴纸); + * 2. 有存档时显示"继续旅行 (n/total)"并附"重新开始"次级入口; + * 3. 点击开始后放行游戏输入(ui:release-input / ui:closed)。 + * + * 主要流程: + * mountUi 调用 show() → 用户点击主按钮 → hide + 双事件放行 → + * MapScene 接管输入(若尚未创建则回查 inputReleased 标志)。 + */ + +import { eventBus } from '../event-bus.js'; +import * as gameState from '../game-state.js'; + +/** + * 封面页 DOM 组件。 + */ +export class TitleOverlay { + /** + * 创建并挂载封面 DOM。 + * + * @param {HTMLElement} root #ui 根容器。 + */ + constructor(root) { + /** @type {HTMLElement} 组件根节点。 */ + this.el = document.createElement('section'); + this.el.className = 'title-overlay'; + this.el.innerHTML = ` +
+
+
+
+
+
🗺️
+
国庆 · 长安行
+
+

西安旅行记忆

+

一座可以走进去的旅行手账

+ + + +

走进地图上的景点,翻开属于你的拍立得回忆

+
+
+ `; + root.appendChild(this.el); + + this.el.querySelector('.to-start').addEventListener('click', () => this.handleStart()); + this.el.querySelector('.to-reset').addEventListener('click', () => this.handleReset()); + } + + /** + * 显示封面(按存档状态装配文案)。 + * + * @param {Object} options 显示选项。 + * @param {boolean} options.hasSave 是否存在存档。 + * @param {Object} options.snapshot 进度快照。 + * @returns {void} + */ + show({ hasSave, snapshot }) { + const startBtn = this.el.querySelector('.to-start'); + const resetBtn = this.el.querySelector('.to-reset'); + const progress = this.el.querySelector('.to-progress'); + if (hasSave && snapshot.unlocked.length > 0) { + startBtn.textContent = `继续旅行`; + resetBtn.hidden = false; + progress.hidden = false; + progress.textContent = `已收集 ${snapshot.unlocked.length} / ${snapshot.total} 个回忆`; + } else { + startBtn.textContent = '开始旅行'; + resetBtn.hidden = true; + progress.hidden = true; + } + this.el.classList.remove('hidden'); + } + + /** + * 隐藏封面。 + * + * @returns {void} + */ + hide() { + this.el.classList.add('hidden'); + } + + /** + * 开始/继续:放行输入并关闭封面(带翻页淡出过渡)。 + * + * @returns {void} + */ + handleStart() { + this.el.classList.add('leaving'); + eventBus.emit('ui:release-input'); + eventBus.emit('ui:closed', {}); + setTimeout(() => { + this.hide(); + this.el.classList.remove('leaving'); + }, 600); + } + + /** + * 重新开始:确认后清档并整页刷新。 + * + * @returns {void} + */ + handleReset() { + const confirmed = window.confirm('确定清空全部收集进度,重新开始这次旅行吗?'); + if (confirmed) { + gameState.resetProgress(); + window.location.reload(); + } + } +} diff --git a/src/ui/dom-layer.js b/src/ui/dom-layer.js new file mode 100644 index 0000000..f80bbe2 --- /dev/null +++ b/src/ui/dom-layer.js @@ -0,0 +1,66 @@ +/** + * @fileoverview DOM UI 层管理器(dom-layer)。 + * + * 文件用途: + * - 挂载并协调全部 DOM UI 组件(封面 / 拍立得弹窗 / HUD / 纪念页 / 竖屏提示)。 + * + * 核心职责: + * 1. 组件生命周期:mount 后由各组件自行监听 eventBus 事件驱动显示; + * 2. 维护 inputReleased 标志:封面"开始旅行"放行输入(MapScene 可能晚于 + * 事件创建,需在 create 时回查该标志,避免输入锁死); + * 3. 组件互斥由组件自身在 show/hide 时控制,层管理器只负责装配。 + * + * 主要流程: + * main.js 调用 mountUi() → 各组件挂载到 #ui 容器 → TitleOverlay 首屏 + * 显示 → 用户交互 → 各组件 show/hide 并通过 eventBus 通知 Phaser 层。 + */ + +import { eventBus } from '../event-bus.js'; +import * as gameState from '../game-state.js'; +import { TitleOverlay } from './TitleOverlay.js'; +import { PhotoModal } from './PhotoModal.js'; +import { Hud } from './Hud.js'; +import { CompleteOverlay } from './CompleteOverlay.js'; +import { initRotateHint } from './rotate-hint.js'; + +/** + * 游戏输入是否已被 UI 放行(封面页"开始旅行"后为 true)。 + * + * @type {boolean} + */ +export let inputReleased = false; + +/** + * 放行游戏输入并广播 ui:closed(MapScene create 晚于事件时回查本标志)。 + * + * @returns {void} + */ +export function releaseInput() { + inputReleased = true; +} + +/** + * 装配 DOM UI 层:创建根节点并挂载全部组件。 + * + * @returns {void} + */ +export function mountUi() { + const root = document.getElementById('ui'); + + // 事件桥上暴露放行动作,供封面页调用。 + eventBus.on('ui:release-input', () => releaseInput()); + + const hud = new Hud(root); + const title = new TitleOverlay(root); + const photoModal = new PhotoModal(root); + const complete = new CompleteOverlay(root); + + // 组件装配后初始化:封面首屏显示(覆盖资源加载过程),其余待事件驱动。 + title.show({ + hasSave: gameState.hasSave(), + snapshot: gameState.loadProgress(), + }); + hud.init(); + + initRotateHint(); +} diff --git a/src/ui/rotate-hint.js b/src/ui/rotate-hint.js new file mode 100644 index 0000000..6452eab --- /dev/null +++ b/src/ui/rotate-hint.js @@ -0,0 +1,52 @@ +/** + * @fileoverview 竖屏提示组件(rotate-hint)。 + * + * 文件用途: + * - 触摸设备处于竖屏时全屏提示旋转手机,回到横屏自动恢复。 + * + * 核心职责: + * 1. 检测触摸能力与屏幕方向(orientation media query + resize 兜底); + * 2. 控制 index.html 中 #rotate-hint 的显示与隐藏。 + * + * 主要流程: + * initRotateHint() 注册监听 → 竖屏 + 触摸 → 显示提示层 → 横屏 → 隐藏。 + */ + +/** + * 初始化竖屏提示监听。 + * + * @returns {void} + */ +export function initRotateHint() { + /** @type {HTMLElement} 提示层节点。 */ + const el = document.getElementById('rotate-hint'); + if (!el) { + return; + } + + /** + * 判断当前是否应显示提示(触摸设备 + 竖屏)。 + * + * @returns {boolean} 需要提示返回 true。 + */ + const shouldShow = () => { + const hasTouch = + 'ontouchstart' in window || (navigator.maxTouchPoints ?? 0) > 0; + const portrait = window.innerHeight > window.innerWidth; + return hasTouch && portrait; + }; + + /** + * 应用显示状态。 + * + * @returns {void} + */ + const apply = () => { + el.classList.toggle('show', shouldShow()); + }; + + const mql = window.matchMedia('(orientation: portrait)'); + mql.addEventListener?.('change', apply); + window.addEventListener('resize', apply); + apply(); +} diff --git a/src/ui/styles.css b/src/ui/styles.css new file mode 100644 index 0000000..47564d6 --- /dev/null +++ b/src/ui/styles.css @@ -0,0 +1,651 @@ +/** + * @fileoverview DOM UI 层全局样式(手账旅行风)。 + * + * 文件用途: + * - 定义全站视觉基调与各 UI 组件样式:封面页、拍立得弹窗、HUD、 + * 纪念页、竖屏提示、通用按钮。 + * + * 核心职责: + * 1. 视觉基调:羊皮纸底 / 棕褐墨色 / 暖橙强调 / 楷体手写感; + * 2. 布局体系:#ui 覆盖层(pointer-events 按组件开合)与全屏组件纸底; + * 3. 动效:拍立得叠沓翻页、封面翻页淡出、HUD 数字跳动、盖章质感。 + * + * 主要流程: + * index.html 引入 → 各组件仅依赖 class 命名(BEM 式前缀),无内联样式依赖。 + */ + +/* ══════════ 视觉基调变量 ══════════ */ +:root { + --paper: #f5ecd7; + --paper-deep: #efe3c8; + --ink: #6b4e2e; + --ink-soft: #8a6a42; + --ink-faint: #b08a5e; + --accent: #e67e22; + --accent-deep: #c96f4a; + --card: #fffdf7; + --shadow: 0 6px 18px rgba(60, 42, 20, 0.35); + --font-hand: 'Kaiti SC', 'STKaiti', 'KaiTi', serif; + --font-body: 'Kaiti SC', 'STKaiti', 'KaiTi', serif; +} + +/* ══════════ 全局重置 ══════════ */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + width: 100%; + height: 100%; + overflow: hidden; + touch-action: none; + -webkit-user-select: none; + user-select: none; + background: var(--paper); + font-family: var(--font-body); + color: var(--ink); +} + +#game { + position: fixed; + inset: 0; +} + +/* UI 层:覆盖全屏,子组件自行开启 pointer-events。 */ +#ui { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 10; +} + +#ui > * { + pointer-events: auto; +} + +.hidden { + display: none !important; +} + +/* ══════════ 通用按钮 ══════════ */ +.btn-primary { + font-family: var(--font-hand); + font-size: clamp(18px, 2.6vmin, 26px); + color: var(--ink); + background: var(--card); + border: 2.5px solid var(--ink-soft); + border-radius: 999px; + padding: 0.55em 2.2em; + cursor: pointer; + box-shadow: 0 3px 0 rgba(107, 78, 46, 0.35); + transition: transform 0.18s ease, box-shadow 0.18s ease; + letter-spacing: 0.12em; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 5px 0 rgba(107, 78, 46, 0.35); +} + +.btn-primary:active { + transform: translateY(1px); + box-shadow: 0 1px 0 rgba(107, 78, 46, 0.35); +} + +.btn-ghost { + font-family: var(--font-hand); + font-size: clamp(14px, 1.8vmin, 17px); + color: var(--ink-faint); + background: none; + border: none; + border-bottom: 1.5px dashed var(--ink-faint); + cursor: pointer; + padding: 0.3em 0.6em; + letter-spacing: 0.1em; +} + +.btn-ghost:hover { + color: var(--ink-soft); + border-bottom-color: var(--ink-soft); +} + +/* ══════════ 封面页 ══════════ */ +.title-overlay { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.6s ease, transform 0.6s ease; +} + +.title-overlay.leaving { + opacity: 0; + transform: scale(1.04); + pointer-events: none; +} + +/* 纸张底:多层渐变模拟羊皮纸 + 边缘做旧。 */ +.to-paper { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: + radial-gradient(ellipse at 28% 22%, rgba(255, 251, 235, 0.7), transparent 55%), + radial-gradient(ellipse at 75% 78%, rgba(190, 155, 100, 0.22), transparent 50%), + radial-gradient(ellipse at 50% 50%, transparent 40%, rgba(160, 125, 80, 0.16) 100%), + var(--paper); + box-shadow: inset 0 0 120px rgba(150, 115, 70, 0.35); +} + +.to-content { + display: flex; + flex-direction: column; + align-items: center; + gap: clamp(10px, 2.2vmin, 22px); + text-align: center; +} + +/* 封面装饰拍立得。 */ +.to-polaroid { + background: var(--card); + padding: 10px 10px 14px; + box-shadow: var(--shadow); + transform: rotate(-3deg); + margin-bottom: clamp(6px, 1.5vmin, 14px); +} + +.to-cover-photo { + width: clamp(120px, 18vmin, 190px); + height: clamp(90px, 13.5vmin, 142px); + display: flex; + align-items: center; + justify-content: center; + font-size: clamp(48px, 8vmin, 84px); + background: linear-gradient(150deg, #f6e7c8, #edd9b0); + border-radius: 2px; +} + +.to-cover-caption { + font-family: var(--font-hand); + font-size: clamp(13px, 1.8vmin, 17px); + color: var(--ink-soft); + padding-top: 8px; + letter-spacing: 0.2em; +} + +.to-title { + font-family: var(--font-hand); + font-size: clamp(38px, 7.5vmin, 78px); + color: var(--ink); + letter-spacing: 0.14em; + text-shadow: 2px 3px 0 rgba(201, 168, 117, 0.35); +} + +.to-subtitle { + font-family: var(--font-hand); + font-size: clamp(15px, 2.2vmin, 22px); + color: var(--ink-faint); + letter-spacing: 0.32em; +} + +.to-progress { + font-family: var(--font-hand); + font-size: clamp(13px, 1.9vmin, 18px); + color: var(--accent-deep); + background: rgba(230, 126, 34, 0.12); + border: 1.5px dashed rgba(230, 126, 34, 0.5); + border-radius: 999px; + padding: 0.3em 1.4em; +} + +.to-start { + margin-top: clamp(4px, 1vmin, 10px); +} + +.to-reset { + margin-top: -4px; +} + +.to-foot { + position: absolute; + bottom: clamp(18px, 4vmin, 40px); + font-size: clamp(12px, 1.6vmin, 15px); + color: var(--ink-faint); + letter-spacing: 0.14em; + opacity: 0.85; +} + +/* 胶带贴纸装饰。 */ +.to-deco { + position: absolute; + width: clamp(90px, 14vmin, 150px); + height: clamp(26px, 4vmin, 40px); + background: rgba(233, 214, 160, 0.65); + border-left: 1px dashed rgba(160, 130, 80, 0.4); + border-right: 1px dashed rgba(160, 130, 80, 0.4); + box-shadow: 0 2px 6px rgba(80, 60, 40, 0.18); +} + +.to-tape-1 { + top: 9%; + left: 12%; + transform: rotate(-18deg); +} + +.to-tape-2 { + bottom: 14%; + right: 10%; + transform: rotate(24deg); +} + +/* ══════════ 拍立得弹窗 ══════════ */ +.photo-modal { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.pm-backdrop { + position: absolute; + inset: 0; + background: rgba(50, 36, 20, 0.55); + backdrop-filter: blur(2px); +} + +.pm-stage { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: clamp(8px, 1.8vmin, 18px); + max-width: min(84vw, 760px); + animation: pm-enter 0.38s cubic-bezier(0.2, 1.4, 0.4, 1); +} + +@keyframes pm-enter { + from { + opacity: 0; + transform: translateY(26px) scale(0.92); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.pm-header { + display: flex; + align-items: center; + gap: 14px; +} + +.pm-name { + font-family: var(--font-hand); + font-size: clamp(26px, 4.2vmin, 42px); + color: #fff8ec; + letter-spacing: 0.18em; + text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); +} + +.pm-badge { + font-family: var(--font-hand); + font-size: clamp(12px, 1.7vmin, 16px); + color: #ffdf9e; + background: rgba(201, 111, 74, 0.85); + border-radius: 999px; + padding: 0.25em 0.9em; + transform: rotate(-2deg); + animation: badge-wiggle 1.6s ease-in-out infinite; +} + +@keyframes badge-wiggle { + 0%, 100% { transform: rotate(-2deg) scale(1); } + 50% { transform: rotate(2deg) scale(1.05); } +} + +.pm-count { + font-family: var(--font-hand); + font-size: clamp(13px, 1.8vmin, 17px); + color: rgba(255, 248, 236, 0.75); +} + +/* 拍立得叠沓。 */ +.pm-stack { + position: relative; + width: clamp(300px, 52vmin, 560px); + aspect-ratio: 4 / 4.55; +} + +.polaroid { + position: absolute; + inset: 0; + background: var(--card); + padding: 4.5% 4.5% 12%; + box-shadow: var(--shadow); + transform: rotate(var(--tilt, 0deg)); + cursor: pointer; + transition: transform 0.3s ease, opacity 0.3s ease; +} + +.polaroid.pos-0 { + z-index: 3; +} + +.polaroid.pos-0:hover { + transform: rotate(var(--tilt, 0deg)) scale(1.02); +} + +.polaroid.pos-1 { + z-index: 2; + transform: rotate(calc(var(--tilt, 0deg) + 4deg)) translateY(4.5%) scale(0.97); + filter: brightness(0.96); +} + +.polaroid.pos-2 { + z-index: 1; + transform: rotate(calc(var(--tilt, 0deg) + 8deg)) translateY(9%) scale(0.94); + filter: brightness(0.92); +} + +/* 翻页飞出动画。 */ +.polaroid.fly { + transform: translateX(135%) rotate(16deg) scale(0.9); + opacity: 0; +} + +.pl-photo { + width: 100%; + aspect-ratio: 4 / 3; + background: var(--paper-deep); + border-radius: 2px; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +.pl-photo img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.pl-missing { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + height: 100%; + font-family: var(--font-hand); + font-size: clamp(16px, 2.4vmin, 24px); + color: var(--ink-soft); + border: 2.5px dashed rgba(138, 106, 66, 0.4); + border-radius: 4px; +} + +.pl-caption { + font-family: var(--font-hand); + font-size: clamp(15px, 2.2vmin, 22px); + color: var(--ink-soft); + text-align: center; + margin-top: 3.5%; + letter-spacing: 0.08em; +} + +.pl-date { + position: absolute; + right: 6%; + bottom: 3.5%; + font-family: var(--font-hand); + font-size: clamp(11px, 1.6vmin, 15px); + color: var(--ink-faint); + transform: rotate(-1.5deg); +} + +.pm-story { + font-family: var(--font-hand); + font-size: clamp(14px, 2vmin, 19px); + color: rgba(255, 248, 236, 0.9); + text-align: center; + max-width: 90%; + line-height: 1.7; + letter-spacing: 0.06em; +} + +.pm-hint { + font-family: var(--font-hand); + font-size: clamp(11px, 1.5vmin, 14px); + color: rgba(255, 248, 236, 0.55); + letter-spacing: 0.12em; +} + +.pm-close { + position: absolute; + top: clamp(14px, 3vmin, 30px); + right: clamp(14px, 3vmin, 30px); + width: clamp(38px, 5.5vmin, 52px); + height: clamp(38px, 5.5vmin, 52px); + border-radius: 50%; + border: 2px solid rgba(255, 248, 236, 0.7); + background: rgba(80, 56, 30, 0.5); + color: #fff8ec; + font-size: clamp(16px, 2.4vmin, 22px); + cursor: pointer; + transition: background 0.2s ease, transform 0.2s ease; +} + +.pm-close:hover { + background: rgba(201, 111, 74, 0.9); + transform: rotate(90deg); +} + +/* ══════════ HUD ══════════ */ +.hud { + position: absolute; + top: calc(clamp(12px, 2.4vmin, 24px) + env(safe-area-inset-top)); + right: calc(clamp(12px, 2.4vmin, 24px) + env(safe-area-inset-right)); + display: flex; + align-items: center; + gap: 8px; + background: rgba(245, 236, 215, 0.88); + border: 2px solid var(--ink-soft); + border-radius: 999px; + padding: 0.35em 1.1em; + box-shadow: 0 3px 10px rgba(60, 42, 20, 0.25); + pointer-events: none; +} + +.hud-icon { + font-size: clamp(16px, 2.4vmin, 22px); +} + +.hud-count { + font-family: var(--font-hand); + font-size: clamp(14px, 2vmin, 19px); + color: var(--ink); + letter-spacing: 0.1em; +} + +.hud.pop { + animation: hud-pop 0.5s cubic-bezier(0.2, 1.6, 0.4, 1); +} + +@keyframes hud-pop { + 0% { transform: scale(1); } + 40% { transform: scale(1.18) rotate(-2deg); } + 100% { transform: scale(1); } +} + +/* ══════════ 纪念页 ══════════ */ +.complete-overlay { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + overflow-y: auto; +} + +.co-paper { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: + radial-gradient(ellipse at 30% 20%, rgba(255, 251, 235, 0.7), transparent 55%), + radial-gradient(ellipse at 75% 80%, rgba(190, 155, 100, 0.25), transparent 50%), + var(--paper); + box-shadow: inset 0 0 120px rgba(150, 115, 70, 0.35); + animation: co-enter 0.5s ease; +} + +@keyframes co-enter { + from { opacity: 0; transform: scale(1.05); } + to { opacity: 1; transform: scale(1); } +} + +.co-content { + display: flex; + flex-direction: column; + align-items: center; + gap: clamp(10px, 2vmin, 20px); + text-align: center; + padding: clamp(20px, 5vmin, 50px) clamp(16px, 4vmin, 40px); + max-height: 100%; +} + +.co-title { + font-family: var(--font-hand); + font-size: clamp(32px, 6vmin, 62px); + color: var(--ink); + letter-spacing: 0.14em; +} + +.co-subtitle { + font-family: var(--font-hand); + font-size: clamp(14px, 2vmin, 20px); + color: var(--ink-faint); + letter-spacing: 0.2em; +} + +.co-stats { + display: flex; + gap: clamp(24px, 5vmin, 60px); + margin: clamp(4px, 1vmin, 10px) 0; +} + +.co-stat { + display: flex; + flex-direction: column; + align-items: center; +} + +.co-stat b { + font-family: var(--font-hand); + font-size: clamp(30px, 5.5vmin, 56px); + color: var(--accent-deep); +} + +.co-stat span { + font-family: var(--font-hand); + font-size: clamp(13px, 1.8vmin, 17px); + color: var(--ink-soft); + letter-spacing: 0.2em; +} + +/* 九宫格(每景点一张 mini 拍立得)。 */ +.co-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: clamp(10px, 1.8vmin, 18px); + margin-top: clamp(6px, 1.4vmin, 14px); +} + +/* 小屏(手机横屏)降为两行布局。 */ +@media (max-width: 860px) { + .co-grid { + grid-template-columns: repeat(5, 1fr); + gap: 8px; + } +} + +.co-card { + background: var(--card); + padding: 8% 6% 12%; + box-shadow: 0 3px 10px rgba(60, 42, 20, 0.28); + transform: rotate(var(--tilt, 0deg)); + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; +} + +.co-photo { + font-size: clamp(24px, 4.6vmin, 46px); +} + +.co-name { + font-family: var(--font-hand); + font-size: clamp(11px, 1.7vmin, 17px); + color: var(--ink); + white-space: nowrap; +} + +.co-date { + font-family: var(--font-hand); + font-size: clamp(9px, 1.4vmin, 13px); + color: var(--ink-faint); +} + +.co-continue { + margin-top: clamp(10px, 2.4vmin, 24px); +} + +/* ══════════ 竖屏提示 ══════════ */ +#rotate-hint { + position: fixed; + inset: 0; + z-index: 100; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 18px; + background: var(--paper); + text-align: center; +} + +#rotate-hint.show { + display: flex; +} + +.rotate-phone { + font-size: 64px; + animation: phone-rotate 1.8s ease-in-out infinite; +} + +@keyframes phone-rotate { + 0%, 25% { transform: rotate(0deg); } + 60%, 100% { transform: rotate(90deg); } +} + +#rotate-hint p { + font-family: var(--font-hand); + font-size: 18px; + color: var(--ink-soft); + line-height: 1.8; + letter-spacing: 0.1em; +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..0eeff27 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,25 @@ +/** + * @fileoverview Vite 构建配置。 + * + * 文件用途: + * - 定义西安旅行记忆游戏的开发服务器与生产构建行为。 + * + * 核心职责: + * 1. base 使用相对路径 './',便于部署到 GitHub Pages 子目录或任意静态目录; + * 2. 关闭资源内联(assetsInlineLimit: 0),避免大图被转 base64 拖慢加载; + * 3. 开发服务器开放局域网访问(host: true),方便手机真机调试。 + */ + +import { defineConfig } from 'vite'; + +export default defineConfig({ + base: './', + build: { + outDir: 'dist', + assetsInlineLimit: 0, + }, + server: { + host: true, + port: 3000, + }, +});