分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Transformable Whegs - IFR Principle Animation</title>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700;800&family=Rajdhani:wght@500;600;700&display=swap');

        :root {
            --bg-color: #050914;
            --grid-color: rgba(0, 240, 255, 0.05);
            --primary-cyan: #00f0ff;
            --primary-cyan-dim: rgba(0, 240, 255, 0.3);
            --accent-magenta: #ff2a5f;
            --accent-green: #00ff66;
            --structural-gray: #475569;
            --text-main: #e2e8f0;
            --hud-bg: rgba(5, 9, 20, 0.8);
        }

        body {
            margin: 0;
            padding: 0;
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: 'Rajdhani', sans-serif;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-image: 
                linear-gradient(var(--grid-color) 1px, transparent 1px),
                linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
            background-size: 40px 40px;
            background-position: center center;
        }

        #animation-container {
            position: relative;
            width: 100vw;
            max-width: 1400px;
            aspect-ratio: 16/9;
            max-height: 100vh;
            box-shadow: 0 0 100px rgba(0, 240, 255, 0.05) inset;
            border: 1px solid rgba(0, 240, 255, 0.1);
            overflow: hidden;
        }

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

        /* HUD Overlay Styles */
        .hud-panel {
            position: absolute;
            background: var(--hud-bg);
            border: 1px solid var(--primary-cyan-dim);
            padding: 15px 20px;
            backdrop-filter: blur(4px);
            border-left: 3px solid var(--primary-cyan);
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
            pointer-events: none;
        }

        .hud-top-left { top: 30px; left: 30px; }
        .hud-bottom-right { bottom: 30px; right: 30px; border-left: none; border-right: 3px solid var(--accent-magenta); text-align: right; }
        .hud-bottom-left { bottom: 30px; left: 30px; }

        .hud-title {
            font-family: 'JetBrains Mono', monospace;
            font-size: 0.8rem;
            color: var(--primary-cyan);
            letter-spacing: 2px;
            text-transform: uppercase;
            margin-bottom: 5px;
        }

        .hud-value {
            font-family: 'JetBrains Mono', monospace;
            font-size: 2rem;
            font-weight: 800;
            color: #fff;
            text-shadow: 0 0 10px var(--primary-cyan-dim);
        }

        .hud-value.alert {
            color: var(--accent-magenta);
            text-shadow: 0 0 15px rgba(255, 42, 95, 0.5);
        }

        .hud-value.success {
            color: var(--accent-green);
            text-shadow: 0 0 15px rgba(0, 255, 102, 0.5);
        }

        .hud-unit {
            font-size: 1rem;
            color: var(--structural-gray);
            margin-left: 5px;
        }

        .hud-label {
            font-size: 0.9rem;
            color: #94a3b8;
            margin-top: 5px;
        }

        .triz-box {
            position: absolute;
            top: 30px;
            right: 30px;
            width: 320px;
            background: rgba(0, 255, 102, 0.05);
            border: 1px solid rgba(0, 255, 102, 0.2);
            border-top: 3px solid var(--accent-green);
            padding: 15px;
            backdrop-filter: blur(4px);
        }

        .triz-header {
            font-family: 'JetBrains Mono', monospace;
            color: var(--accent-green);
            font-size: 0.85rem;
            font-weight: bold;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .triz-header::before {
            content: '';
            display: block;
            width: 8px;
            height: 8px;
            background: var(--accent-green);
            box-shadow: 0 0 8px var(--accent-green);
        }

        .triz-desc {
            font-size: 0.95rem;
            line-height: 1.4;
            color: #cbd5e1;
        }

        .highlight-text {
            color: #fff;
            font-weight: 600;
            text-shadow: 0 0 5px rgba(255,255,255,0.3);
        }

        /* SVG Element Styles */
        .glowing-cyan { filter: drop-shadow(0 0 8px var(--primary-cyan)); }
        .glowing-magenta { filter: drop-shadow(0 0 12px var(--accent-magenta)); }
        .glowing-green { filter: drop-shadow(0 0 8px var(--accent-green)); }
        
        .fade-overlay {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            background: var(--bg-color);
            pointer-events: none;
            transition: opacity 0.5s ease;
            opacity: 0;
            z-index: 100;
        }
    </style>
</head>
<body>

<div id="animation-container">
    <svg id="canvas" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
        <defs>
            <!-- Filters for Glow Effects -->
            <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                <feGaussianBlur stdDeviation="4" result="blur" />
                <feMerge>
                    <feMergeNode in="blur" />
                    <feMergeNode in="SourceGraphic" />
                </feMerge>
            </filter>
            
            <filter id="glow-magenta" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur stdDeviation="6" result="blur" />
                <feMerge>
                    <feMergeNode in="blur" />
                    <feMergeNode in="SourceGraphic" />
                </feMerge>
            </filter>

            <!-- Patterns -->
            <pattern id="stair-texture" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
                <path d="M 20 0 L 0 20" stroke="#1e293b" stroke-width="1" fill="none"/>
            </pattern>

            <linearGradient id="ground-grad" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0%" stop-color="#0f172a" />
                <stop offset="100%" stop-color="#050914" />
            </linearGradient>

            <linearGradient id="force-grad" x1="0%" y1="0%" x2="100%" y2="0%">
                <stop offset="0%" stop-color="rgba(255, 42, 95, 0)" />
                <stop offset="100%" stop-color="rgba(255, 42, 95, 1)" />
            </linearGradient>
        </defs>

        <!-- Environment -->
        <g id="environment">
            <!-- Grid Background Lines (Technical Vibe) -->
            <path d="M 0 600 L 1600 600" stroke="rgba(0,240,255,0.2)" stroke-width="1" stroke-dasharray="4 4" />
            <path d="M 0 350 L 1600 350" stroke="rgba(0,240,255,0.2)" stroke-width="1" stroke-dasharray="4 4" />
            
            <!-- Flat Ground -->
            <rect x="0" y="600" width="800" height="300" fill="url(#ground-grad)" />
            <path d="M 0 600 L 800 600" stroke="#334155" stroke-width="4" />
            
            <!-- Stair Obstacle -->
            <rect x="800" y="350" width="800" height="550" fill="url(#ground-grad)" />
            <rect x="800" y="350" width="800" height="550" fill="url(#stair-texture)" />
            <path d="M 800 600 L 800 350 L 1600 350" stroke="#00f0ff" stroke-width="4" filter="url(#glow-cyan)" />
            
            <!-- Stair Corner Highlight (Target) -->
            <circle cx="800" cy="350" r="6" fill="#00f0ff" filter="url(#glow-cyan)" />
            <circle cx="800" cy="350" r="12" fill="none" stroke="#00f0ff" stroke-width="1" class="pulse-ring" opacity="0.5"/>
        </g>

        <!-- Dynamic Vectors & Forces -->
        <g id="force-vectors" opacity="0">
            <!-- Resistance Force Arrow -->
            <path d="M 880 500 L 810 500" stroke="url(#force-grad)" stroke-width="8" marker-end="url(#arrowhead)" />
            <polygon points="800,500 815,492 815,508" fill="#ff2a5f" filter="url(#glow-magenta)" />
            <text x="890" y="505" fill="#ff2a5f" font-family="'JetBrains Mono'" font-size="16" filter="url(#glow-magenta)">OBSTACLE RESISTANCE</text>
        </g>

        <!-- Main Wheel Assembly -->
        <g id="wheel-assembly" transform="translate(300, 480)">
            <!-- Outer Rim Blocks (Treads/Whegs) -->
            <g id="rim-blocks"></g>
            
            <!-- Linkages connecting Inner to Rim -->
            <g id="linkages"></g>
            
            <!-- Outer Hub (Housing) -->
            <circle cx="0" cy="0" r="50" fill="#0f172a" stroke="#475569" stroke-width="3" />
            <circle cx="0" cy="0" r="50" fill="none" stroke="#00f0ff" stroke-width="1" stroke-dasharray="2 6" opacity="0.5"/>
            
            <!-- Inner Hub (Motor Driven) -->
            <g id="inner-hub">
                <circle cx="0" cy="0" r="30" fill="#1e293b" stroke="#00f0ff" stroke-width="2" />
                <polygon points="0,-15 13,7.5 -13,7.5" fill="#00f0ff" opacity="0.3" /> <!-- Direction Indicator -->
                <circle cx="0" cy="0" r="8" fill="#00f0ff" filter="url(#glow-cyan)" />
            </g>

            <!-- Spring Pin Mechanism (Torque Sensor) -->
            <g id="locking-pin" transform="translate(0, -40)">
                <rect x="-6" y="-10" width="12" height="20" fill="#334155" rx="2" />
                <path id="pin-core" d="M -3 -8 L 3 -8 L 3 8 L -3 8 Z" fill="#00ff66" filter="url(#glow-green)" />
            </g>
            
            <!-- Measurement Overlays (Dynamic) -->
            <g id="wheel-measurements" opacity="0">
                <path id="radius-line" d="M 0 0 L 0 -120" stroke="#00f0ff" stroke-width="1" stroke-dasharray="4 4" />
                <path id="extension-line" d="M 0 -120 L 0 -160" stroke="#00ff66" stroke-width="2" />
                <text x="10" y="-140" fill="#00ff66" font-family="'JetBrains Mono'" font-size="14" font-weight="bold">+40mm EXTENSION</text>
            </g>
        </g>
    </svg>

    <!-- HUD Elements -->
    <div class="hud-panel hud-top-left">
        <div class="hud-title">System Status</div>
        <div class="hud-value" id="hud-mode">WHEEL MODE</div>
        <div class="hud-label" id="hud-desc">High-speed flat rolling. High efficiency.</div>
    </div>

    <div class="hud-panel hud-bottom-left">
        <div class="hud-title">Drive Torque</div>
        <div class="hud-value" id="hud-torque">2.1<span class="hud-unit">N·m</span></div>
        <div style="width: 200px; height: 4px; background: #1e293b; margin-top: 10px; position: relative;">
            <div id="torque-bar" style="width: 25%; height: 100%; background: var(--primary-cyan); transition: width 0.1s, background 0.3s;"></div>
            <div style="position: absolute; left: 80%; top: -8px; width: 2px; height: 20px; background: var(--accent-magenta);"></div>
            <div style="position: absolute; left: 80%; top: 15px; font-family: 'JetBrains Mono'; font-size: 10px; color: var(--accent-magenta);">8 N·m THRESHOLD</div>
        </div>
    </div>

    <div class="hud-panel hud-bottom-right">
        <div class="hud-title">Traction Extension</div>
        <div class="hud-value" id="hud-extension">+0<span class="hud-unit">mm</span></div>
        <div class="hud-label">Adaptive Wheg Radius</div>
    </div>

    <div class="triz-box">
        <div class="triz-header">TRIZ IFR ACTIVE</div>
        <div class="triz-desc">
            <span class="highlight-text">Ideal Final Result:</span> The system adapts itself without complex external controls.<br><br>
            By utilizing the <span class="highlight-text">obstacle's resistance</span> as the trigger energy, the wheel instantly transforms into legged mode (Whegs) only when required, eliminating the contradiction between flat-ground efficiency and obstacle-climbing capability.
        </div>
    </div>

    <div id="fade" class="fade-overlay"></div>
</div>

<script>
    document.addEventListener("DOMContentLoaded", () => {
        // --- SVG Elements & Constants ---
        const rimGroup = document.getElementById('rim-blocks');
        const linkGroup = document.getElementById('linkages');
        const wheelAssembly = document.getElementById('wheel-assembly');
        const innerHub = document.getElementById('inner-hub');
        const pinCore = document.getElementById('pin-core');
        const forceVectors = document.getElementById('force-vectors');
        const wheelMeasurements = document.getElementById('wheel-measurements');
        
        // HUD Elements
        const hudMode = document.getElementById('hud-mode');
        const hudDesc = document.getElementById('hud-desc');
        const hudTorque = document.getElementById('hud-torque');
        const torqueBar = document.getElementById('torque-bar');
        const hudExtension = document.getElementById('hud-extension');
        const fadeLayer = document.getElementById('fade');

        const NUM_SEGMENTS = 6;
        const BASE_RADIUS = 120; // Closed wheel radius
        const MAX_EXTENSION = 40; // +40mm requirement
        const INNER_HUB_RADIUS = 25;
        const LINK_ANCHOR_RADIUS = 85; // Point on rim block where linkage attaches
        
        // --- Geometry Generation ---
        
        // Create the 6 Rim Blocks (Whegs) and Linkages
        const segments = [];
        for(let i=0; i<NUM_SEGMENTS; i++) {
            const angleDeg = i * (360 / NUM_SEGMENTS);
            
            // 1. Create Rim Block Path
            const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
            // Draw a thick structural arc that tapers into a hook
            // Center is (0,0), drawing pointing "up" (angle 0), then we rotate the group
            const arcSpan = 52; // degrees
            const halfSpan = arcSpan / 2;
            const rOuter = BASE_RADIUS;
            const rInner = BASE_RADIUS - 18;
            
            // Math for arc coordinates
            const rad = Math.PI / 180;
            const x1o = Math.sin(-halfSpan * rad) * rOuter; const y1o = -Math.cos(-halfSpan * rad) * rOuter;
            const x2o = Math.sin(halfSpan * rad) * rOuter;  const y2o = -Math.cos(halfSpan * rad) * rOuter;
            const x1i = Math.sin(-halfSpan * rad) * rInner; const y1i = -Math.cos(-halfSpan * rad) * rInner;
            const x2i = Math.sin(halfSpan * rad) * rInner;  const y2i = -Math.cos(halfSpan * rad) * rInner;
            
            // Shape: Arc, then angle down to form hook tip, back inner arc, then close
            const d = `
                M ${x1o} ${y1o} 
                A ${rOuter} ${rOuter} 0 0 1 ${x2o} ${y2o}
                L ${x2o + 15} ${y2o + 10} 
                L ${x2i + 5} ${y2i + 5}
                A ${rInner} ${rInner} 0 0 0 ${x1i} ${y1i}
                Z`;
            
            path.setAttribute("d", d);
            path.setAttribute("fill", "#1e293b");
            path.setAttribute("stroke", "#00f0ff");
            path.setAttribute("stroke-width", "2");
            
            const rimG = document.createElementNS("http://www.w3.org/2000/svg", "g");
            rimG.appendChild(path);
            
            // Add pivot point visual on rim
            const rimPivot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
            rimPivot.setAttribute("cx", "0");
            rimPivot.setAttribute("cy", `-${LINK_ANCHOR_RADIUS}`);
            rimPivot.setAttribute("r", "4");
            rimPivot.setAttribute("fill", "#fff");
            rimG.appendChild(rimPivot);

            rimGroup.appendChild(rimG);
            
            // 2. Create Linkage Line
            const link = document.createElementNS("http://www.w3.org/2000/svg", "line");
            link.setAttribute("stroke", "#94a3b8");
            link.setAttribute("stroke-width", "4");
            link.setAttribute("stroke-linecap", "round");
            linkGroup.appendChild(link);
            
            segments.push({
                rimGroup: rimG,
                linkLine: link,
                baseAngle: angleDeg
            });
        }

        // --- Kinematics Engine ---
        // Calculates positions based on deployment progress (0 to 1)
        function updateKinematics(deployProgress) {
            // Inner hub rotates relative to outer base to push linkages
            const relativeAngle = deployProgress * 45; // max 45 deg relative rotation
            innerHub.setAttribute("transform", `rotate(${relativeAngle})`);
            
            // Calculate leg extension and local rotation (to expose the hook)
            const currentExtension = deployProgress * MAX_EXTENSION;
            const currentRadiusOffset = currentExtension;
            const hookPronouncementAngle = deployProgress * 25; // Rotate leg to point hook down
            
            segments.forEach((seg, i) => {
                // 1. Position Rim Block
                // It moves outward radially, and rotates locally to expose the hook
                const totalAngle = seg.baseAngle;
                
                // Translate out, then rotate locally, then apply base angle rotation
                // Note: SVG transforms are applied right-to-left.
                seg.rimGroup.setAttribute("transform", `
                    rotate(${totalAngle})
                    translate(0, -${currentRadiusOffset})
                    rotate(${hookPronouncementAngle})
                `);
                
                // 2. Position Linkage
                // Pivot A: on the inner hub, rotated by relativeAngle
                const rad = Math.PI / 180;
                const angleA = (seg.baseAngle + relativeAngle) * rad;
                const ax = Math.sin(angleA) * INNER_HUB_RADIUS;
                const ay = -Math.cos(angleA) * INNER_HUB_RADIUS;
                
                // Pivot B: on the rim block. 
                // We must calculate its absolute position taking into account the rim's local translation and rotation
                const angleB_base = seg.baseAngle * rad;
                // Position of rim origin before local rotation
                const rx = Math.sin(angleB_base) * currentRadiusOffset;
                const ry = -Math.cos(angleB_base) * currentRadiusOffset;
                
                // The pivot B is at (0, -LINK_ANCHOR_RADIUS) in the rim's local un-rotated space.
                // We apply the local rotation and translation.
                const localPivotDist = LINK_ANCHOR_RADIUS;
                const totalAngleB = (seg.baseAngle + hookPronouncementAngle) * rad;
                
                // Center of the rim group after translation
                const centerRimX = Math.sin(angleB_base) * currentRadiusOffset;
                const centerRimY = -Math.cos(angleB_base) * currentRadiusOffset;
                
                // Absolute position of B
                const bx = centerRimX + Math.sin(totalAngleB) * localPivotDist;
                const by = centerRimY - Math.cos(totalAngleB) * localPivotDist;
                
                seg.linkLine.setAttribute("x1", ax);
                seg.linkLine.setAttribute("y1", ay);
                seg.linkLine.setAttribute("x2", bx);
                seg.linkLine.setAttribute("y2", by);
            });
            
            // Update measurements overlay
            const measureLine = document.getElementById('extension-line');
            measureLine.setAttribute("d", `M 0 -120 L 0 -${120 + currentExtension}`);
            if(deployProgress > 0) {
                wheelMeasurements.setAttribute("opacity", deployProgress);
            } else {
                wheelMeasurements.setAttribute("opacity", 0);
            }
        }

        // --- Animation State Machine ---
        let startTime = null;
        const CYCLE_DURATION = 9000; // Total loop time in ms

        function easeInOutCubic(x) {
            return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
        }

        function easeOutBack(x) {
            const c1 = 1.70158;
            const c3 = c1 + 1;
            return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
        }

        function updateFrame(timestamp) {
            if (!startTime) startTime = timestamp;
            let elapsed = (timestamp - startTime) % CYCLE_DURATION;
            
            // Animation State Variables
            let wheelX = 0;
            let wheelY = 480; // Flat ground Y = 600 - base_radius(120)
            let wheelRot = 0;
            let deployLevel = 0;
            
            // HUD Variables
            let torque = 2.1;
            let mode = "WHEEL MODE";
            let modeColor = "var(--primary-cyan)";
            let isAlert = false;
            let isSuccess = false;

            // Phase 1: Flat Rolling (0 - 2000ms)
            if (elapsed < 2000) {
                let p = elapsed / 2000;
                wheelX = 100 + p * 580; // Move from 100 to 680 (hitting stair at 800 - 120 = 680)
                wheelRot = p * 360 * 1.5; // Rotate 1.5 times
                torque = 2.0 + Math.random() * 0.4; // Normal fluctuation
            }
            // Phase 2: Impact & Resistance Builds (2000 - 2400ms)
            else if (elapsed < 2400) {
                let p = (elapsed - 2000) / 400;
                wheelX = 680;
                wheelRot = 360 * 1.5 + p * 10; // Slight rotation as it pushes
                
                // Torque spikes past 8Nm
                torque = 2.4 + p * 7.5; // reaches ~9.9
                isAlert = true;
                
                if (p > 0.8) {
                    forceVectors.setAttribute("opacity", (p - 0.8) * 5); // Fade in vector
                    pinCore.setAttribute("fill", "#ff2a5f"); // Pin turns red (broken)
                    pinCore.setAttribute("filter", "url(#glow-magenta)");
                } else {
                    forceVectors.setAttribute("opacity", 0);
                    pinCore.setAttribute("fill", "#00ff66");
                    pinCore.setAttribute("filter", "url(#glow-green)");
                }
                mode = "RESISTANCE DETECTED";
                modeColor = "var(--accent-magenta)";
            }
            // Phase 3: Deployment (2400 - 3200ms)
            else if (elapsed < 3200) {
                let p = (elapsed - 2400) / 800;
                let easedP = easeOutBack(Math.min(p * 1.2, 1)); // Snappy deploy
                
                wheelX = 680;
                wheelRot = 360 * 1.5 + 10 + p * 30; // Motor continues turning to drive deploy
                deployLevel = easedP;
                torque = 8.5 + Math.random(); // High torque maintained during deploy
                
                forceVectors.setAttribute("opacity", 1 - p); // Fade out vector
                pinCore.setAttribute("fill", "#ff2a5f");
                
                mode = "WHEGS DEPLOYING";
                modeColor = "var(--accent-magenta)";
                isAlert = true;
            }
            // Phase 4: Climbing the Step (3200 - 5500ms)
            else if (elapsed < 5500) {
                let p = (elapsed - 3200) / 2300;
                let easedP = easeInOutCubic(p);
                
                // Kinematics: The wheel center pivots over the stair edge
                // Start: X=680, Y=480. Target: X=850, Y=230 (350 stair height - 120 radius)
                wheelX = 680 + easedP * 170;
                wheelY = 480 - easedP * 250;
                
                // Rotation continues as it climbs
                wheelRot = 360 * 1.5 + 40 + p * 180;
                deployLevel = 1;
                torque = 6.0 - p * 3; // Torque drops as it crests
                
                mode = "CLIMBING MODE (WHEGS)";
                modeColor = "var(--accent-green)";
                isSuccess = true;
                pinCore.setAttribute("fill", "#334155"); // Pin idle
                pinCore.setAttribute("filter", "none");
            }
            // Phase 5: Retraction (5500 - 6500ms)
            else if (elapsed < 6500) {
                let p = (elapsed - 5500) / 1000;
                let easedP = easeInOutCubic(p);
                
                wheelX = 850 + p * 150; // Move forward on top
                wheelY = 230; // Top level Y
                wheelRot = 360 * 1.5 + 220 + p * 90;
                
                deployLevel = 1 - easedP; // Retract
                torque = 3.0 - p * 0.9;
                
                mode = "AUTO-RETRACTING";
                modeColor = "var(--primary-cyan)";
                pinCore.setAttribute("fill", "#00ff66"); // Pin locks back
                pinCore.setAttribute("filter", "url(#glow-green)");
            }
            // Phase 6: Roll Away on top (6500 - 8000ms)
            else if (elapsed < 8000) {
                let p = (elapsed - 6500) / 1500;
                wheelX = 1000 + p * 400; // Roll off screen
                wheelY = 230;
                wheelRot = 360 * 1.5 + 310 + p * 270;
                deployLevel = 0;
                torque = 2.0 + Math.random() * 0.4;
                
                mode = "WHEEL MODE";
                modeColor = "var(--primary-cyan)";
            }
            // Phase 7: Fade out and reset (8000 - 9000ms)
            else {
                let p = (elapsed - 8000) / 1000;
                wheelX = 1400;
                wheelY = 230;
                deployLevel = 0;
                torque = 0;
                
                if (p < 0.5) {
                    fadeLayer.style.opacity = p * 2; // Fade to black
                } else {
                    // Reset position behind black screen
                    wheelX = 100;
                    wheelY = 480;
                    wheelRot = 0;
                    fadeLayer.style.opacity = 2 - (p * 2); // Fade back in
                }
                mode = "SYSTEM RESET";
            }

            // Apply calculated transforms
            wheelAssembly.setAttribute("transform", `translate(${wheelX}, ${wheelY}) rotate(${wheelRot})`);
            updateKinematics(deployLevel);

            // Update HUD DOM
            hudTorque.innerHTML = `${torque.toFixed(1)}<span class="hud-unit">N·m</span>`;
            hudTorque.className = `hud-value ${isAlert ? 'alert' : ''}`;
            
            let torquePercent = Math.min((torque / 10) * 100, 100);
            torqueBar.style.width = `${torquePercent}%`;
            torqueBar.style.background = torque > 8 ? 'var(--accent-magenta)' : 'var(--primary-cyan)';

            hudMode.textContent = mode;
            hudMode.style.color = modeColor;
            
            const extValue = (deployLevel * MAX_EXTENSION).toFixed(1);
            hudExtension.innerHTML = `+${extValue}<span class="hud-unit">mm</span>`;
            hudExtension.className = `hud-value ${deployLevel > 0.9 ? 'success' : ''}`;
            
            if(deployLevel === 0) hudDesc.textContent = "High-speed flat rolling. High efficiency.";
            else if(deployLevel > 0 && deployLevel < 1 && isAlert) hudDesc.textContent = "Torque > 8N·m. Mechanical lock disengaged.";
            else if(deployLevel === 1) hudDesc.textContent = "Hooking mechanism active. Overcoming obstacle.";

            requestAnimationFrame(updateFrame);
        }

        // Initialization
        updateKinematics(0); // Set initial closed state
        requestAnimationFrame(updateFrame); // Start Auto-Play Loop
    });
</script>

</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分