diff --git a/game/ui/MapTransitionModal.ts b/game/ui/MapTransitionModal.ts new file mode 100644 index 0000000..0325514 --- /dev/null +++ b/game/ui/MapTransitionModal.ts @@ -0,0 +1,187 @@ +import type { MapConfig } from '../data/mapConfigs' +import { ALL_MAPS } from '../data/mapConfigs' +import { GameManager } from '../GameManager' + +/** + * 地图切换过渡弹窗(DOM 层,覆盖 canvas) + * 显示当前地图通关信息,3秒后自动关闭并触发下一张地图加载 + */ +export class MapTransitionModal { + private container: HTMLElement | null = null + private autoCloseTimer: ReturnType | null = null + + /** + * 显示地图过关弹窗 + * @param clearedMap 刚通关的地图配置 + * @param onNext 3秒后自动触发的回调(加载下一张地图) + */ + show(clearedMap: MapConfig, onNext: () => void): void { + this.cleanup() + const manager = GameManager.getInstance() + const nextMapIndex = manager.currentMapIndex + 1 + const nextMap = ALL_MAPS[nextMapIndex] + const isFinalMap = !nextMap + + this.createDOM(clearedMap, nextMap ?? null, isFinalMap, onNext) + } + + private createDOM( + clearedMap: MapConfig, + nextMap: MapConfig | null, + isFinalMap: boolean, + onNext: () => void + ): void { + const manager = GameManager.getInstance() + const kpi = manager.kpi + const hcReward = 100 + + const overlay = document.createElement('div') + overlay.id = 'map-transition-overlay' + overlay.style.cssText = ` + position: fixed; + inset: 0; + background: rgba(0,0,0,0.85); + z-index: 9998; + display: flex; + align-items: center; + justify-content: center; + font-family: 'VT323', monospace; + animation: mtFadeIn 0.4s ease; + ` + + const card = document.createElement('div') + card.style.cssText = ` + background: #0f1b2d; + border: 2px solid #7c3aed; + border-radius: 16px; + padding: 36px 48px; + max-width: 520px; + width: 90%; + box-shadow: 0 0 60px rgba(124,58,237,0.6); + text-align: center; + animation: mtSlideIn 0.4s ease; + ` + + // 过关标题 + const titleEl = document.createElement('div') + titleEl.textContent = isFinalMap ? '全部通关!最终胜利!' : `过关!` + titleEl.style.cssText = ` + font-size: 36px; + color: #fbbf24; + margin-bottom: 8px; + letter-spacing: 3px; + ` + card.appendChild(titleEl) + + // 地图名称 + const mapNameEl = document.createElement('div') + mapNameEl.textContent = `「${clearedMap.name}」已清场` + mapNameEl.style.cssText = ` + font-size: 22px; + color: #a78bfa; + margin-bottom: 20px; + ` + card.appendChild(mapNameEl) + + // 分割线 + const hr = document.createElement('div') + hr.style.cssText = ` + height: 1px; + background: linear-gradient(90deg, transparent, #7c3aed, transparent); + margin-bottom: 16px; + ` + card.appendChild(hr) + + // KPI & 奖励 + const statsEl = document.createElement('div') + statsEl.innerHTML = `KPI: ${kpi}%  |  获得奖励: +${hcReward} HC` + statsEl.style.cssText = ` + font-size: 20px; + color: #e2e8f0; + margin-bottom: 24px; + ` + card.appendChild(statsEl) + + // 下一关提示 / 最终胜利提示 + const nextEl = document.createElement('div') + if (isFinalMap) { + nextEl.textContent = '恭喜完成全部关卡!你是真正的打工人传奇!' + nextEl.style.cssText = ` + font-size: 18px; + color: #fbbf24; + background: rgba(251,191,36,0.1); + border: 1px solid #fbbf24; + border-radius: 8px; + padding: 12px 16px; + margin-bottom: 20px; + ` + } else { + nextEl.innerHTML = `下一关: ${nextMap!.name}` + nextEl.style.cssText = ` + font-size: 20px; + color: #e2e8f0; + background: rgba(96,165,250,0.1); + border: 1px solid #3b82f6; + border-radius: 8px; + padding: 12px 16px; + margin-bottom: 20px; + ` + } + card.appendChild(nextEl) + + // 倒计时提示 + const countdownEl = document.createElement('div') + countdownEl.id = 'mt-countdown' + countdownEl.textContent = isFinalMap ? '3秒后返回主菜单...' : '3秒后自动进入下一关...' + countdownEl.style.cssText = ` + font-size: 16px; + color: #6b7280; + ` + card.appendChild(countdownEl) + + // 注入动画 + const style = document.createElement('style') + style.textContent = ` + @keyframes mtFadeIn { from { opacity: 0 } to { opacity: 1 } } + @keyframes mtSlideIn { from { transform: scale(0.85); opacity: 0 } to { transform: scale(1); opacity: 1 } } + ` + document.head.appendChild(style) + + overlay.appendChild(card) + document.body.appendChild(overlay) + this.container = overlay + + // 给 HC 奖励 + manager.addHC(hcReward) + + // 3秒自动触发 + let remaining = 3 + const tick = () => { + remaining-- + const el = document.getElementById('mt-countdown') + if (el) el.textContent = isFinalMap + ? `${remaining}秒后返回主菜单...` + : `${remaining}秒后自动进入下一关...` + } + const interval = setInterval(tick, 1000) + this.autoCloseTimer = setTimeout(() => { + clearInterval(interval) + this.cleanup() + onNext() + }, 3000) + } + + cleanup(): void { + if (this.autoCloseTimer !== null) { + clearTimeout(this.autoCloseTimer) + this.autoCloseTimer = null + } + const el = document.getElementById('map-transition-overlay') + if (el) el.remove() + this.container = null + } + + destroy(): void { + this.cleanup() + } +}