import React, { useEffect, useState, useMemo, useRef } from 'react'; import { X, Pause, Play, AlertTriangle } from 'lucide-react'; import { AreaChart, Area, ResponsiveContainer, YAxis, XAxis, ReferenceDot } from 'recharts'; import { Scenario } from '../types'; interface PlayerProps { scenario: Scenario | null; onClose: () => void; } // Mock script synced with progress (0-100) const DIALOGUE_SCRIPT = [ { time: 0, text: "正在建立神经连接..." }, { time: 5, text: "(检测到心率轻微上升)" }, { time: 12, text: "“放松,把控制权交给我。”" }, { time: 20, text: "“很好,保持呼吸频率...”" }, { time: 28, text: "正在启动触觉反馈模块" }, { time: 35, text: "“感觉到那个节奏了吗?”" }, { time: 45, text: "强度正在逐渐增加..." }, { time: 55, text: "“不要抵抗,顺从它。”" }, { time: 65, text: "“我会稍微加快一点速度。”" }, { time: 75, text: "(设备输出功率提升至 80%)" }, { time: 85, text: "“就是现在...”" }, { time: 92, text: "“做得很好,指挥官。”" }, { time: 100, text: "连接结束。" }, ]; const Player: React.FC = ({ scenario, onClose }) => { const [isPlaying, setIsPlaying] = useState(true); const [progress, setProgress] = useState(0); const [isDragging, setIsDragging] = useState(false); // Track dragging state const [showAlert, setShowAlert] = useState(false); // Generate 101 data points (0 to 100) to map perfectly to progress const data = useMemo(() => { return Array.from({ length: 101 }, (_, i) => { // Create a realistic wave: Warmup -> Build up -> Climax -> Cooldown let hz = 10; if (i < 20) hz = 10 + (i * 1.5) + (Math.random() * 5); // Warmup else if (i < 50) hz = 40 + Math.sin(i * 0.5) * 10 + (Math.random() * 10); // Plateau/Tease else if (i < 85) hz = 70 + (i - 50) * 0.8 + (Math.random() * 15); // Build up to climax else hz = 90 - ((i - 85) * 5) + (Math.random() * 5); // Cooldown return { time: i, hz: Math.max(0, Math.min(100, hz)) }; }); }, []); useEffect(() => { let interval: any; if (isPlaying && !isDragging) { interval = setInterval(() => { setProgress(p => { if (p >= 100) { setIsPlaying(false); return 100; } return p + 0.1; }); }, 50); } return () => { if (interval) clearInterval(interval); }; }, [isPlaying, isDragging]); const handleEmergencyStop = () => { setIsPlaying(false); setShowAlert(true); if (window.navigator && window.navigator.vibrate) { window.navigator.vibrate([100, 50, 100]); } setTimeout(() => { onClose(); }, 1500); }; // Handle Seek / Drag const handleSeek = (e: React.ChangeEvent) => { const newVal = parseFloat(e.target.value); setProgress(newVal); }; const handleDragStart = () => { setIsDragging(true); }; const handleDragEnd = () => { setIsDragging(false); }; // Calculate active line based on progress const activeLineIndex = DIALOGUE_SCRIPT.findIndex((line, index) => { const nextLine = DIALOGUE_SCRIPT[index + 1]; return progress >= line.time && (!nextLine || progress < nextLine.time); }); // Get current HZ for the dot const currentHz = data[Math.min(100, Math.floor(progress))]?.hz || 0; if (!scenario) return null; return (
{/* Background Visual */}
bg
{/* Emergency Overlay */} {showAlert && (

EMERGENCY STOP

系统已强制中断

)} {/* Header - Seamless Gradient */}

{scenario.title}

{scenario.category} SCENARIO
{/* Central Content (Rolling Lyrics) */}
{/* Top Gradient Mask */}
{DIALOGUE_SCRIPT.map((line, i) => { const isActive = i === activeLineIndex; const distance = Math.abs(activeLineIndex - i); const isFar = distance > 2; return (

{line.text}

); })}
{/* Bottom Controls & Viz - Unified Gradient */}
{/* Chart Visualizer */}
{/* Controls (Pointer events enabled) */}
Device Intensity 伸缩频率: {Math.max(1, Math.ceil(progress / 10))}档
{(progress * 1.5).toFixed(0)}s / {scenario.duration}
{/* Draggable Progress Bar Container */}
{/* 1. Visual Track (Background) */}
{/* Visual Progress Fill */}
{/* 2. Visual Thumb (Knob) - Moves with progress */}
{/* 3. Invisible Input Range (The actual interaction layer) */}

双击屏幕紧急停止

); }; export default Player;