分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>时空二维网格调度模型 - IFR最终理想解</title>
    <style>
        :root {
            /* Core Theme Colors */
            --bg-color: #06090f;
            --panel-bg: #0d121e;
            --grid-line: #1e293b;
            --text-main: #e2e8f0;
            --text-muted: #64748b;
            --accent-cyan: #06b6d4;
            --accent-emerald: #10b981;
            --accent-amber: #f59e0b;
            --alert-rose: #f43f5e;
            --ui-border: rgba(30, 41, 59, 0.8);
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: 'SF Pro Display', 'Inter', -apple-system, 'Microsoft YaHei', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            overflow: hidden;
            background-image: radial-gradient(circle at 50% 50%, #111827 0%, #06090f 100%);
        }

        .dashboard-container {
            position: relative;
            width: 100%;
            max-width: 1800px;
            height: 100%;
            display: flex;
            flex-direction: column;
            padding: 24px;
        }

        /* Header UI */
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 24px;
            padding: 0 24px;
            z-index: 10;
        }

        .title-group h1 {
            font-size: 24px;
            font-weight: 600;
            letter-spacing: 2px;
            color: #fff;
            text-shadow: 0 0 20px rgba(6, 182, 212, 0.4);
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .title-group h1::before {
            content: '';
            display: inline-block;
            width: 12px;
            height: 12px;
            background-color: var(--accent-cyan);
            border-radius: 2px;
            box-shadow: 0 0 10px var(--accent-cyan);
        }

        .subtitle {
            font-size: 14px;
            color: var(--text-muted);
            margin-top: 6px;
            font-family: monospace;
        }

        .clock-display {
            display: flex;
            align-items: center;
            background: rgba(13, 18, 30, 0.8);
            border: 1px solid var(--accent-emerald);
            padding: 12px 24px;
            border-radius: 8px;
            box-shadow: 0 0 30px rgba(16, 185, 129, 0.1);
            backdrop-filter: blur(10px);
        }

        .clock-time {
            font-size: 32px;
            font-weight: 700;
            font-family: 'Courier New', Courier, monospace;
            color: var(--accent-emerald);
            margin-right: 16px;
            text-shadow: 0 0 15px rgba(16, 185, 129, 0.5);
        }

        .system-status {
            font-size: 14px;
            color: var(--text-main);
            display: flex;
            flex-direction: column;
        }
        
        .status-badge {
            font-size: 12px;
            background: rgba(16, 185, 129, 0.2);
            color: var(--accent-emerald);
            padding: 2px 8px;
            border-radius: 4px;
            margin-bottom: 4px;
            width: fit-content;
        }

        /* SVG Container */
        .svg-container {
            flex-grow: 1;
            position: relative;
            background: var(--panel-bg);
            border: 1px solid var(--ui-border);
            border-radius: 12px;
            overflow: hidden;
            box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
        }

        svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        /* Interactive Controls */
        .controls {
            position: absolute;
            bottom: 32px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 16px;
            z-index: 20;
            background: rgba(13, 18, 30, 0.9);
            padding: 12px 24px;
            border-radius: 30px;
            border: 1px solid var(--ui-border);
            backdrop-filter: blur(10px);
        }

        button {
            background: transparent;
            border: 1px solid var(--text-muted);
            color: var(--text-main);
            padding: 8px 16px;
            border-radius: 20px;
            cursor: pointer;
            font-size: 14px;
            transition: all 0.3s ease;
        }

        button:hover {
            background: rgba(255, 255, 255, 0.05);
            border-color: var(--text-main);
        }

        button.active {
            background: rgba(6, 182, 212, 0.1);
            border-color: var(--accent-cyan);
            color: var(--accent-cyan);
        }

        /* SVG Animations & Styles */
        .anim-obj {
            transition: transform 1.2s cubic-bezier(0.34, 1.56, 0.64, 1);
        }
        
        .anim-slow {
            transition: transform 1.8s cubic-bezier(0.65, 0, 0.35, 1);
        }

        .glow {
            filter: drop-shadow(0 0 8px currentColor);
        }
        
        .matrix-cell {
            transition: fill 0.5s ease;
        }

        .radar-sweep {
            animation: sweep 4s linear infinite;
            transform-origin: center;
        }

        @keyframes sweep {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        @keyframes pulse {
            0%, 100% { opacity: 0.3; }
            50% { opacity: 0.8; }
        }

        .pulse {
            animation: pulse 2s infinite;
        }

        .crane-arm {
            transition: transform 1s ease-in-out;
            transform-origin: 30px 30px;
        }
        
        .crane-working {
            animation: craneWork 2s infinite alternate ease-in-out;
        }

        @keyframes craneWork {
            0% { transform: rotate(-20deg); }
            100% { transform: rotate(20deg); }
        }
        
        @keyframes dashMove {
            to { stroke-dashoffset: -20; }
        }
        
        .path-line {
            stroke-dasharray: 6;
            animation: dashMove 1s linear infinite;
            opacity: 0;
            transition: opacity 0.5s ease;
        }
        
        .path-line.active {
            opacity: 0.6;
        }

        /* Triz Callout */
        .triz-panel {
            position: absolute;
            right: 32px;
            bottom: 32px;
            width: 320px;
            background: rgba(13, 18, 30, 0.85);
            border-left: 4px solid var(--accent-emerald);
            padding: 16px;
            border-radius: 4px;
            backdrop-filter: blur(10px);
            z-index: 10;
        }

        .triz-panel h3 {
            color: var(--accent-emerald);
            font-size: 16px;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .triz-panel p {
            font-size: 13px;
            color: #cbd5e1;
            line-height: 1.6;
            margin-bottom: 8px;
        }
    </style>
</head>
<body>

    <div class="dashboard-container">
        <!-- Header -->
        <div class="header">
            <div class="title-group">
                <h1>基于“时间窗×空间格”的搬迁调度最终理想解</h1>
                <div class="subtitle">TRIZ IFR | 无等待、无拥堵、资源完美利用系统</div>
            </div>
            <div class="clock-display">
                <div class="clock-time" id="clock-display">08:00</div>
                <div class="system-status">
                    <span class="status-badge" id="status-badge">系统就绪</span>
                    <span id="status-text">预排计划生成中...</span>
                </div>
            </div>
        </div>

        <!-- Main Visualization -->
        <div class="svg-container">
            <svg viewBox="0 0 1600 800" preserveAspectRatio="xMidYMid meet">
                <defs>
                    <!-- Grid Pattern -->
                    <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                        <path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(30, 41, 59, 0.4)" stroke-width="1" />
                    </pattern>
                    <!-- Glowing Filters -->
                    <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                        <feGaussianBlur stdDeviation="5" result="blur" />
                        <feComposite in="SourceGraphic" in2="blur" operator="over" />
                    </filter>
                    <filter id="glow-amber" x="-20%" y="-20%" width="140%" height="140%">
                        <feGaussianBlur stdDeviation="5" result="blur" />
                        <feComposite in="SourceGraphic" in2="blur" operator="over" />
                    </filter>
                    <!-- Truck Symbol -->
                    <g id="truck-icon">
                        <rect x="-15" y="-25" width="30" height="50" rx="4" fill="currentColor" opacity="0.9" />
                        <rect x="-11" y="-15" width="22" height="15" rx="2" fill="#000" opacity="0.4" />
                        <circle cx="-17" cy="-15" r="3" fill="#333" />
                        <circle cx="17" cy="-15" r="3" fill="#333" />
                        <circle cx="-17" cy="15" r="3" fill="#333" />
                        <circle cx="17" cy="15" r="3" fill="#333" />
                    </g>
                    <!-- Matrix Token Symbol -->
                    <g id="matrix-token">
                        <rect x="-35" y="-15" width="70" height="30" rx="15" fill="currentColor" fill-opacity="0.2" stroke="currentColor" stroke-width="2" />
                        <circle cx="-20" cy="0" r="4" fill="currentColor" class="glow" />
                        <text x="-5" y="4" fill="#fff" font-size="12" font-weight="bold" font-family="monospace">T-X</text>
                    </g>
                </defs>

                <!-- Background -->
                <rect width="1600" height="800" fill="url(#grid)" />

                <!-- ==================== LEFT PANEL: MATRIX ENGINE ==================== -->
                <g transform="translate(50, 100)">
                    <!-- Panel BG -->
                    <rect width="650" height="600" fill="rgba(15, 23, 42, 0.6)" rx="12" stroke="rgba(30, 41, 59, 1)" />
                    
                    <!-- Title -->
                    <text x="30" y="40" fill="#94a3b8" font-size="16" font-weight="bold" letter-spacing="1">时空二维网格排程引擎 (逻辑运算层)</text>
                    <line x1="30" y1="55" x2="620" y2="55" stroke="rgba(30, 41, 59, 1)" stroke-width="2" />

                    <!-- Y-Axis (Space Grid) -->
                    <g fill="#cbd5e1" font-size="14" font-weight="500">
                        <text x="30" y="160">上车道 (入场)</text>
                        <text x="30" y="280">缓冲等待区 (容量: 3)</text>
                        <text x="30" y="400">吊装站位 A (容量: 1)</text>
                        <text x="30" y="520">吊装站位 B (容量: 1)</text>
                    </g>

                    <!-- X-Axis (Time Windows) -->
                    <g fill="#cbd5e1" font-size="14" font-family="monospace" text-anchor="middle">
                        <text x="200" y="90">08:00</text>
                        <text x="300" y="90">08:30</text>
                        <text x="400" y="90">09:00</text>
                        <text x="500" y="90">09:30</text>
                        <text x="600" y="90">10:00</text>
                    </g>

                    <!-- Grid Cells -->
                    <g id="grid-cells" stroke="rgba(30, 41, 59, 0.5)" stroke-width="1" fill="none">
                        <!-- Generates via JS for concise code, but placing a few for structural integrity if JS fails -->
                        <path d="M 150 110 L 150 560 M 250 110 L 250 560 M 350 110 L 350 560 M 450 110 L 450 560 M 550 110 L 550 560" />
                        <path d="M 150 200 L 630 200 M 150 320 L 630 320 M 150 440 L 630 440" />
                    </g>

                    <!-- Active Time Scanner -->
                    <rect id="time-scanner" x="150" y="100" width="100" height="470" fill="url(#time-scan-grad)" opacity="0.3" class="anim-obj" />
                    <defs>
                        <linearGradient id="time-scan-grad" x1="0" y1="0" x2="0" y2="1">
                            <stop offset="0%" stop-color="#06b6d4" stop-opacity="0" />
                            <stop offset="50%" stop-color="#06b6d4" stop-opacity="0.2" />
                            <stop offset="100%" stop-color="#06b6d4" stop-opacity="0" />
                        </linearGradient>
                    </defs>

                    <!-- Data Tokens (Injected via JS but defined here) -->
                    <g id="tokens-layer"></g>
                </g>

                <!-- ==================== RIGHT PANEL: PHYSICAL MAP ==================== -->
                <g transform="translate(750, 100)">
                    <!-- Panel BG -->
                    <rect width="800" height="600" fill="rgba(15, 23, 42, 0.4)" rx="12" stroke="rgba(30, 41, 59, 1)" />
                    
                    <text x="30" y="40" fill="#94a3b8" font-size="16" font-weight="bold" letter-spacing="1">井场物理空间映射 (物理执行层)</text>
                    <line x1="30" y1="55" x2="770" y2="55" stroke="rgba(30, 41, 59, 1)" stroke-width="2" />

                    <!-- Topography / Zones -->
                    <!-- Road -->
                    <path d="M 50 600 L 50 500 C 50 480 70 460 90 460 L 750 460" fill="none" stroke="rgba(255, 255, 255, 0.1)" stroke-width="60" stroke-linecap="round"/>
                    <text x="700" y="465" fill="#64748b" font-size="12" text-anchor="end">上车道 / 入场</text>

                    <!-- Buffer Zone -->
                    <rect x="350" y="320" width="300" height="120" rx="8" fill="none" stroke="var(--accent-amber)" stroke-width="2" stroke-dasharray="8 4" opacity="0.6" />
                    <text x="360" y="340" fill="var(--accent-amber)" font-size="12" opacity="0.8">缓冲等待区 (超时阈值: 15min)</text>
                    <rect x="380" y="360" width="50" height="60" fill="rgba(245, 158, 11, 0.1)" rx="4"/>
                    <rect x="470" y="360" width="50" height="60" fill="rgba(245, 158, 11, 0.1)" rx="4"/>
                    <rect x="560" y="360" width="50" height="60" fill="rgba(245, 158, 11, 0.1)" rx="4"/>

                    <!-- Crane Zone A -->
                    <rect x="150" y="100" width="220" height="180" rx="8" fill="rgba(16, 185, 129, 0.05)" stroke="var(--accent-emerald)" stroke-width="2" />
                    <text x="160" y="125" fill="var(--accent-emerald)" font-size="14" font-weight="bold">吊装站位区 A</text>
                    <!-- Crane A Graphic -->
                    <g transform="translate(260, 200)" id="crane-A">
                        <rect x="-40" y="-40" width="80" height="80" fill="none" stroke="#334155" stroke-dasharray="4"/>
                        <circle cx="0" cy="0" r="15" fill="#f59e0b" />
                        <rect x="-8" y="-10" width="16" height="20" fill="#333" rx="2" />
                        <g class="crane-arm" id="arm-A">
                            <rect x="-4" y="-80" width="8" height="80" fill="#f59e0b" />
                            <polygon points="-8,-80 8,-80 0,-90" fill="#f59e0b" />
                        </g>
                    </g>

                    <!-- Crane Zone B -->
                    <rect x="500" y="100" width="220" height="180" rx="8" fill="rgba(6, 182, 212, 0.05)" stroke="var(--accent-cyan)" stroke-width="2" />
                    <text x="510" y="125" fill="var(--accent-cyan)" font-size="14" font-weight="bold">吊装站位区 B</text>
                    <!-- Crane B Graphic -->
                    <g transform="translate(610, 200)" id="crane-B">
                        <rect x="-40" y="-40" width="80" height="80" fill="none" stroke="#334155" stroke-dasharray="4"/>
                        <circle cx="0" cy="0" r="15" fill="#06b6d4" />
                        <rect x="-8" y="-10" width="16" height="20" fill="#333" rx="2" />
                        <g class="crane-arm" id="arm-B">
                            <rect x="-4" y="-80" width="8" height="80" fill="#06b6d4" />
                            <polygon points="-8,-80 8,-80 0,-90" fill="#06b6d4" />
                        </g>
                    </g>

                    <!-- Active Paths (Glowing Lines showing intent) -->
                    <path id="path-road-to-buf" d="M 500 460 L 500 420" stroke="var(--accent-amber)" stroke-width="3" fill="none" class="path-line" />
                    <path id="path-buf-to-cA" d="M 380 320 L 380 200 L 320 200" stroke="var(--accent-emerald)" stroke-width="3" fill="none" class="path-line" />
                    <path id="path-buf-to-cB" d="M 560 320 L 560 200 L 550 200" stroke="var(--accent-cyan)" stroke-width="3" fill="none" class="path-line" />
                    
                    <!-- Trucks Layer -->
                    <g id="trucks-layer"></g>
                </g>
            </svg>
        </div>

        <!-- TRIZ Info Panel -->
        <div class="triz-panel">
            <h3>
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="12" cy="12" r="10"></circle>
                    <path d="M12 16v-4"></path>
                    <path d="M12 8h.01"></path>
                </svg>
                最终理想解 (IFR) 原理
            </h3>
            <p><strong>矛盾破除:</strong>将无序的“等待”转化为精确的“时空网格匹配”。</p>
            <p><strong>资源利用:</strong>极小增加复杂度,仅利用手机端回传状态与预设规则引擎。</p>
            <p><strong>运作机制:</strong>系统自动进行时空冲突检测,动态重排(如 T2 避开满载的 A 区,自动导向 B 区),实现0拥堵进场。</p>
        </div>

        <!-- Controls -->
        <div class="controls">
            <button id="btn-pause" onclick="togglePlay()">暂停 / 播放</button>
            <button id="btn-speed" onclick="toggleSpeed()">切换速度 (1x)</button>
        </div>
    </div>

    <script>
        // Data Configuration
        const config = {
            colors: {
                T1: '#10b981', // Emerald
                T2: '#f59e0b', // Amber
                T3: '#06b6d4', // Cyan
                T4: '#f43f5e'  // Rose
            },
            matrixNodes: {
                road: 155, buffer: 275, craneA: 395, craneB: 515
            },
            timeNodes: {
                0: 200, 1: 300, 2: 400, 3: 500, 4: 600, 5: 700
            },
            mapNodes: {
                start: {x: -50, y: 460},
                roadWait: {x: 400, y: 460},
                buf1: {x: 405, y: 390},
                buf2: {x: 495, y: 390},
                buf3: {x: 585, y: 390},
                craneA: {x: 180, y: 200, rot: -90},
                craneB: {x: 700, y: 200, rot: 90},
                exitA: {x: 180, y: -50},
                exitB: {x: 700, y: -50}
            }
        };

        // Animation State Machine
        let isPlaying = true;
        let speedMultiplier = 1;
        let currentStep = 0;
        let stateMachineTimeout;

        // Create Elements
        function createElements() {
            const tokenLayer = document.getElementById('tokens-layer');
            const truckLayer = document.getElementById('trucks-layer');
            
            // Create 3 trucks
            [1, 2, 3].forEach(id => {
                const color = config.colors[`T${id}`];
                
                // Matrix Token
                const token = document.createElementNS("http://www.w3.org/2000/svg", "g");
                token.setAttribute("id", `tok-T${id}`);
                token.setAttribute("color", color);
                token.setAttribute("class", "anim-obj");
                token.setAttribute("transform", `translate(-100, -100)`);
                token.innerHTML = document.getElementById('matrix-token').innerHTML.replace('T-X', `车-${id}`);
                tokenLayer.appendChild(token);

                // Map Truck
                const truck = document.createElementNS("http://www.w3.org/2000/svg", "g");
                truck.setAttribute("id", `map-T${id}`);
                truck.setAttribute("color", color);
                truck.setAttribute("class", "anim-slow glow");
                truck.setAttribute("transform", `translate(-100, -100)`);
                truck.innerHTML = `<use href="#truck-icon" />`;
                truckLayer.appendChild(truck);
            });
        }

        // DOM Manipulators
        function moveToken(id, timeIdx, zone) {
            const el = document.getElementById(`tok-T${id}`);
            if(el && config.timeNodes[timeIdx] !== undefined) {
                const x = config.timeNodes[timeIdx];
                const y = config.matrixNodes[zone];
                el.setAttribute("transform", `translate(${x}, ${y})`);
                el.style.opacity = 1;
            } else if (el) {
                el.style.opacity = 0; // hide if out of bounds
            }
        }

        function moveTruck(id, nodeKey) {
            const el = document.getElementById(`map-T${id}`);
            const node = config.mapNodes[nodeKey];
            if(el && node) {
                const rot = node.rot ? `rotate(${node.rot})` : 'rotate(90)'; // Default face right
                el.setAttribute("transform", `translate(${node.x}, ${node.y}) ${rot}`);
            }
        }

        function setCrane(zone, active) {
            const el = document.getElementById(`arm-${zone}`);
            if(active) el.classList.add('crane-working');
            else el.classList.remove('crane-working');
        }

        function setPath(id, active) {
            const el = document.getElementById(id);
            if (el) {
                if (active) el.classList.add('active');
                else el.classList.remove('active');
            }
        }

        function updateUI(timeStr, statusStr, badgeStr) {
            document.getElementById('clock-display').innerText = timeStr;
            document.getElementById('status-text').innerText = statusStr;
            document.getElementById('status-badge').innerText = badgeStr;
            
            // Move time scanner
            const timeIdx = {"08:00":0, "08:30":1, "09:00":2, "09:30":3, "10:00":4, "10:30":5}[timeStr];
            if(timeIdx !== undefined) {
                const scanner = document.getElementById('time-scanner');
                scanner.setAttribute("transform", `translate(${timeIdx * 100}, 0)`);
            }
        }

        // Sleep function adhering to speed
        function sleep(ms) {
            return new Promise(resolve => {
                stateMachineTimeout = setTimeout(resolve, ms / speedMultiplier);
            });
        }

        // Await Play State
        async function waitIfPaused() {
            while(!isPlaying) {
                await new Promise(r => setTimeout(r, 100));
            }
        }

        // Simulation Timeline
        async function runSimulation() {
            while(true) {
                // Initialize
                moveToken(1, -1, 'road'); moveTruck(1, 'start');
                moveToken(2, -1, 'road'); moveTruck(2, 'start');
                moveToken(3, -1, 'road'); moveTruck(3, 'start');
                setCrane('A', false); setCrane('B', false);
                setPath('path-road-to-buf', false); setPath('path-buf-to-cA', false); setPath('path-buf-to-cB', false);
                
                await waitIfPaused();
                await sleep(1000);

                // --- T=0: 08:00 ---
                updateUI('08:00', '预排计划下发: 车辆按时间窗进场', '规则引擎运行');
                setPath('path-road-to-buf', true);
                
                // T1 to Buffer
                moveToken(1, 0, 'buffer'); 
                moveTruck(1, 'buf1');
                
                await waitIfPaused();
                await sleep(3000);

                // --- T=1: 08:30 ---
                updateUI('08:30', '状态回传: A区无冲突,分配吊装站位', '时空检测 OK');
                setPath('path-road-to-buf', false);
                setPath('path-buf-to-cA', true);

                // T1 to Crane A
                moveToken(1, 1, 'craneA');
                moveTruck(1, 'craneA');
                
                // T2 arrives Buffer
                moveToken(2, 1, 'buffer');
                moveTruck(2, 'buf2');
                
                await sleep(1000);
                setCrane('A', true);

                await waitIfPaused();
                await sleep(2500);

                // --- T=2: 09:00 ---
                updateUI('09:00', '系统滚动刷新: A区满载,动态避让至B区', '触发避让逻辑');
                setPath('path-buf-to-cA', false);
                setPath('path-buf-to-cB', true);
                setCrane('A', false);

                // T1 leaves
                moveToken(1, 2, 'road'); moveTruck(1, 'exitA');
                
                // T2 to Crane B (Avoids waiting for A)
                moveToken(2, 2, 'craneB');
                moveTruck(2, 'craneB');

                // T3 arrives Buffer
                moveToken(3, 2, 'buffer');
                moveTruck(3, 'buf1');

                await sleep(1000);
                setCrane('B', true);

                await waitIfPaused();
                await sleep(2500);

                // --- T=3: 09:30 ---
                updateUI('09:30', '并发执行: T3分配至释放的A区', '资源完美利用');
                setPath('path-buf-to-cB', false);
                setPath('path-buf-to-cA', true);
                setCrane('B', false);

                // T2 leaves
                moveToken(2, 3, 'road'); moveTruck(2, 'exitB');

                // T3 to Crane A
                moveToken(3, 3, 'craneA');
                moveTruck(3, 'craneA');

                await sleep(1000);
                setCrane('A', true);

                await waitIfPaused();
                await sleep(2500);

                // --- T=4: 10:00 ---
                updateUI('10:00', '所有任务在无等待状态下完成', 'IFR达成');
                setPath('path-buf-to-cA', false);
                setCrane('A', false);

                // T3 leaves
                moveToken(3, 4, 'road'); moveTruck(3, 'exitA');

                await waitIfPaused();
                await sleep(3000);

                // Reset for loop
                document.getElementById('tok-T1').style.opacity = 0;
                document.getElementById('tok-T2').style.opacity = 0;
                document.getElementById('tok-T3').style.opacity = 0;
                await sleep(500);
            }
        }

        // Controls Logic
        function togglePlay() {
            isPlaying = !isPlaying;
            const btn = document.getElementById('btn-pause');
            btn.innerText = isPlaying ? "暂停 / 播放" : "已暂停 (点击继续)";
            btn.classList.toggle('active');
        }

        function toggleSpeed() {
            speedMultiplier = speedMultiplier === 1 ? 2 : 1;
            const btn = document.getElementById('btn-speed');
            btn.innerText = `切换速度 (${speedMultiplier}x)`;
            btn.classList.toggle('active');
            
            // Update CSS transition speeds dynamically
            const animTime = 1.2 / speedMultiplier;
            const slowTime = 1.8 / speedMultiplier;
            document.querySelectorAll('.anim-obj').forEach(el => el.style.transitionDuration = `${animTime}s`);
            document.querySelectorAll('.anim-slow').forEach(el => el.style.transitionDuration = `${slowTime}s`);
        }

        // Initialize on Load (Must autoplay as per requirements)
        window.addEventListener('DOMContentLoaded', () => {
            createElements();
            // Start simulation loop
            runSimulation();
        });
        
    </script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分