Files
test1/app/game/TutorialModal.tsx

431 lines
16 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect } from 'react'
const STORAGE_KEY = 'dachang-tutorial-seen-v1'
// ── 教程步骤数据 ─────────────────────────────────────────────────────────────
const STEPS = [
{
id: 'story',
icon: '🏢',
title: '故事背景',
subtitle: '2026年某大厂总部',
content: (
<div style={{ lineHeight: 1.7 }}>
<p>
"组织架构调整"<span style={{ color: '#FBBF24' }}>VP</span>
"职场怪物"<b></b><b></b>
</p>
<p style={{ marginTop: '10px' }}>
<span style={{ color: '#EF4444' }}>KPI归零</span>
"毕业"
</p>
<p style={{ marginTop: '10px' }}>
<br />
<span style={{ color: '#A78BFA' }}>HC</span>线<br />
KPI
</p>
</div>
),
},
{
id: 'deploy',
icon: '🗺️',
title: '如何部署防线',
subtitle: '拖拽?不,点击就够了',
content: (
<div style={{ lineHeight: 1.7 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}></span>
<span><b style={{ color: '#A78BFA' }}></b> = </span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}></span>
<span><b style={{ color: '#22C55E' }}></b></span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}></span>
<span><b style={{ color: '#F59E0B' }}></b> 10 HC</span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}></span>
<span><b style={{ color: '#EC4899' }}></b></span>
</div>
<div style={{
marginTop: '6px',
backgroundColor: 'rgba(167,139,250,0.1)',
border: '1px solid rgba(167,139,250,0.3)',
borderRadius: '6px',
padding: '8px 10px',
fontSize: '13px',
color: '#CBD5E1',
}}>
💡 <b></b>
</div>
</div>
</div>
),
},
{
id: 'wave',
icon: '⚔️',
title: '战斗与波次',
subtitle: '召唤敌人才能赚HC',
content: (
<div style={{ lineHeight: 1.7 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}></span>
<span><b style={{ color: '#C4B5FD' }}></b> HC</span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}>🕐</span>
<span><b style={{ color: '#FBBF24' }}>2</b></span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}>📋</span>
<span>3<b style={{ color: '#FCD34D' }}></b>3</span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}>👑</span>
<span>5<b style={{ color: '#FBBF24' }}>BOSS波</b>VP会随机摧毁你的防御塔</span>
</div>
<div style={{
marginTop: '2px',
backgroundColor: 'rgba(239,68,68,0.1)',
border: '1px solid rgba(239,68,68,0.25)',
borderRadius: '6px',
padding: '8px 10px',
fontSize: '13px',
color: '#FCA5A5',
}}>
<b>3 × 5</b> HC
</div>
</div>
</div>
),
},
{
id: 'meeting',
icon: '📋',
title: '开会 & 发激励',
subtitle: '暂停游戏PUA员工',
content: (
<div style={{ lineHeight: 1.7 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}>🟢</span>
<span><b style={{ color: '#4ADE80' }}>📋 </b><b></b></span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}></span>
<span><b style={{ color: '#EC4899' }}>PUA激励台</b>//...AI会评分并给出游戏加成</span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}>🎯</span>
<span>
<span style={{ color: '#22C55E' }}> 4-6</span>
<span style={{ color: '#FBBF24' }}> 7-8</span>
<span style={{ color: '#FF4E00' }}> 9-10</span>
</span>
</div>
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
<span style={{ fontSize: '22px', flexShrink: 0 }}></span>
<span> = HC × 15%<b style={{ color: '#F97316' }}></b>HC</span>
</div>
<div style={{
marginTop: '2px',
backgroundColor: 'rgba(74,222,128,0.08)',
border: '1px solid rgba(74,222,128,0.25)',
borderRadius: '6px',
padding: '8px 10px',
fontSize: '13px',
color: '#86EFAC',
}}>
💡 <b></b>
</div>
</div>
</div>
),
},
{
id: 'tactics',
icon: '🧠',
title: '兵力部署建议',
subtitle: '老板传授的排兵布阵',
content: (
<div style={{ lineHeight: 1.7 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '9px' }}>
<div style={{
backgroundColor: 'rgba(34,197,94,0.1)',
border: '1px solid rgba(34,197,94,0.3)',
borderRadius: '6px', padding: '7px 10px',
}}>
<b style={{ color: '#22C55E' }}>1-2</b>
<br />002-3
</div>
<div style={{
backgroundColor: 'rgba(59,130,246,0.1)',
border: '1px solid rgba(59,130,246,0.3)',
borderRadius: '6px', padding: '7px 10px',
}}>
<b style={{ color: '#3B82F6' }}>3-4</b>
<br />PPT大师减速P6资深开发远程输出HRBP放中央提速
</div>
<div style={{
backgroundColor: 'rgba(239,68,68,0.1)',
border: '1px solid rgba(239,68,68,0.3)',
borderRadius: '6px', padding: '7px 10px',
}}>
<b style={{ color: '#EF4444' }}>BOSS波5</b>
<br />BOSS路径上
</div>
<div style={{
backgroundColor: 'rgba(167,139,250,0.1)',
border: '1px solid rgba(167,139,250,0.3)',
borderRadius: '6px', padding: '7px 10px',
}}>
<b style={{ color: '#A78BFA' }}></b>
<br /> &gt; 线HRBP永远放在其他塔的中间
</div>
</div>
</div>
),
},
]
// ── 教程弹窗组件 ─────────────────────────────────────────────────────────────
interface TutorialModalProps {
onClose: () => void
}
export function TutorialModal({ onClose }: TutorialModalProps) {
const [step, setStep] = useState(0)
const current = STEPS[step]
const isLast = step === STEPS.length - 1
const isFirst = step === 0
const handleClose = () => {
if (typeof window !== 'undefined') {
localStorage.setItem(STORAGE_KEY, '1')
}
onClose()
}
return (
<div style={{
position: 'fixed',
inset: 0,
backgroundColor: 'rgba(0,0,0,0.82)',
zIndex: 10000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '16px',
backdropFilter: 'blur(4px)',
}}>
<div style={{
backgroundColor: '#0a1628',
border: '2px solid #7C3AED',
borderRadius: '16px',
width: '100%',
maxWidth: '520px',
maxHeight: '90vh',
display: 'flex',
flexDirection: 'column',
boxShadow: '0 0 60px rgba(124,58,237,0.5), 0 24px 64px rgba(0,0,0,0.8)',
overflow: 'hidden',
}}>
{/* 顶部:进度条 */}
<div style={{
height: '3px',
backgroundColor: '#1e3a5f',
flexShrink: 0,
}}>
<div style={{
height: '100%',
width: `${((step + 1) / STEPS.length) * 100}%`,
background: 'linear-gradient(90deg, #7C3AED, #EC4899)',
transition: 'width 0.3s ease',
borderRadius: '2px',
}} />
</div>
{/* 步骤指示点 */}
<div style={{
display: 'flex',
justifyContent: 'center',
gap: '6px',
padding: '12px 24px 0',
flexShrink: 0,
}}>
{STEPS.map((s, i) => (
<button
key={s.id}
onClick={() => setStep(i)}
style={{
width: i === step ? '20px' : '8px',
height: '8px',
borderRadius: '4px',
border: 'none',
backgroundColor: i === step ? '#7C3AED' : i < step ? '#4C1D95' : '#1e3a5f',
cursor: 'pointer',
transition: 'all 0.2s ease',
padding: 0,
}}
/>
))}
</div>
{/* 内容区 */}
<div style={{
flex: 1,
overflowY: 'auto',
padding: '20px 28px',
}}>
{/* 图标 + 标题 */}
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
<div style={{ fontSize: '40px', marginBottom: '8px', lineHeight: 1 }}>
{current.icon}
</div>
<div style={{
fontFamily: "'Press Start 2P', monospace",
fontSize: '11px',
color: '#A78BFA',
marginBottom: '6px',
letterSpacing: '0.5px',
}}>
{current.title}
</div>
<div style={{
fontFamily: 'VT323, monospace',
fontSize: '16px',
color: '#64748B',
}}>
{current.subtitle}
</div>
</div>
{/* 分割线 */}
<div style={{
height: '1px',
background: 'linear-gradient(90deg, transparent, #1e3a5f, transparent)',
marginBottom: '16px',
}} />
{/* 正文 */}
<div style={{
fontFamily: 'VT323, monospace',
fontSize: '15px',
color: '#CBD5E1',
}}>
{current.content}
</div>
</div>
{/* 底部导航 */}
<div style={{
borderTop: '1px solid #1e3a5f',
padding: '14px 24px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexShrink: 0,
backgroundColor: 'rgba(10,18,40,0.5)',
}}>
{/* 步骤文字 */}
<span style={{
fontFamily: 'VT323, monospace',
fontSize: '13px',
color: '#334155',
}}>
{step + 1} / {STEPS.length}
</span>
<div style={{ display: 'flex', gap: '8px' }}>
{/* 上一步 */}
{!isFirst && (
<button
onClick={() => setStep(s => s - 1)}
style={{
padding: '8px 16px',
backgroundColor: '#0F1B2D',
border: '1px solid #1e3a5f',
borderRadius: '8px',
color: '#94A3B8',
fontFamily: 'VT323, monospace',
fontSize: '16px',
cursor: 'pointer',
}}
>
</button>
)}
{/* 跳过(仅前几步) */}
{!isLast && (
<button
onClick={handleClose}
style={{
padding: '8px 16px',
backgroundColor: 'transparent',
border: '1px solid #1e293b',
borderRadius: '8px',
color: '#475569',
fontFamily: 'VT323, monospace',
fontSize: '16px',
cursor: 'pointer',
}}
>
</button>
)}
{/* 下一步 / 开始战斗 */}
<button
onClick={isLast ? handleClose : () => setStep(s => s + 1)}
style={{
padding: '8px 20px',
backgroundColor: isLast ? '#7C3AED' : '#1e3a5f',
border: `2px solid ${isLast ? '#A78BFA' : '#7C3AED'}`,
borderRadius: '8px',
color: isLast ? '#E2E8F0' : '#C4B5FD',
fontFamily: isLast ? "'Press Start 2P', monospace" : 'VT323, monospace',
fontSize: isLast ? '9px' : '16px',
cursor: 'pointer',
boxShadow: isLast ? '0 0 16px rgba(124,58,237,0.5)' : 'none',
letterSpacing: isLast ? '0.5px' : 'normal',
transition: 'all 0.15s',
}}
>
{isLast ? '开始战斗 ▶' : '下一步 →'}
</button>
</div>
</div>
</div>
</div>
)
}
// ── Hook判断是否需要显示教程 ────────────────────────────────────────────────
export function useTutorial() {
const [show, setShow] = useState(false)
useEffect(() => {
// 避免 SSR 问题,在客户端检查
if (typeof window === 'undefined') return
const seen = localStorage.getItem(STORAGE_KEY)
if (!seen) setShow(true)
}, [])
const dismiss = () => {
localStorage.setItem(STORAGE_KEY, '1')
setShow(false)
}
return { show, dismiss }
}