diff --git a/game/ui/WeeklyReportModal.ts b/game/ui/WeeklyReportModal.ts new file mode 100644 index 0000000..6869a0b --- /dev/null +++ b/game/ui/WeeklyReportModal.ts @@ -0,0 +1,285 @@ +import { GameManager } from '../GameManager' +import { generateWeeklyOptions } from '../data/buzzwords' + +export type WeeklyReportCallbacks = { + onBossInspection: () => void + onFullStamina: () => void +} + +/** + * 周报结算弹窗 + * 每3波结束后触发,仿钉钉/飞书全屏通知风格 + * 用 DOM 层实现(z-index: 9999),覆盖 canvas + */ +export class WeeklyReportModal { + private container: HTMLElement | null = null + private timerInterval: ReturnType | null = null + private callbacks: WeeklyReportCallbacks + private onClose?: () => void + + constructor(callbacks: WeeklyReportCallbacks) { + this.callbacks = callbacks + } + + /** 显示弹窗 */ + show(onClose?: () => void): void { + this.onClose = onClose + this.createDOM() + this.startTimer() + } + + private createDOM(): void { + // 移除旧弹窗(防止重复) + this.cleanup() + + const { options, correctIndex } = generateWeeklyOptions() + + // 背景蒙层 + const overlay = document.createElement('div') + overlay.id = 'weekly-report-overlay' + overlay.style.cssText = ` + position: fixed; + inset: 0; + background: rgba(0,0,0,0.82); + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + font-family: 'VT323', monospace; + animation: fadeInOverlay 0.3s ease; + ` + + // 弹窗卡片 + const card = document.createElement('div') + card.style.cssText = ` + background: #1a1a2e; + border: 2px solid #7c3aed; + border-radius: 12px; + padding: 32px 40px; + max-width: 520px; + width: 90%; + box-shadow: 0 0 40px rgba(124,58,237,0.5); + position: relative; + animation: slideInCard 0.35s ease; + ` + + // 顶部进度条容器 + const progressWrap = document.createElement('div') + progressWrap.style.cssText = ` + position: absolute; + top: 0; left: 0; right: 0; + height: 6px; + background: #374151; + border-radius: 12px 12px 0 0; + overflow: hidden; + ` + const progressBar = document.createElement('div') + progressBar.id = 'weekly-progress-bar' + progressBar.style.cssText = ` + height: 100%; + background: #ef4444; + width: 100%; + transition: width linear; + ` + progressWrap.appendChild(progressBar) + card.appendChild(progressWrap) + + // 标题 + const title = document.createElement('div') + title.textContent = '📊 周报提交时间!' + title.style.cssText = ` + font-size: 28px; + color: #a78bfa; + text-align: center; + margin-bottom: 8px; + letter-spacing: 2px; + ` + card.appendChild(title) + + // 副标题 + const subtitle = document.createElement('div') + subtitle.textContent = '请选择正确的互联网黑话,提交本周工作总结' + subtitle.style.cssText = ` + font-size: 16px; + color: #9ca3af; + text-align: center; + margin-bottom: 8px; + ` + card.appendChild(subtitle) + + // 倒计时文字 + const timerText = document.createElement('div') + timerText.id = 'weekly-timer-text' + timerText.textContent = '10' + timerText.style.cssText = ` + font-size: 20px; + color: #fcd34d; + text-align: center; + margin-bottom: 24px; + ` + card.appendChild(timerText) + + // 选项区域 + const optionsWrap = document.createElement('div') + optionsWrap.style.cssText = ` + display: flex; + gap: 12px; + justify-content: center; + margin-bottom: 20px; + ` + + options.forEach((opt, idx) => { + const btn = document.createElement('button') + btn.textContent = opt + btn.dataset.index = String(idx) + btn.style.cssText = ` + flex: 1; + padding: 14px 8px; + background: #0f172a; + border: 2px solid #4b5563; + border-radius: 8px; + color: #e2e8f0; + font-family: 'VT323', monospace; + font-size: 20px; + cursor: pointer; + transition: border-color 0.2s, background 0.2s; + ` + btn.addEventListener('mouseenter', () => { + if (!btn.disabled) { + btn.style.borderColor = '#7c3aed' + btn.style.background = '#1e1b4b' + } + }) + btn.addEventListener('mouseleave', () => { + if (!btn.disabled) { + btn.style.borderColor = '#4b5563' + btn.style.background = '#0f172a' + } + }) + btn.addEventListener('click', () => { + this.handleAnswer(idx === correctIndex, options[correctIndex]) + }) + optionsWrap.appendChild(btn) + }) + card.appendChild(optionsWrap) + + // 结果区域(初始隐藏) + const resultArea = document.createElement('div') + resultArea.id = 'weekly-result' + resultArea.style.cssText = ` + display: none; + text-align: center; + padding: 12px; + border-radius: 8px; + font-size: 20px; + ` + card.appendChild(resultArea) + + // 注入动画样式 + const style = document.createElement('style') + style.textContent = ` + @keyframes fadeInOverlay { from { opacity: 0 } to { opacity: 1 } } + @keyframes slideInCard { from { transform: translateY(-30px); opacity: 0 } to { transform: translateY(0); opacity: 1 } } + ` + document.head.appendChild(style) + + overlay.appendChild(card) + document.body.appendChild(overlay) + this.container = overlay + } + + private startTimer(): void { + let remaining = 10 + const progressBar = document.getElementById('weekly-progress-bar') + const timerText = document.getElementById('weekly-timer-text') + + if (progressBar) { + // 使用 CSS transition 驱动进度条(10秒内从100%到0%) + requestAnimationFrame(() => { + progressBar.style.transition = 'width 10s linear' + progressBar.style.width = '0%' + }) + } + + this.timerInterval = setInterval(() => { + remaining-- + if (timerText) timerText.textContent = String(remaining) + if (remaining <= 0) { + this.stopTimer() + this.handleAnswer(false, '') + } + }, 1000) + } + + private stopTimer(): void { + if (this.timerInterval !== null) { + clearInterval(this.timerInterval) + this.timerInterval = null + } + } + + private handleAnswer(isCorrect: boolean, correctWord: string): void { + this.stopTimer() + + // 禁用所有按钮 + const buttons = this.container?.querySelectorAll('button') + buttons?.forEach(btn => { + (btn as HTMLButtonElement).disabled = true + ;(btn as HTMLButtonElement).style.opacity = '0.5' + ;(btn as HTMLButtonElement).style.cursor = 'default' + }) + + const resultArea = document.getElementById('weekly-result') + if (!resultArea) return + + if (isCorrect) { + // 随机给 HC 或补满精力 + if (Math.random() > 0.5) { + const reward = 50 + Math.floor(Math.random() * 51) + GameManager.getInstance().addHC(reward) + resultArea.textContent = `✓ 正确!"${correctWord}" 就是本周黑话!获得 ${reward} HC!` + resultArea.style.background = 'rgba(16,185,129,0.15)' + resultArea.style.border = '1px solid #10b981' + resultArea.style.color = '#6ee7b7' + } else { + this.callbacks.onFullStamina() + resultArea.textContent = `✓ 正确!"${correctWord}" 完美!全场精力已补满!` + resultArea.style.background = 'rgba(16,185,129,0.15)' + resultArea.style.border = '1px solid #10b981' + resultArea.style.color = '#6ee7b7' + } + } else { + this.callbacks.onBossInspection() + const msg = correctWord + ? `✗ 答错了!正确答案是"${correctWord}"!老板来视察了!全场禁锢3秒!` + : '✗ 超时了!老板来视察了!全场禁锢3秒!' + resultArea.textContent = msg + resultArea.style.background = 'rgba(239,68,68,0.15)' + resultArea.style.border = '1px solid #ef4444' + resultArea.style.color = '#fca5a5' + } + + resultArea.style.display = 'block' + + // 2秒后自动关闭 + setTimeout(() => { + this.close() + }, 2000) + } + + private close(): void { + this.cleanup() + this.onClose?.() + } + + private cleanup(): void { + this.stopTimer() + const existing = document.getElementById('weekly-report-overlay') + if (existing) existing.remove() + this.container = null + } + + destroy(): void { + this.cleanup() + } +}