分享图
动画工坊
引擎就绪
<!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 {
            --bg-color: #0b0f19;
            --panel-bg: #131a2a;
            --border-color: #1f2937;
            --text-main: #e2e8f0;
            --text-muted: #94a3b8;
            --accent-blue: #3b82f6;
            --accent-cyan: #06b6d4;
            --accent-orange: #f97316;
            --accent-red: #ef4444;
            --accent-green: #10b981;
            
            --font-main: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
        }

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

        body {
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: var(--font-main);
            height: 100vh;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            background-image: 
                linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
                linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
            background-size: 30px 30px;
            background-position: center center;
        }

        header {
            padding: 16px 24px;
            background: rgba(11, 15, 25, 0.8);
            border-bottom: 1px solid var(--border-color);
            backdrop-filter: blur(10px);
            display: flex;
            justify-content: space-between;
            align-items: center;
            z-index: 10;
        }

        .title-block h1 {
            font-size: 16px;
            font-weight: 600;
            letter-spacing: 0.5px;
            color: var(--accent-cyan);
        }

        .title-block p {
            font-size: 12px;
            color: var(--text-muted);
            margin-top: 4px;
        }

        .ifr-badge {
            background: rgba(6, 182, 212, 0.1);
            border: 1px solid rgba(6, 182, 212, 0.3);
            color: var(--accent-cyan);
            padding: 4px 12px;
            border-radius: 4px;
            font-size: 12px;
            font-weight: bold;
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .ifr-badge::before {
            content: '';
            display: block;
            width: 8px;
            height: 8px;
            background: var(--accent-cyan);
            border-radius: 50%;
            box-shadow: 0 0 8px var(--accent-cyan);
            animation: pulse-dot 2s infinite;
        }

        main {
            flex: 1;
            display: flex;
            padding: 24px;
            gap: 24px;
            position: relative;
        }

        .panel {
            background: var(--panel-bg);
            border: 1px solid var(--border-color);
            border-radius: 12px;
            padding: 20px;
            display: flex;
            flex-direction: column;
            box-shadow: 0 8px 32px rgba(0,0,0,0.4);
            position: relative;
            overflow: hidden;
        }

        /* 物理结构视图 */
        .physical-view {
            flex: 1;
            min-width: 400px;
        }

        /* 数据图表视图 */
        .digital-view {
            flex: 1;
            min-width: 400px;
        }

        .panel-title {
            font-size: 14px;
            font-weight: 600;
            color: var(--text-muted);
            margin-bottom: 16px;
            display: flex;
            justify-content: space-between;
        }

        .status-text {
            font-family: var(--font-mono);
            font-size: 12px;
            color: var(--accent-green);
        }

        .svg-container {
            flex: 1;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        svg {
            width: 100%;
            height: 100%;
            max-height: 100%;
        }

        /* 数据面板覆盖物 */
        .data-overlay {
            position: absolute;
            bottom: 20px;
            left: 20px;
            right: 20px;
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 12px;
            pointer-events: none;
        }

        .data-box {
            background: rgba(0, 0, 0, 0.4);
            border: 1px solid rgba(255, 255, 255, 0.1);
            border-radius: 6px;
            padding: 8px 12px;
            backdrop-filter: blur(4px);
        }

        .data-label {
            font-size: 10px;
            color: var(--text-muted);
            text-transform: uppercase;
        }

        .data-value {
            font-family: var(--font-mono);
            font-size: 16px;
            font-weight: 600;
            color: var(--text-main);
            margin-top: 4px;
            display: flex;
            align-items: baseline;
            gap: 4px;
        }

        .data-unit {
            font-size: 10px;
            color: var(--text-muted);
        }

        /* 动画定义 */
        @keyframes pulse-dot {
            0% { transform: scale(0.95); opacity: 0.5; }
            50% { transform: scale(1.1); opacity: 1; }
            100% { transform: scale(0.95); opacity: 0.5; }
        }

        @keyframes dash {
            to { stroke-dashoffset: 0; }
        }

        @keyframes transmit {
            0% { opacity: 0; transform: translateX(-5px) scale(0.8); }
            50% { opacity: 1; transform: translateX(0) scale(1.1); }
            100% { opacity: 0; transform: translateX(5px) scale(0.8); }
        }

        /* 动态样式类 (由JS控制) */
        .glow-active {
            filter: drop-shadow(0 0 8px currentColor);
        }

        .state-idle .data-value { color: var(--accent-cyan); }
        .state-accel .data-value { color: var(--accent-orange); }
        .state-peak .data-value { color: var(--accent-red); }
        .state-decel .data-value { color: var(--accent-green); }

        /* 解释说明文字约束 */
        .legend-overlay {
            position: absolute;
            top: 20px;
            right: 20px;
            font-size: 12px;
            background: rgba(11, 15, 25, 0.85);
            border: 1px solid var(--border-color);
            padding: 10px;
            border-radius: 6px;
            pointer-events: none;
            max-width: 200px;
        }
        
        .legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 6px;
            color: var(--text-muted);
        }
        .legend-item:last-child { margin-bottom: 0; }
        .legend-color {
            width: 12px; height: 12px; border-radius: 3px;
        }

    </style>
</head>
<body>

    <header>
        <div class="title-block">
            <h1>基于 TRIZ IFR 的无气泵传感器检测方案</h1>
            <p>利用发动机本身作为压力源,消除内置气泵,实现轻量化与真实工况复现。</p>
        </div>
        <div class="ifr-badge">
            最终理想解 (IFR) 状态演示
        </div>
    </header>

    <main>
        <!-- 左侧:物理系统原理视图 -->
        <div class="panel physical-view">
            <div class="panel-title">
                <span>物理采集端 (现场机舱)</span>
                <span class="status-text" id="engine-status">IDLE STANDBY</span>
            </div>
            
            <div class="svg-container">
                <svg viewBox="0 0 500 500" preserveAspectRatio="xMidYMid meet" id="physical-svg">
                    <!-- 定义渐变和滤镜 -->
                    <defs>
                        <radialGradient id="engine-glow" cx="50%" cy="50%" r="50%">
                            <stop offset="0%" stop-color="#f97316" stop-opacity="0.3" id="glow-stop"/>
                            <stop offset="100%" stop-color="transparent" stop-opacity="0"/>
                        </radialGradient>
                        <linearGradient id="pipe-grad" x1="0%" y1="0%" x2="0%" y2="100%">
                            <stop offset="0%" stop-color="#1e293b"/>
                            <stop offset="100%" stop-color="#0f172a"/>
                        </linearGradient>
                        <filter id="neon-blue" x="-20%" y="-20%" width="140%" height="140%">
                            <feGaussianBlur stdDeviation="3" result="blur" />
                            <feMerge>
                                <feMergeNode in="blur" />
                                <feMergeNode in="SourceGraphic" />
                            </feMerge>
                        </filter>
                    </defs>

                    <!-- 背景引擎区域暗示 -->
                    <circle cx="150" cy="350" r="120" fill="url(#engine-glow)" id="engine-heat"/>
                    
                    <!-- 发动机进气歧管 (压力源) -->
                    <g transform="translate(50, 300)">
                        <path d="M 0,20 Q 50,-10 100,20 T 200,20" fill="none" stroke="#334155" stroke-width="40" stroke-linecap="round"/>
                        <path d="M 0,20 Q 50,-10 100,20 T 200,20" fill="none" stroke="url(#pipe-grad)" stroke-width="38" stroke-linecap="round"/>
                        <!-- 内部气流/压力指示 -->
                        <path d="M 10,20 Q 50,0 100,20 T 190,20" fill="none" stroke="#06b6d4" stroke-width="12" stroke-linecap="round" id="airflow-indicator" opacity="0.6"/>
                        <text x="100" y="60" fill="#64748b" font-size="12" font-family="monospace" text-anchor="middle">发动机进气歧管 (内源动力)</text>
                    </g>

                    <!-- 三通接头与气路 -->
                    <g transform="translate(150, 290)">
                        <!-- 主管道连接处 -->
                        <path d="M 0,0 L 0,-120" fill="none" stroke="#475569" stroke-width="16"/>
                        <path d="M 0,0 L 0,-120" fill="none" stroke="#1e293b" stroke-width="14"/>
                        <!-- 压力传递核心线 -->
                        <path d="M 0,-10 L 0,-110" fill="none" stroke="#06b6d4" stroke-width="6" id="pressure-line" class="glow-active"/>
                        
                        <!-- 三通分流 -->
                        <path d="M -60,-120 L 60,-120" fill="none" stroke="#475569" stroke-width="12" stroke-linecap="round"/>
                        <path d="M -58,-120 L 58,-120" fill="none" stroke="#1e293b" stroke-width="10" stroke-linecap="round"/>
                        <path d="M 0,-110 L 0,-120 M 0,-120 L -50,-120 M 0,-120 L 50,-120" fill="none" stroke="#06b6d4" stroke-width="4" id="pressure-split" class="glow-active"/>

                        <text x="20" y="-80" fill="#94a3b8" font-size="10" font-family="monospace">免拆三通</text>
                    </g>

                    <!-- 参考压力传感器 (左) -->
                    <g transform="translate(70, 150)">
                        <rect x="0" y="0" width="40" height="40" rx="4" fill="#1e293b" stroke="#06b6d4" stroke-width="2"/>
                        <circle cx="20" cy="20" r="10" fill="#0f172a" stroke="#06b6d4" stroke-width="1"/>
                        <text x="20" y="-10" fill="#06b6d4" font-size="11" text-anchor="middle" font-weight="bold">参考传感器</text>
                        <text x="20" y="55" fill="#64748b" font-size="9" text-anchor="middle">高精度</text>
                        <!-- 信号线 -->
                        <path d="M 20,0 L 20,-30 L 120,-30" fill="none" stroke="#06b6d4" stroke-width="2" stroke-dasharray="4 2" class="signal-line"/>
                    </g>

                    <!-- 被测传感器 (右) -->
                    <g transform="translate(190, 150)">
                        <rect x="0" y="0" width="40" height="40" rx="4" fill="#1e293b" stroke="#f97316" stroke-width="2"/>
                        <polygon points="20,10 30,25 10,25" fill="#0f172a" stroke="#f97316" stroke-width="1"/>
                        <text x="20" y="-10" fill="#f97316" font-size="11" text-anchor="middle" font-weight="bold">被测传感器</text>
                        <text x="20" y="55" fill="#64748b" font-size="9" text-anchor="middle">待标定</text>
                        <!-- 信号线 -->
                        <path d="M 20,0 L 20,-20 L -20,-20" fill="none" stroke="#f97316" stroke-width="2" stroke-dasharray="4 2" class="signal-line"/>
                    </g>

                    <!-- 采集主机 (烟盒大小) -->
                    <g transform="translate(140, 50)">
                        <rect x="0" y="0" width="100" height="70" rx="6" fill="#131a2a" stroke="#475569" stroke-width="2"/>
                        <rect x="10" y="10" width="80" height="20" rx="2" fill="#0f172a" stroke="#1e293b"/>
                        <text x="50" y="24" fill="#10b981" font-size="10" font-family="monospace" text-anchor="middle">DAQ SYNC</text>
                        <text x="50" y="50" fill="#e2e8f0" font-size="12" text-anchor="middle" font-weight="bold">无泵采集主机</text>
                        <circle cx="85" cy="50" r="3" fill="#10b981" id="power-led" class="glow-active"/>
                        
                        <!-- 蓝牙发射波 -->
                        <g transform="translate(110, 35)" stroke="#3b82f6" fill="none" stroke-width="2" stroke-linecap="round">
                            <path d="M 0,-10 Q 10,0 0,10" class="bt-wave" style="animation: transmit 1.5s infinite 0s;"/>
                            <path d="M 5,-20 Q 20,0 5,20" class="bt-wave" style="animation: transmit 1.5s infinite 0.3s;"/>
                            <path d="M 10,-30 Q 30,0 10,30" class="bt-wave" style="animation: transmit 1.5s infinite 0.6s;"/>
                        </g>
                    </g>
                    
                </svg>
            </div>

            <div class="data-overlay">
                <div class="data-box" id="box-rpm">
                    <div class="data-label">发动机转速 (模拟油门)</div>
                    <div class="data-value" id="val-rpm">800 <span class="data-unit">RPM</span></div>
                </div>
                <div class="data-box" id="box-kpa">
                    <div class="data-label">流形绝对压力</div>
                    <div class="data-value" id="val-kpa">101.3 <span class="data-unit">kPa</span></div>
                </div>
                <div class="data-box" id="box-vol">
                    <div class="data-label">被测传感电压</div>
                    <div class="data-value" id="val-vol">1.05 <span class="data-unit">V</span></div>
                </div>
            </div>
        </div>

        <!-- 右侧:数字化诊断视图 -->
        <div class="panel digital-view">
            <div class="panel-title">
                <span>实时特性曲线 (诊断端)</span>
                <span class="status-text" style="color: var(--accent-blue);">BT CONNECTED</span>
            </div>
            
            <div class="svg-container">
                <svg viewBox="0 0 500 500" preserveAspectRatio="xMidYMid meet">
                    <!-- 图表背景网格 -->
                    <g stroke="#1e293b" stroke-width="1">
                        <line x1="50" y1="50" x2="50" y2="450" />
                        <line x1="125" y1="50" x2="125" y2="450" stroke-dasharray="2 4"/>
                        <line x1="200" y1="50" x2="200" y2="450" stroke-dasharray="2 4"/>
                        <line x1="275" y1="50" x2="275" y2="450" stroke-dasharray="2 4"/>
                        <line x1="350" y1="50" x2="350" y2="450" stroke-dasharray="2 4"/>
                        <line x1="425" y1="50" x2="425" y2="450" stroke-dasharray="2 4"/>
                        
                        <line x1="50" y1="450" x2="450" y2="450" />
                        <line x1="50" y1="350" x2="450" y2="350" stroke-dasharray="2 4"/>
                        <line x1="50" y1="250" x2="450" y2="250" stroke-dasharray="2 4"/>
                        <line x1="50" y1="150" x2="450" y2="150" stroke-dasharray="2 4"/>
                        <line x1="50" y1="50" x2="450" y2="50" stroke-dasharray="2 4"/>
                    </g>

                    <!-- 坐标轴标签 -->
                    <g fill="#64748b" font-size="10" font-family="monospace">
                        <!-- X轴 (参考压力 kPa) -->
                        <text x="455" y="465" text-anchor="end">参考压力 (kPaA)</text>
                        <text x="50" y="465" text-anchor="middle">100</text>
                        <text x="200" y="465" text-anchor="middle">200</text>
                        <text x="350" y="465" text-anchor="middle">300</text>
                        
                        <!-- Y轴 (电压 V) -->
                        <text x="40" y="45" text-anchor="end">输出电压 (V)</text>
                        <text x="40" y="450" text-anchor="end">1.0</text>
                        <text x="40" y="350" text-anchor="end">2.0</text>
                        <text x="40" y="250" text-anchor="end">3.0</text>
                        <text x="40" y="150" text-anchor="end">4.0</text>
                        <text x="40" y="50" text-anchor="end">5.0</text>
                    </g>

                    <!-- 理想参考曲线 (虚线) -->
                    <path d="M 50,450 L 425,75" fill="none" stroke="#334155" stroke-width="2" stroke-dasharray="5 5" />
                    
                    <!-- 动态绘制的实际曲线 -->
                    <!-- 加速上升段 (红色) -->
                    <path id="graph-path-up" d="M 50,450" fill="none" stroke="#ef4444" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" filter="url(#neon-blue)"/>
                    <!-- 减速下降段 (绿色,展现迟滞现象) -->
                    <path id="graph-path-down" d="" fill="none" stroke="#10b981" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" filter="url(#neon-blue)"/>
                    
                    <!-- 当前采样点 -->
                    <circle id="graph-point" cx="50" cy="450" r="5" fill="#e2e8f0" stroke="#3b82f6" stroke-width="2" class="glow-active"/>
                    
                    <!-- 动态虚线定位器 -->
                    <g id="crosshair" stroke="#e2e8f0" stroke-width="1" stroke-dasharray="2 2" opacity="0.3">
                        <line id="ch-v" x1="50" y1="50" x2="50" y2="450" />
                        <line id="ch-h" x1="50" y1="450" x2="450" y2="450" />
                    </g>
                </svg>
            </div>

            <div class="legend-overlay">
                <div class="legend-item">
                    <div class="legend-color" style="background: #334155; border: 1px dashed #64748b;"></div>
                    <span>标准出厂标定线</span>
                </div>
                <div class="legend-item">
                    <div class="legend-color" style="background: #ef4444;"></div>
                    <span>加速(增压)上行轨迹</span>
                </div>
                <div class="legend-item">
                    <div class="legend-color" style="background: #10b981;"></div>
                    <span>减速(泄压)下行轨迹</span>
                </div>
                <div class="legend-item" style="font-size: 10px; margin-top: 8px; border-top: 1px solid #1f2937; padding-top: 8px;">
                    * 曲线开口反映传感器迟滞失真
                </div>
            </div>
        </div>
    </main>

    <script>
        document.addEventListener("DOMContentLoaded", () => {
            // DOM Elements
            const pathUp = document.getElementById('graph-path-up');
            const pathDown = document.getElementById('graph-path-down');
            const graphPoint = document.getElementById('graph-point');
            const chV = document.getElementById('ch-v');
            const chH = document.getElementById('ch-h');
            
            const valRpm = document.getElementById('val-rpm');
            const valKpa = document.getElementById('val-kpa');
            const valVol = document.getElementById('val-vol');
            const statusText = document.getElementById('engine-status');
            
            const airflowIndicator = document.getElementById('airflow-indicator');
            const pressureLine = document.getElementById('pressure-line');
            const pressureSplit = document.getElementById('pressure-split');
            const engineHeat = document.getElementById('engine-heat');
            const glowStop = document.getElementById('glow-stop');

            // Animation Configuration
            const cycleDuration = 12000; // 12 seconds per cycle
            let startTime = null;
            
            // Physical parameters
            const minRpm = 800;
            const maxRpm = 2500;
            const minKpa = 100; // Atmospheric pressure
            const maxKpa = 300; // Turbo boost
            
            // Graph Mapping (50,450 to 450,50)
            // X: 100kPa -> 50px, 300kPa -> 350px. Formula: x = 50 + (kpa - 100) * (300/200) = 50 + (kpa-100)*1.5
            // Y: 1V -> 450px, 4V -> 150px. Formula: y = 450 - (vol - 1) * (300/3) = 450 - (vol-1)*100
            
            function mapX(kpa) { return 50 + (kpa - 100) * 1.5; }
            function mapY(vol) { return 450 - (vol - 1.0) * 100; }
            
            // Sensor Simulation with hysteresis
            function simulateVoltage(kpa, isAscending) {
                // Ideal linear: 1V at 100kPa, 4V at 300kPa -> v = 1 + (kpa-100)*3/200
                let idealV = 1.0 + (kpa - 100) * 0.015;
                // Add hysteresis (lag): lower voltage when going up, higher when going down
                let hysteresis = isAscending ? -0.08 : 0.12; 
                if (kpa < 110) hysteresis *= (kpa - 100)/10; // smooth start
                if (kpa > 290) hysteresis *= (300 - kpa)/10; // smooth end
                
                return Math.max(0.5, Math.min(4.5, idealV + hysteresis + (Math.random()*0.02 - 0.01))); // Add tiny noise
            }

            let pathUpD = "M 50,450 ";
            let pathDownD = "";
            let lastKpa = minKpa;

            function animate(timestamp) {
                if (!startTime) startTime = timestamp;
                const elapsed = timestamp - startTime;
                const progress = (elapsed % cycleDuration) / cycleDuration;
                
                let phase = '';
                let rpm = minRpm, kpa = minKpa, vol = 1.0;
                let isAscending = true;

                // State Machine (Time distribution)
                // 0.0 - 0.15: Idle (800 RPM)
                // 0.15 - 0.40: Rapid Accel (800 -> 2500 RPM)
                // 0.40 - 0.55: Hold Peak (2500 RPM)
                // 0.55 - 0.85: Decel (2500 -> 800 RPM)
                // 0.85 - 1.00: Idle settling

                if (progress < 0.15) {
                    phase = 'IDLE';
                    // New cycle reset
                    if (progress < 0.02) {
                        pathUpD = "M 50,450 ";
                        pathDownD = "";
                        pathUp.setAttribute('d', pathUpD);
                        pathDown.setAttribute('d', pathDownD);
                    }
                    kpa = minKpa + (Math.random()*2-1); // slight fluctuation
                } else if (progress < 0.40) {
                    phase = 'ACCEL';
                    let t = (progress - 0.15) / 0.25;
                    // Ease-in-out curve for RPM/Pressure
                    let ease = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
                    rpm = minRpm + (maxRpm - minRpm) * ease;
                    kpa = minKpa + (maxKpa - minKpa) * ease;
                } else if (progress < 0.55) {
                    phase = 'PEAK HOLD';
                    rpm = maxRpm + (Math.random()*20-10);
                    kpa = maxKpa + (Math.random()*5-2.5);
                    isAscending = false; // Prepare for turn
                } else if (progress < 0.85) {
                    phase = 'DECEL / COAST';
                    isAscending = false;
                    let t = (progress - 0.55) / 0.30;
                    // Exponential decay for pressure drop
                    let ease = 1 - Math.pow(1 - t, 3);
                    rpm = maxRpm - (maxRpm - minRpm) * ease;
                    kpa = maxKpa - (maxKpa - minKpa) * ease;
                } else {
                    phase = 'IDLE RETURN';
                    isAscending = false;
                    kpa = minKpa + (Math.random()*2-1);
                }

                vol = simulateVoltage(kpa, isAscending);

                // Update DOM UI
                valRpm.innerHTML = `${Math.round(rpm)} <span class="data-unit">RPM</span>`;
                valKpa.innerHTML = `${kpa.toFixed(1)} <span class="data-unit">kPaA</span>`;
                valVol.innerHTML = `${vol.toFixed(3)} <span class="data-unit">V</span>`;
                statusText.innerText = phase;
                
                // Color coding based on phase
                document.body.className = phase === 'ACCEL' ? 'state-accel' : 
                                          phase === 'PEAK HOLD' ? 'state-peak' : 
                                          phase === 'DECEL / COAST' ? 'state-decel' : 'state-idle';

                // Update Physical Visuals
                // Color temperature mapping for airflow (Cyan -> Orange -> Red)
                let pressureRatio = (kpa - minKpa) / (maxKpa - minKpa);
                let r = Math.round(6 + pressureRatio * 233); // 06 -> EF
                let g = Math.round(182 - pressureRatio * 114); // B6 -> 44
                let b = Math.round(212 - pressureRatio * 144); // D4 -> 44
                let color = `rgb(${r},${g},${b})`;
                
                airflowIndicator.setAttribute('stroke', color);
                pressureLine.setAttribute('stroke', color);
                pressureSplit.setAttribute('stroke', color);
                
                glowStop.setAttribute('stop-color', color);
                engineHeat.setAttribute('r', 120 + pressureRatio * 30);
                
                // Stroke dasharray animation speed based on RPM
                airflowIndicator.setAttribute('stroke-dasharray', `${10 + pressureRatio*20} ${10 + pressureRatio*5}`);
                airflowIndicator.style.strokeDashoffset = -(elapsed * (0.05 + pressureRatio*0.15)) + 'px';

                // Update Graph
                let x = mapX(kpa);
                let y = mapY(vol);
                
                graphPoint.setAttribute('cx', x);
                graphPoint.setAttribute('cy', y);
                chV.setAttribute('x1', x); chV.setAttribute('x2', x);
                chH.setAttribute('y1', y); chH.setAttribute('y2', y);

                // Build path string based on phase
                if (phase === 'ACCEL' || (phase === 'IDLE' && progress > 0.1)) {
                    pathUpD += ` L ${x.toFixed(1)},${y.toFixed(1)}`;
                    pathUp.setAttribute('d', pathUpD);
                    if (pathDownD === "") pathDownD = `M ${x.toFixed(1)},${y.toFixed(1)}`; // Sync start point for down path
                } else if (phase === 'PEAK HOLD' || phase === 'DECEL / COAST' || phase === 'IDLE RETURN') {
                     // Start new segment if transitioning
                    if(pathDownD === "" || pathDownD.startsWith("M") && pathDownD.length < 20) {
                        pathDownD = `M ${x.toFixed(1)},${y.toFixed(1)}`;
                    } else {
                        pathDownD += ` L ${x.toFixed(1)},${y.toFixed(1)}`;
                    }
                    pathDown.setAttribute('d', pathDownD);
                }

                lastKpa = kpa;
                requestAnimationFrame(animate);
            }

            // Start animation loop
            requestAnimationFrame(animate);
        });
    </script>
</body>
</html>
积分规则:每轮对话扣10分