分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>顶置随动柔性同步带系统原理动画</title>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@400;500;700&display=swap');

        :root {
            --bg-color: #0b0e14;
            --grid-color: rgba(30, 41, 59, 0.5);
            --accent-cyan: #00f0ff;
            --accent-amber: #ffaa00;
            --accent-red: #ff2a6d;
            --text-main: #e2e8f0;
            --text-muted: #94a3b8;
            --mech-dark: #1e293b;
            --mech-light: #334155;
            --glass-color: rgba(186, 230, 253, 0.2);
            --glass-border: rgba(186, 230, 253, 0.8);
        }

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

        body {
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: 'Noto Sans SC', sans-serif;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
            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;
        }

        /* Subtle noise texture */
        body::before {
            content: "";
            position: fixed;
            top: 0; left: 0; width: 100vw; height: 100vh;
            background: url('data:image/svg+xml;utf8,%3Csvg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.03"/%3E%3C/svg%3E');
            pointer-events: none;
            z-index: 10;
        }

        #animation-container {
            width: 100%;
            max-width: 1400px;
            height: 80vh;
            position: relative;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        svg {
            width: 100%;
            height: 100%;
            filter: drop-shadow(0 0 20px rgba(0, 0, 0, 0.5));
        }

        /* Typography & Overlays */
        .overlay-panel {
            position: absolute;
            background: rgba(11, 14, 20, 0.85);
            border: 1px solid rgba(0, 240, 255, 0.2);
            backdrop-filter: blur(8px);
            padding: 16px;
            border-radius: 8px;
            z-index: 5;
            pointer-events: none;
        }

        .panel-top-left { top: 20px; left: 20px; border-left: 3px solid var(--accent-cyan); }
        .panel-bottom-right { bottom: 20px; right: 20px; border-right: 3px solid var(--accent-amber); text-align: right; }
        
        .hud-text {
            font-family: 'JetBrains Mono', monospace;
            font-size: 12px;
            color: var(--accent-cyan);
            letter-spacing: 1px;
            margin-bottom: 4px;
            text-transform: uppercase;
        }

        .title {
            font-size: 18px;
            font-weight: 700;
            color: #fff;
            margin-bottom: 8px;
        }

        .desc {
            font-size: 13px;
            color: var(--text-muted);
            line-height: 1.5;
            max-width: 300px;
        }

        .data-row {
            display: flex;
            justify-content: space-between;
            margin-top: 8px;
            font-size: 12px;
            border-bottom: 1px dashed rgba(255,255,255,0.1);
            padding-bottom: 4px;
        }
        
        .data-label { color: var(--text-muted); }
        .data-value { font-family: 'JetBrains Mono', monospace; font-weight: 700; }
        .val-cyan { color: var(--accent-cyan); }
        .val-amber { color: var(--accent-amber); }

        /* Status Indicator */
        .status-indicator {
            position: absolute;
            top: 20px;
            right: 20px;
            display: flex;
            align-items: center;
            gap: 8px;
            font-family: 'JetBrains Mono', monospace;
            font-size: 12px;
            color: var(--text-main);
            background: rgba(11, 14, 20, 0.8);
            padding: 8px 12px;
            border-radius: 20px;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }

        .status-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background-color: var(--accent-cyan);
            box-shadow: 0 0 8px var(--accent-cyan);
            animation: pulse 2s infinite;
        }

        @keyframes pulse {
            0% { opacity: 1; box-shadow: 0 0 8px var(--accent-cyan); }
            50% { opacity: 0.4; box-shadow: 0 0 2px var(--accent-cyan); }
            100% { opacity: 1; box-shadow: 0 0 8px var(--accent-cyan); }
        }

        .hidden { opacity: 0; transition: opacity 0.3s; }
        .visible { opacity: 1; transition: opacity 0.3s; }

    </style>
</head>
<body>

    <div id="animation-container">
        <!-- Top Left Info -->
        <div class="overlay-panel panel-top-left">
            <div class="hud-text">SYS.IFR.01 // Kinematic Visualization</div>
            <div class="title">顶置随动柔性同步带系统</div>
            <div class="desc">构建上下夹击的“汉堡包”式搬运架构,消除滚动摩擦产生的相对滑动。</div>
        </div>

        <!-- Bottom Right Telemetry -->
        <div class="overlay-panel panel-bottom-right">
            <div class="hud-text">Real-time Telemetry</div>
            <div class="data-row">
                <span class="data-label">V_top (顶部线速度)</span>
                <span class="data-value val-cyan" id="v-top-display">0.000 m/s</span>
            </div>
            <div class="data-row">
                <span class="data-label">V_bottom (底部线速度)</span>
                <span class="data-value val-cyan" id="v-bot-display">0.000 m/s</span>
            </div>
            <div class="data-row">
                <span class="data-label">ΔV (速度差)</span>
                <span class="data-value val-amber">< 0.01 mm/s</span>
            </div>
            <div class="data-row">
                <span class="data-label">PU海绵压缩量</span>
                <span class="data-value val-amber" id="comp-display">0.0 mm</span>
            </div>
            <div class="data-row">
                <span class="data-label">表面摩擦状态</span>
                <span class="data-value val-cyan" id="friction-display">Static</span>
            </div>
        </div>

        <!-- System Status -->
        <div class="status-indicator">
            <div class="status-dot"></div>
            <span id="phase-status">System Standby</span>
        </div>

        <svg viewBox="0 0 1200 600" preserveAspectRatio="xMidYMid meet">
            <defs>
                <!-- Gradients -->
                <linearGradient id="beltGrad" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#475569" />
                    <stop offset="50%" stop-color="#1e293b" />
                    <stop offset="100%" stop-color="#0f172a" />
                </linearGradient>
                <linearGradient id="glassGrad" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="rgba(186, 230, 253, 0.4)" />
                    <stop offset="50%" stop-color="rgba(186, 230, 253, 0.1)" />
                    <stop offset="100%" stop-color="rgba(186, 230, 253, 0.3)" />
                </linearGradient>

                <!-- Patterns -->
                <pattern id="spongePattern" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
                    <line x1="0" y1="0" x2="0" y2="10" stroke="#d97706" stroke-width="2" opacity="0.4" />
                </pattern>
                
                <pattern id="beltTeeth" width="20" height="10" patternUnits="userSpaceOnUse">
                    <rect x="0" y="0" width="10" height="10" fill="#0f172a" />
                    <rect x="10" y="0" width="10" height="10" fill="#334155" />
                </pattern>

                <!-- Filters -->
                <filter id="glowCyan" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="5" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="glowAmber" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="8" result="blur" />
                    <feComponentTransfer in="blur" result="glow">
                        <feFuncR type="linear" slope="1.5" />
                        <feFuncG type="linear" slope="1" />
                        <feFuncB type="linear" slope="0" />
                    </feComponentTransfer>
                    <feComposite in="SourceGraphic" in2="glow" operator="over" />
                </filter>

                <!-- Arrow Marker -->
                <marker id="arrowRed" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                    <path d="M 0 1 L 10 5 L 0 9 z" fill="var(--accent-red)" />
                </marker>
                <marker id="arrowCyan" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                    <path d="M 0 1 L 10 5 L 0 9 z" fill="var(--accent-cyan)" />
                </marker>
            </defs>

            <!-- Grid Lines (Background Tech Details) -->
            <g stroke="rgba(255,255,255,0.05)" stroke-width="1">
                <line x1="100" y1="0" x2="100" y2="600" stroke-dasharray="4 4"/>
                <line x1="1100" y1="0" x2="1100" y2="600" stroke-dasharray="4 4"/>
                <line x1="0" y1="300" x2="1200" y2="300" stroke-dasharray="2 6"/>
            </g>

            <!-- Sync Mechanism (Background) -->
            <g opacity="0.3">
                <path d="M 150 150 L 150 450" stroke="var(--text-muted)" stroke-width="4" stroke-dasharray="8 4" />
                <path d="M 1050 150 L 1050 450" stroke="var(--text-muted)" stroke-width="4" stroke-dasharray="8 4" />
                <text x="160" y="300" fill="var(--text-muted)" font-family="JetBrains Mono" font-size="10" transform="rotate(-90 160 300)">PHYSICAL SYNC SHAFT</text>
            </g>

            <!-- Bottom Conveyor -->
            <g id="bottom-system">
                <!-- Pulleys -->
                <circle cx="150" cy="450" r="40" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="4" />
                <circle cx="1050" cy="450" r="40" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="4" />
                <circle cx="150" cy="450" r="10" fill="var(--accent-cyan)" />
                <circle cx="1050" cy="450" r="10" fill="var(--mech-light)" />
                
                <!-- Rotating elements inside pulleys -->
                <g id="pulley-spokes-bot-1" transform="translate(150, 450)">
                    <line x1="-30" y1="0" x2="30" y2="0" stroke="var(--mech-light)" stroke-width="4"/>
                    <line x1="0" y1="-30" x2="0" y2="30" stroke="var(--mech-light)" stroke-width="4"/>
                </g>
                <g id="pulley-spokes-bot-2" transform="translate(1050, 450)">
                    <line x1="-30" y1="0" x2="30" y2="0" stroke="var(--mech-light)" stroke-width="4"/>
                    <line x1="0" y1="-30" x2="0" y2="30" stroke="var(--mech-light)" stroke-width="4"/>
                </g>

                <!-- Bottom Belt Path -->
                <path d="M 150 410 L 1050 410 A 40 40 0 0 1 1050 490 L 150 490 A 40 40 0 0 1 150 410 Z" fill="none" stroke="url(#beltGrad)" stroke-width="12" />
                <!-- Animated Belt Teeth -->
                <rect id="belt-bot-teeth" x="150" y="405" width="900" height="10" fill="url(#beltTeeth)" clip-path="url(#belt-clip)" />
            </g>

            <!-- Top Conveyor (Synchronized Flexible Belt) -->
            <g id="top-system">
                <!-- Pulleys -->
                <circle cx="150" cy="150" r="40" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="4" />
                <circle cx="1050" cy="150" r="40" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="4" />
                <circle cx="150" cy="150" r="10" fill="var(--accent-cyan)" />
                <circle cx="1050" cy="150" r="10" fill="var(--mech-light)" />

                <g id="pulley-spokes-top-1" transform="translate(150, 150)">
                    <line x1="-30" y1="0" x2="30" y2="0" stroke="var(--mech-light)" stroke-width="4"/>
                    <line x1="0" y1="-30" x2="0" y2="30" stroke="var(--mech-light)" stroke-width="4"/>
                </g>
                <g id="pulley-spokes-top-2" transform="translate(1050, 150)">
                    <line x1="-30" y1="0" x2="30" y2="0" stroke="var(--mech-light)" stroke-width="4"/>
                    <line x1="0" y1="-30" x2="0" y2="30" stroke="var(--mech-light)" stroke-width="4"/>
                </g>

                <!-- Base Belt -->
                <path d="M 150 110 L 1050 110 A 40 40 0 0 1 1050 190 L 150 190 A 40 40 0 0 1 150 110 Z" fill="none" stroke="url(#beltGrad)" stroke-width="12" />
                <rect id="belt-top-teeth" x="150" y="185" width="900" height="10" fill="url(#beltTeeth)" />

                <!-- Dynamic PU Sponge Layer -->
                <!-- The path 'd' attribute will be updated via JS to simulate compression -->
                <path id="pu-sponge" d="" fill="url(#spongePattern)" stroke="var(--accent-amber)" stroke-width="2" filter="url(#glowAmber)" opacity="0.9" />
                
                <!-- Sponge Label -->
                <text x="600" y="225" fill="var(--accent-amber)" font-family="Noto Sans SC" font-size="12" font-weight="700" text-anchor="middle" letter-spacing="2">高回弹聚氨酯(PU)柔性层</text>
            </g>

            <!-- The Glass Object -->
            <g id="glass-group">
                <!-- Subtle glow when clamped -->
                <rect id="glass-glow" x="-10" y="385" width="220" height="30" fill="var(--accent-cyan)" opacity="0" filter="url(#glowCyan)" />
                <rect id="glass-plate" x="0" y="390" width="200" height="20" fill="url(#glassGrad)" stroke="var(--glass-border)" stroke-width="2" />
                <!-- Glass internal reflection lines -->
                <line id="glass-refl-1" x1="10" y1="395" x2="190" y2="395" stroke="rgba(255,255,255,0.4)" stroke-width="1" />
                <text id="glass-label" x="100" y="405" fill="#fff" font-family="JetBrains Mono" font-size="10" text-anchor="middle" dominant-baseline="middle">GLASS SUBSTRATE</text>
            </g>

            <!-- Vector Annotations (Dynamically positioned and shown) -->
            <g id="vectors" class="hidden">
                <!-- Top Velocity Arrow -->
                <path id="v-top-arrow" d="M 0 375 L 60 375" stroke="var(--accent-red)" stroke-width="3" fill="none" marker-end="url(#arrowRed)" />
                <text id="v-top-text" x="65" y="370" fill="var(--accent-red)" font-family="JetBrains Mono" font-size="12" font-weight="bold">v1</text>
                
                <!-- Bottom Velocity Arrow -->
                <path id="v-bot-arrow" d="M 0 425 L 60 425" stroke="var(--accent-cyan)" stroke-width="3" fill="none" marker-end="url(#arrowCyan)" />
                <text id="v-bot-text" x="65" y="435" fill="var(--accent-cyan)" font-family="JetBrains Mono" font-size="12" font-weight="bold">v2</text>
                
                <!-- Equal Sign -->
                <text id="v-eq-text" x="45" y="405" fill="#fff" font-family="JetBrains Mono" font-size="14" font-weight="bold" text-anchor="middle" dominant-baseline="middle" filter="url(#glowCyan)">v1 = v2</text>

                <!-- Pressure Arrows (Pointing inwards) -->
                <g id="pressure-arrows" opacity="0.6">
                    <!-- Top down -->
                    <path d="M 100 370 L 100 385" stroke="var(--accent-amber)" stroke-width="2" marker-end="url(#arrowCyan)" />
                    <path d="M 150 370 L 150 385" stroke="var(--accent-amber)" stroke-width="2" marker-end="url(#arrowCyan)" />
                    <!-- Bot up -->
                    <path d="M 100 430 L 100 415" stroke="var(--accent-cyan)" stroke-width="2" marker-end="url(#arrowCyan)" />
                    <path d="M 150 430 L 150 415" stroke="var(--accent-cyan)" stroke-width="2" marker-end="url(#arrowCyan)" />
                </g>
            </g>

            <!-- Macro Zoom Line (Indicating zero gap/compression) -->
            <g id="macro-view" opacity="0">
                <circle cx="0" cy="390" r="25" fill="none" stroke="var(--accent-amber)" stroke-width="1" stroke-dasharray="2 2" />
                <line x1="0" y1="365" x2="30" y2="300" stroke="var(--accent-amber)" stroke-width="1" stroke-dasharray="2 2" />
                <text x="35" y="295" fill="var(--accent-amber)" font-family="Noto Sans SC" font-size="11">接触面零相对滑动</text>
            </g>

            <!-- Entrance Funnel Indicator -->
            <g id="funnel-ind" opacity="0">
                <path d="M 140 370 Q 150 380 180 388" fill="none" stroke="var(--text-muted)" stroke-width="1.5" stroke-dasharray="3 3" marker-end="url(#arrowCyan)" />
                <text x="130" y="360" fill="var(--text-muted)" font-family="Noto Sans SC" font-size="11">喇叭状倒角入口</text>
            </g>

        </svg>
    </div>

    <script>
        document.addEventListener("DOMContentLoaded", () => {
            // -- Elements --
            const sponge = document.getElementById('pu-sponge');
            const glassGroup = document.getElementById('glass-group');
            const glassPlate = document.getElementById('glass-plate');
            const glassGlow = document.getElementById('glass-glow');
            const spokesBot1 = document.getElementById('pulley-spokes-bot-1');
            const spokesBot2 = document.getElementById('pulley-spokes-bot-2');
            const spokesTop1 = document.getElementById('pulley-spokes-top-1');
            const spokesTop2 = document.getElementById('pulley-spokes-top-2');
            const beltBotTeeth = document.getElementById('belt-bot-teeth');
            const beltTopTeeth = document.getElementById('belt-top-teeth');
            
            const vectors = document.getElementById('vectors');
            const pressureArrows = document.getElementById('pressure-arrows');
            const vTopArrow = document.getElementById('v-top-arrow');
            const vBotArrow = document.getElementById('v-bot-arrow');
            const vTopText = document.getElementById('v-top-text');
            const vBotText = document.getElementById('v-bot-text');
            const vEqText = document.getElementById('v-eq-text');
            const macroView = document.getElementById('macro-view');
            const funnelInd = document.getElementById('funnel-ind');

            const phaseStatus = document.getElementById('phase-status');
            const vTopDisplay = document.getElementById('v-top-display');
            const vBotDisplay = document.getElementById('v-bot-display');
            const compDisplay = document.getElementById('comp-display');
            const frictionDisplay = document.getElementById('friction-display');

            // -- Configuration Math --
            // Y coordinates
            const botBeltY = 410; // Surface of bottom belt
            const topBeltBaseY = 190; // Bottom of the rigid top belt
            const glassHeight = 20;
            const glassY = 390; // Top of glass (410 - 20)
            
            // Sponge configurations
            const spongeUncompressedY = 395; // Reaches down to 395 (leaves 5px gap to glass top mathematically, but visually we compress)
            const spongeCompressedY = 390;   // Compresses to glass top (390)
            const maxCompression = spongeUncompressedY - spongeCompressedY; // 5 units visual
            
            const spongeStartX = 150;
            const spongeEndX = 1050;
            const spongePathSegments = 100;
            const spongeStep = (spongeEndX - spongeStartX) / spongePathSegments;
            
            // Animation variables
            let glassX = -250;
            const glassWidth = 200;
            const endX = 1250;
            const baseVelocity = 3.5; // pixels per frame roughly
            let time = 0;

            // -- Functions --

            // Generate the dynamic SVG path for the PU sponge
            function updateSpongePath(currentGlassX) {
                let path = `M ${spongeStartX} ${topBeltBaseY} L ${spongeEndX} ${topBeltBaseY}`; // Top rigid edge
                
                let bottomPoints = [];
                const chamferDist = 40; // Soft ramp for sponge compression

                for (let i = spongePathSegments; i >= 0; i--) {
                    let px = spongeStartX + i * spongeStep;
                    let py = spongeUncompressedY;

                    let distToFront = currentGlassX - px;
                    let distToBack = px - (currentGlassX + glassWidth);

                    if (px >= currentGlassX && px <= currentGlassX + glassWidth) {
                        // Fully compressed over glass
                        py = spongeCompressedY;
                    } else if (distToFront > 0 && distToFront < chamferDist) {
                        // Ramp down at front
                        let ratio = distToFront / chamferDist;
                        // Use a smoothstep curve for nicer deformation
                        ratio = ratio * ratio * (3 - 2 * ratio); 
                        py = spongeCompressedY + maxCompression * ratio;
                    } else if (distToBack > 0 && distToBack < chamferDist) {
                        // Ramp up at back
                        let ratio = distToBack / chamferDist;
                        ratio = ratio * ratio * (3 - 2 * ratio);
                        py = spongeCompressedY + maxCompression * ratio;
                    }

                    // Add slight random noise to uncompressed bottom to look like sponge
                    if (py === spongeUncompressedY) {
                         py += Math.sin(px * 0.5) * 0.5; 
                    }

                    bottomPoints.push(`${px} ${py}`);
                }

                path += ` L ${spongeEndX} ${spongeUncompressedY}`; // Right edge down
                path += ` L ${bottomPoints.join(' L ')}`; // The dynamic bottom edge
                path += ` Z`;
                
                sponge.setAttribute('d', path);
            }

            // Main Animation Loop
            function animate() {
                time++;
                
                // Kinematics logic
                glassX += baseVelocity;
                
                // Reset loop
                if (glassX > endX) {
                    glassX = -250;
                }

                // Center of glass for relative calculations
                let gCenterX = glassX + glassWidth / 2;

                // 1. Update Glass Position
                glassGroup.setAttribute('transform', `translate(${glassX}, 0)`);

                // 2. Update Sponge Deformation
                updateSpongePath(glassX);

                // 3. Rotate Pulleys and Move Belt Patterns
                // Calculate rotation based on circumference (r=40, C=251.2)
                let rotation = (glassX / 251.2) * 360;
                spokesBot1.setAttribute('transform', `translate(150, 450) rotate(${rotation})`);
                spokesBot2.setAttribute('transform', `translate(1050, 450) rotate(${rotation})`);
                // Top pulleys counter-rotate (or same if mapped directly, visual aesthetic)
                spokesTop1.setAttribute('transform', `translate(150, 150) rotate(${rotation})`);
                spokesTop2.setAttribute('transform', `translate(1050, 150) rotate(${rotation})`);
                
                // Move belt teeth pattern
                let beltOffset = -(glassX % 20); // Width of pattern is 20
                beltBotTeeth.setAttribute('x', 150 + beltOffset);
                beltTopTeeth.setAttribute('x', 150 + beltOffset);

                // 4. State Machine & Visual Directives (IFR focusing)
                
                // State: Entering (Funnel)
                if (gCenterX > 50 && gCenterX < 250) {
                    phaseStatus.innerText = "Phase 1: Infeed (Funnel)";
                    phaseStatus.style.color = "var(--text-main)";
                    funnelInd.setAttribute('opacity', (gCenterX - 50) / 100);
                    vTopDisplay.innerText = "1.500 m/s";
                    vBotDisplay.innerText = "1.500 m/s";
                    compDisplay.innerText = "1.2 mm";
                    frictionDisplay.innerText = "Engaging";
                    glassGlow.setAttribute('opacity', '0');
                    vectors.classList.add('hidden');
                    macroView.setAttribute('opacity', '0');
                } 
                // State: Fully Engaged (The Ideal Final Result)
                else if (gCenterX >= 250 && gCenterX <= 950) {
                    phaseStatus.innerText = "Phase 2: Sync Transport";
                    phaseStatus.style.color = "var(--accent-cyan)";
                    funnelInd.setAttribute('opacity', '0');
                    
                    // Telemetry update
                    vTopDisplay.innerText = "1.500 m/s";
                    vBotDisplay.innerText = "1.500 m/s";
                    compDisplay.innerText = "3.0 mm (Target)";
                    frictionDisplay.innerText = "Zero Relative";
                    frictionDisplay.style.color = "var(--accent-cyan)";

                    // Show vectors attached to glass
                    vectors.classList.remove('hidden');
                    vectors.setAttribute('transform', `translate(${glassX + 60}, 0)`);
                    
                    // Show macro zoom indicator at center
                    if (gCenterX > 400 && gCenterX < 800) {
                        macroView.setAttribute('opacity', '1');
                        macroView.setAttribute('transform', `translate(${gCenterX}, 0)`);
                    } else {
                        macroView.setAttribute('opacity', '0');
                    }

                    // Pulse glow to show clamping force
                    let glowIntensity = 0.5 + Math.sin(time * 0.1) * 0.2;
                    glassGlow.setAttribute('opacity', glowIntensity.toString());

                } 
                // State: Exiting
                else if (gCenterX > 950 && gCenterX < 1150) {
                    phaseStatus.innerText = "Phase 3: Outfeed";
                    phaseStatus.style.color = "var(--text-main)";
                    vectors.classList.add('hidden');
                    macroView.setAttribute('opacity', '0');
                    glassGlow.setAttribute('opacity', '0');
                    compDisplay.innerText = "Rebounding";
                }
                // Off-screen
                else {
                    phaseStatus.innerText = "System Standby";
                    phaseStatus.style.color = "var(--text-muted)";
                    vTopDisplay.innerText = "0.000 m/s";
                    vBotDisplay.innerText = "0.000 m/s";
                    compDisplay.innerText = "0.0 mm";
                    frictionDisplay.innerText = "None";
                    frictionDisplay.style.color = "var(--text-muted)";
                }

                requestAnimationFrame(animate);
            }

            // Initialize
            updateSpongePath(glassX);
            requestAnimationFrame(animate);
        });
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>顶置随动柔性同步带 - 零滑动物理原理可视化</title>
    <style>
        :root {
            --bg-color: #050b14;
            --grid-color: rgba(30, 58, 138, 0.2);
            --glass-color: #06b6d4;
            --glass-fill: rgba(6, 182, 212, 0.25);
            --sponge-color: #f59e0b;
            --sponge-fill: rgba(245, 158, 11, 0.65);
            --belt-dark: #1e293b;
            --belt-teeth: #334155;
            --gear-color: #475569;
            --vector-color: #ef4444;
            --text-muted: #64748b;
            --text-highlight: #10b981;
        }

        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            background-color: var(--bg-color);
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
            overflow: hidden;
            color: #fff;
        }

        .canvas-container {
            width: 100%;
            max-width: 1200px;
            aspect-ratio: 16/9;
            position: relative;
            box-shadow: 0 0 100px rgba(6, 182, 212, 0.05);
            border: 1px solid rgba(255, 255, 255, 0.05);
            background-image: 
                linear-gradient(var(--grid-color) 1px, transparent 1px),
                linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
            background-size: 20px 20px;
            background-position: center center;
            border-radius: 8px;
        }

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

        /* 边缘信息面板定位 */
        .info-panel {
            position: absolute;
            font-size: 11px;
            line-height: 1.6;
            color: var(--text-muted);
            pointer-events: none;
            backdrop-filter: blur(4px);
            background: rgba(5, 11, 20, 0.6);
            padding: 8px 12px;
            border: 1px solid rgba(255, 255, 255, 0.05);
            border-radius: 4px;
        }
        
        .info-top-left { top: 20px; left: 20px; }
        .info-top-right { top: 20px; right: 20px; text-align: right; }
        .info-bottom-left { bottom: 20px; left: 20px; }
        .info-bottom-right { bottom: 20px; right: 20px; text-align: right; }

        .highlight {
            color: var(--text-highlight);
            font-weight: bold;
        }
        
        .alert {
            color: var(--vector-color);
        }

        .cyan-text {
            color: var(--glass-color);
        }

        .amber-text {
            color: var(--sponge-color);
        }

        .title {
            font-size: 14px;
            color: #e2e8f0;
            margin-bottom: 4px;
            letter-spacing: 1px;
        }
    </style>
</head>
<body>

    <div class="canvas-container">
        <!-- 边缘数据面板 -->
        <div class="info-panel info-top-left">
            <div class="title">IFR 系统状态监控 (最终理想解)</div>
            <div>运转模式: 汉堡包式夹击同步传输</div>
            <div>摩擦状态: <span class="highlight">绝对相对静止 (ΔV ≈ 0)</span></div>
        </div>

        <div class="info-panel info-top-right">
            <div class="title">动态参数遥测</div>
            <div>V_top (顶部海绵带): <span class="highlight">200.00 mm/s</span></div>
            <div>V_bot (底部主控带): <span class="highlight">200.00 mm/s</span></div>
            <div>速度同步偏差: <span class="highlight alert">< 0.01 mm/s</span></div>
        </div>

        <div class="info-panel info-bottom-left">
            <div class="title">构架级配置参数</div>
            <div>柔性面料: 聚氨酯(PU)高回弹海绵 <span class="amber-text">[15-20mm厚度]</span></div>
            <div>形变吸收: 吸收2-3mm厚度公差与机械震动</div>
        </div>

        <div class="info-panel info-bottom-right">
            <div class="title">表面保护机理</div>
            <div>面压分布: 柔性面积广阔 <span class="cyan-text">[接触应力降至最低]</span></div>
            <div>破坏因子消除: 滚动摩擦=0, 滑动擦伤=0</div>
        </div>

        <!-- 核心SVG可视化容器 -->
        <svg id="main-svg" viewBox="0 0 1000 562.5" preserveAspectRatio="xMidYMid meet">
            <defs>
                <!-- 辉光滤镜 -->
                <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="4" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="glow-strong" x="-50%" y="-50%" width="200%" height="200%">
                    <feGaussianBlur stdDeviation="6" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>

                <!-- 玻璃材质渐变 -->
                <linearGradient id="glass-grad" x1="0%" y1="0%" x2="100%" y2="100%">
                    <stop offset="0%" stop-color="rgba(6, 182, 212, 0.4)" />
                    <stop offset="50%" stop-color="rgba(6, 182, 212, 0.1)" />
                    <stop offset="100%" stop-color="rgba(6, 182, 212, 0.5)" />
                </linearGradient>

                <!-- 海绵PU材质纹理 -->
                <pattern id="sponge-pattern" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
                    <circle cx="4" cy="4" r="1.5" fill="#d97706" opacity="0.4" />
                </pattern>

                <!-- 齿轮齿定义 -->
                <g id="gear-teeth">
                    <path d="M -3,-25 L 3,-25 L 4,-20 L -4,-20 Z" fill="var(--gear-color)" />
                    <!-- JS将在此旋转复制生成完整齿轮 -->
                </g>

                <!-- 箭头标记 -->
                <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                    <path d="M 0,1 L 8,5 L 0,9 Z" fill="var(--vector-color)" />
                </marker>
            </defs>

            <!-- 静态机械支架/背景元素 -->
            <g id="structure" opacity="0.3">
                <rect x="150" y="210" width="700" height="15" fill="#334155" rx="4" />
                <rect x="150" y="335" width="700" height="15" fill="#334155" rx="4" />
                <!-- 支撑柱 -->
                <rect x="180" y="50" width="10" height="160" fill="#475569" />
                <rect x="810" y="50" width="10" height="160" fill="#475569" />
                <rect x="180" y="350" width="10" height="150" fill="#475569" />
                <rect x="810" y="350" width="10" height="150" fill="#475569" />
            </g>

            <!-- 底部主控同步带组 -->
            <g id="bottom-system">
                <!-- 底部刚性导轨 -->
                <rect x="150" y="320" width="700" height="15" fill="var(--belt-dark)" rx="7.5" />
                <!-- 底部皮带运动层 (虚线模拟表面纹理) -->
                <path id="bottom-belt-surface" d="M 150 320 L 850 320 M 850 335 L 150 335" 
                      stroke="var(--belt-teeth)" stroke-width="4" stroke-dasharray="10 5" fill="none" />
                
                <!-- 底部传动轮 -->
                <g id="gear-bot-left" transform="translate(150, 327.5)">
                    <circle r="18" fill="var(--gear-color)" />
                    <circle r="8" fill="#1e293b" />
                </g>
                <g id="gear-bot-right" transform="translate(850, 327.5)">
                    <circle r="18" fill="var(--gear-color)" />
                    <circle r="8" fill="#1e293b" />
                </g>
            </g>

            <!-- 顶部PU海绵同步带组 -->
            <g id="top-system">
                <!-- 顶部刚性基座带 -->
                <rect x="150" y="225" width="700" height="15" fill="var(--belt-dark)" rx="7.5" />
                <!-- 顶部皮带运动层 -->
                <path id="top-belt-surface" d="M 150 225 L 850 225 M 850 240 L 150 240" 
                      stroke="var(--belt-teeth)" stroke-width="4" stroke-dasharray="10 5" fill="none" />
                
                <!-- 高回弹PU海绵层 (由JS动态计算形变路径) -->
                <!-- 默认厚度 20mm (Y: 240 -> 260) -->
                <path id="sponge-layer" d="" fill="var(--sponge-fill)" stroke="var(--sponge-color)" stroke-width="1" />
                <!-- 覆盖纹理 -->
                <path id="sponge-layer-pattern" d="" fill="url(#sponge-pattern)" style="pointer-events: none;" />

                <!-- 顶部传动轮 -->
                <g id="gear-top-left" transform="translate(150, 232.5)">
                    <circle r="18" fill="var(--gear-color)" />
                    <circle r="8" fill="#1e293b" />
                </g>
                <g id="gear-top-right" transform="translate(850, 232.5)">
                    <circle r="18" fill="var(--gear-color)" />
                    <circle r="8" fill="#1e293b" />
                </g>
            </g>

            <!-- 工作载荷:玻璃面板 -->
            <!-- 玻璃基准 Y:300(顶部),320(底部。紧贴底部皮带) -->
            <g id="glass-sheet" transform="translate(-300, 0)">
                <rect x="0" y="300" width="240" height="20" fill="url(#glass-grad)" stroke="var(--glass-color)" stroke-width="2" filter="url(#glow)" />
                <line x1="10" y1="305" x2="230" y2="305" stroke="#fff" stroke-width="1" opacity="0.5" />
                <line x1="10" y1="315" x2="230" y2="315" stroke="#fff" stroke-width="1" opacity="0.2" />
                <text x="120" y="314" fill="#fff" font-size="12" text-anchor="middle" font-weight="bold" opacity="0.9">精密玻璃</text>
            </g>

            <!-- 动态分析覆层 (当玻璃进入中心区域时显示) -->
            <g id="analysis-overlay" opacity="0">
                <!-- 速度矢量分析 -->
                <g id="vector-group" transform="translate(0, 0)">
                    <!-- 顶部矢量 -->
                    <line id="vec-top" x1="0" y1="285" x2="80" y2="285" stroke="var(--vector-color)" stroke-width="3" marker-end="url(#arrow)" filter="url(#glow-strong)"/>
                    <!-- 底部矢量 -->
                    <line id="vec-bot" x1="0" y1="335" x2="80" y2="335" stroke="var(--vector-color)" stroke-width="3" marker-end="url(#arrow)" filter="url(#glow-strong)"/>
                    
                    <!-- 同步连线与文字 -->
                    <line x1="0" y1="285" x2="0" y2="335" stroke="var(--vector-color)" stroke-width="1" stroke-dasharray="3 3" opacity="0.6"/>
                    <rect x="-40" y="302" width="100" height="16" fill="rgba(5, 11, 20, 0.8)" rx="2"/>
                    <text x="10" y="314" fill="var(--text-highlight)" font-size="10" text-anchor="middle" font-weight="bold">v1 = v2</text>
                </g>

                <!-- 海绵压缩量标注 -->
                <g id="compression-mark" transform="translate(0, 0)">
                    <line x1="-10" y1="260" x2="20" y2="260" stroke="var(--sponge-color)" stroke-width="1" />
                    <line x1="-10" y1="300" x2="20" y2="300" stroke="var(--sponge-color)" stroke-width="1" />
                    <line x1="5" y1="260" x2="5" y2="300" stroke="var(--sponge-color)" stroke-width="1" marker-start="url(#arrow)" marker-end="url(#arrow)" />
                    <rect x="15" y="272" width="60" height="16" fill="rgba(5, 11, 20, 0.8)" rx="2"/>
                    <text x="45" y="284" fill="var(--sponge-color)" font-size="10" text-anchor="middle">压缩: 3mm</text>
                </g>
            </g>

        </svg>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // 初始化齿轮绘制
            const gearTeethCount = 12;
            const gearBase = document.getElementById('gear-teeth');
            const pathNode = gearBase.querySelector('path');
            for(let i=1; i<gearTeethCount; i++) {
                const clone = pathNode.cloneNode();
                clone.setAttribute('transform', `rotate(${i * (360/gearTeethCount)})`);
                gearBase.appendChild(clone);
            }

            // 获取DOM元素
            const bottomBeltSurf = document.getElementById('bottom-belt-surface');
            const topBeltSurf = document.getElementById('top-belt-surface');
            const spongeLayer = document.getElementById('sponge-layer');
            const spongeLayerPat = document.getElementById('sponge-layer-pattern');
            const glassSheet = document.getElementById('glass-sheet');
            
            const gearTL = document.getElementById('gear-top-left');
            const gearTR = document.getElementById('gear-top-right');
            const gearBL = document.getElementById('gear-bot-left');
            const gearBR = document.getElementById('gear-bot-right');

            const analysisOverlay = document.getElementById('analysis-overlay');
            const vectorGroup = document.getElementById('vector-group');
            const compressionMark = document.getElementById('compression-mark');

            // 物理常量配置
            const duration = 7000; // 一个完整的动画周期(ms)
            const vSpeed = 200; // 模拟速度 px/s
            const pulleyRadius = 18;
            const circumference = 2 * Math.PI * pulleyRadius; // ~113.1px
            const omega = (vSpeed / circumference) * 360; // deg/s
            
            const spongeRestingY = 260; // 海绵未压缩时的底边Y
            const spongeBaseY = 240; // 海绵固定在皮带上的顶边Y
            const spongeChamferWidth = 40; // 喇叭状倒角宽度
            
            const glassWidth = 240;
            const glassTopY = 300; // 玻璃顶边Y
            const gapY = glassTopY - 250; // 需要压缩的深度计算
            // 修正:此时海绵需压缩至Y=300,原resting可以设为Y=305(产生5mm虚拟压缩感以增强视觉)
            const visualRestingY = 305;
            const visualCompressedY = 300;

            let isRunning = true;

            // 核心渲染循环
            function animate(time) {
                if(!isRunning) return;

                // 1. 计算系统基础运转动画 (永动)
                const dashOffset = -(time / 1000) * vSpeed;
                bottomBeltSurf.setAttribute('stroke-dashoffset', dashOffset);
                topBeltSurf.setAttribute('stroke-dashoffset', dashOffset);

                const currentRotation = (time / 1000) * omega;
                gearTL.setAttribute('transform', `translate(150, 232.5) rotate(${currentRotation})`);
                gearTR.setAttribute('transform', `translate(850, 232.5) rotate(${currentRotation})`);
                gearBL.setAttribute('transform', `translate(150, 327.5) rotate(${currentRotation})`);
                gearBR.setAttribute('transform', `translate(850, 327.5) rotate(${currentRotation})`);

                // 2. 计算玻璃的相对位置
                const t = (time % duration) / duration;
                // 让玻璃从左侧场外(-300) 移动到右侧场外(1100)
                const glassX = -350 + t * 1600; 
                glassSheet.setAttribute('transform', `translate(${glassX}, 0)`);

                // 3. 计算PU海绵的动态形变路径 (核心破除矛盾与IFR体现)
                let dPath = `M 150 ${spongeBaseY} `;
                
                // 遍历构建底部轮廓
                for(let x = 150; x <= 850; x += 10) {
                    // 默认深度 (包含喇叭口倒角逻辑)
                    let currentDepth = visualRestingY;
                    if(x < 150 + spongeChamferWidth) {
                        currentDepth = spongeBaseY + (visualRestingY - spongeBaseY) * ((x - 150) / spongeChamferWidth);
                    } else if (x > 850 - spongeChamferWidth) {
                        currentDepth = spongeBaseY + (visualRestingY - spongeBaseY) * ((850 - x) / spongeChamferWidth);
                    }

                    // 计算与玻璃的碰撞检测及柔性挤压
                    const gLeft = glassX;
                    const gRight = glassX + glassWidth;
                    const compressionRamp = 30; // 海绵响应玻璃边缘的渐变宽度

                    if (x >= gLeft && x <= gRight) {
                        currentDepth = Math.min(currentDepth, visualCompressedY);
                    } else if (x < gLeft && x >= gLeft - compressionRamp) {
                        let ratio = (gLeft - x) / compressionRamp;
                        let ease = 1 - ratio * ratio; // 简单缓动曲线平滑过渡
                        let compY = visualCompressedY + (visualRestingY - visualCompressedY) * ease;
                        currentDepth = Math.min(currentDepth, compY);
                    } else if (x > gRight && x <= gRight + compressionRamp) {
                        let ratio = (x - gRight) / compressionRamp;
                        let ease = 1 - ratio * ratio;
                        let compY = visualCompressedY + (visualRestingY - visualCompressedY) * ease;
                        currentDepth = Math.min(currentDepth, compY);
                    }

                    dPath += `L ${x} ${currentDepth} `;
                }

                // 闭合海绵路径
                dPath += `L 850 ${spongeBaseY} Z`;
                spongeLayer.setAttribute('d', dPath);
                spongeLayerPat.setAttribute('d', dPath);

                // 4. 分析覆层 (视觉引导 IFR)
                // 当玻璃完全进入有效区域(X在200到600之间)时,显示同步矢量和微观数据
                if (glassX > 250 && glassX < 550) {
                    analysisOverlay.setAttribute('opacity', '1');
                    
                    // 让矢量组跟随玻璃运动中心
                    const centerPoint = glassX + glassWidth / 2;
                    vectorGroup.setAttribute('transform', `translate(${centerPoint - 40}, 0)`);
                    
                    // 让形变标注跟随玻璃前沿
                    compressionMark.setAttribute('transform', `translate(${glassX + 60}, 0)`);
                } else {
                    // 柔和淡出可使用CSS transition,这里用简单阈值
                    analysisOverlay.setAttribute('opacity', '0');
                }

                requestAnimationFrame(animate);
            }

            // 启动动画循环
            requestAnimationFrame(animate);
            
            // 确保页面切换或iframe重载时的稳定性
            window.addEventListener('focus', () => {
                if(!isRunning) {
                    isRunning = true;
                    requestAnimationFrame(animate);
                }
            });
        });
    </script>
</body>
</html>
<!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>
        @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@300;500;700&display=swap');

        :root {
            /* 赛博工业风配色方案 (Cyber-Industrial) */
            --bg-base: #05070a;
            --bg-grid: rgba(26, 36, 54, 0.6);
            --hud-bg: rgba(8, 12, 18, 0.85);
            --hud-border: rgba(0, 255, 157, 0.2);
            --glass-cyan: #00e5ff;
            --sponge-amber: #ffaa00;
            --sync-green: #00ff9d;
            --mech-dark: #121926;
            --mech-light: #334155;
            --text-main: #e2e8f0;
            --text-dim: #64748b;
        }

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

        body {
            background-color: var(--bg-base);
            color: var(--text-main);
            font-family: 'Noto Sans SC', sans-serif;
            width: 100vw;
            height: 100vh;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
            background-image: 
                linear-gradient(var(--bg-grid) 1px, transparent 1px),
                linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
            background-size: 50px 50px;
            background-position: center;
        }

        /* 动画主容器,保证巨大且居中 */
        #sim-container {
            width: 100%;
            height: 100%;
            position: relative;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        svg {
            width: 100%;
            height: 100%;
            max-width: 1800px;
            max-height: 1000px;
            filter: drop-shadow(0 0 30px rgba(0,0,0,0.8));
        }

        /* 边缘HUD信息面板 - 严格控制字号和位置,绝不遮挡中心 */
        .hud-panel {
            position: absolute;
            background: var(--hud-bg);
            border: 1px solid var(--hud-border);
            backdrop-filter: blur(10px);
            padding: 16px;
            border-radius: 6px;
            pointer-events: auto; /* 允许交互 */
            z-index: 10;
            min-width: 260px;
        }

        .hud-top-left { top: 24px; left: 24px; border-top: 3px solid var(--glass-cyan); }
        .hud-top-right { top: 24px; right: 24px; border-top: 3px solid var(--sync-green); }
        .hud-bottom-center { bottom: 24px; left: 50%; transform: translateX(-50%); border-bottom: 3px solid var(--sponge-amber); display: flex; gap: 24px;}

        .hud-title {
            font-size: 13px;
            font-weight: 700;
            color: #fff;
            margin-bottom: 6px;
            letter-spacing: 1px;
            text-transform: uppercase;
        }

        .hud-subtitle {
            font-family: 'JetBrains Mono', monospace;
            font-size: 10px;
            color: var(--sync-green);
            margin-bottom: 12px;
            opacity: 0.8;
        }

        .hud-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-size: 12px;
            padding: 6px 0;
            border-bottom: 1px solid rgba(255,255,255,0.05);
        }
        
        .hud-row:last-child { border-bottom: none; }
        .hud-label { color: var(--text-dim); }
        .hud-value { font-family: 'JetBrains Mono', monospace; font-weight: 700; }
        
        .val-cyan { color: var(--glass-cyan); }
        .val-green { color: var(--sync-green); }
        .val-amber { color: var(--sponge-amber); }

        /* 交互控件 */
        .control-group {
            display: flex;
            flex-direction: column;
            gap: 6px;
            min-width: 200px;
        }
        .control-header {
            display: flex;
            justify-content: space-between;
            font-size: 11px;
            color: var(--text-dim);
        }
        input[type=range] {
            -webkit-appearance: none;
            width: 100%;
            background: rgba(255,255,255,0.1);
            height: 4px;
            border-radius: 2px;
            outline: none;
        }
        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: var(--sync-green);
            cursor: pointer;
            box-shadow: 0 0 10px var(--sync-green);
        }

        /* SVG 内联文本样式 */
        .svg-text { font-family: 'Noto Sans SC', sans-serif; font-size: 12px; fill: var(--text-dim); }
        .svg-mono { font-family: 'JetBrains Mono', monospace; font-size: 10px; }
    </style>
</head>
<body>

    <div id="sim-container">
        <!-- 左上:TRIZ 理想解概念 -->
        <div class="hud-panel hud-top-left">
            <div class="hud-title">最终理想解 (IFR) 架构</div>
            <div class="hud-subtitle">TRIZ: 消除系统矛盾的最终状态</div>
            <p style="font-size: 11px; color: var(--text-dim); line-height: 1.6; margin-bottom: 8px;">
                突破传统压轮的局部线接触摩擦,<br>
                构建<strong>“绝对相对静止”</strong>的汉堡包式面夹击。<br>
                巧妙利用高回弹PU海绵进行微观形变补偿,<br>
                在极低系统复杂度下彻底消除滑动擦伤。
            </p>
            <div class="hud-row">
                <span class="hud-label">矛盾参数:</span>
                <span class="hud-value" style="color:#ff4444;">表面损伤 vs 压紧力</span>
            </div>
            <div class="hud-row">
                <span class="hud-label">IFR状态:</span>
                <span class="hud-value val-green">接触点 ΔV 恒等于 0</span>
            </div>
        </div>

        <!-- 右上:遥测数据面板 -->
        <div class="hud-panel hud-top-right">
            <div class="hud-title">系统实时遥测 (Telemetry)</div>
            <div class="hud-subtitle">SYNC VECTOR MONITORING</div>
            <div class="hud-row">
                <span class="hud-label">底部主轴线速度 (V1)</span>
                <span class="hud-value val-cyan" id="disp-v1">1.500 m/s</span>
            </div>
            <div class="hud-row">
                <span class="hud-label">顶部伺服线速度 (V2)</span>
                <span class="hud-value val-cyan" id="disp-v2">1.500 m/s</span>
            </div>
            <div class="hud-row">
                <span class="hud-label">上下皮带速度差 (|V1-V2|)</span>
                <span class="hud-value val-green">< 0.01 mm/s</span>
            </div>
            <div class="hud-row">
                <span class="hud-label">瞬时滚动摩擦力</span>
                <span class="hud-value val-green">0.00 N</span>
            </div>
        </div>

        <!-- 底部:交互控制区 -->
        <div class="hud-panel hud-bottom-center">
            <div class="control-group">
                <div class="control-header">
                    <span>主控线速度倍率</span>
                    <span id="disp-speed-val" class="val-green">1.0x</span>
                </div>
                <input type="range" id="ctrl-speed" min="0.2" max="3.0" step="0.1" value="1.0">
            </div>
            <div class="control-group">
                <div class="control-header">
                    <span>PU 海绵压缩量模拟</span>
                    <span id="disp-comp-val" class="val-amber">3.0 mm</span>
                </div>
                <input type="range" id="ctrl-comp" min="1.0" max="8.0" step="0.5" value="3.0">
            </div>
        </div>

        <!-- 核心视区 (高保真SVG) -->
        <svg viewBox="0 0 1600 800" preserveAspectRatio="xMidYMid meet">
            <defs>
                <!-- 材质渐变 -->
                <linearGradient id="grad-glass" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="rgba(0, 229, 255, 0.4)" />
                    <stop offset="50%" stop-color="rgba(0, 229, 255, 0.05)" />
                    <stop offset="100%" stop-color="rgba(0, 229, 255, 0.2)" />
                </linearGradient>
                <linearGradient id="grad-belt" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#475569" />
                    <stop offset="50%" stop-color="#1e293b" />
                    <stop offset="100%" stop-color="#0f172a" />
                </linearGradient>

                <!-- PU海绵微观纹理 (网格+噪点感) -->
                <pattern id="pat-sponge" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(15)">
                    <circle cx="4" cy="4" r="2" fill="#d97706" opacity="0.4" />
                    <path d="M0,0 L8,8 M8,0 L0,8" stroke="#b45309" stroke-width="0.5" opacity="0.3"/>
                </pattern>
                
                <!-- 齿形带纹理 -->
                <pattern id="pat-teeth" width="24" height="12" patternUnits="userSpaceOnUse">
                    <rect x="0" y="0" width="12" height="12" fill="#0f172a" />
                    <rect x="12" y="0" width="12" height="12" fill="#334155" />
                </pattern>

                <!-- 光效滤镜 -->
                <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="6" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="glow-green" x="-30%" y="-30%" width="160%" height="160%">
                    <feGaussianBlur stdDeviation="8" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>

                <!-- 标记箭头 -->
                <marker id="arrow-green" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto">
                    <path d="M0,1 L10,5 L0,9 Z" fill="var(--sync-green)" />
                </marker>
                <marker id="arrow-amber" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
                    <path d="M0,2 L8,5 L0,8 Z" fill="var(--sponge-amber)" />
                </marker>
            </defs>

            <!-- 坐标辅助线 (背景层) -->
            <g stroke="rgba(255,255,255,0.05)" stroke-width="1">
                <line x1="200" y1="0" x2="200" y2="800" stroke-dasharray="4 8"/>
                <line x1="1400" y1="0" x2="1400" y2="800" stroke-dasharray="4 8"/>
                <line x1="0" y1="400" x2="1600" y2="400" stroke-dasharray="10 10"/>
                <text x="210" y="30" class="svg-mono">X:200.0 (IN-FEED ZONE)</text>
                <text x="1410" y="30" class="svg-mono">X:1400.0 (OUT-FEED ZONE)</text>
                <text x="10" y="390" class="svg-mono">Y:400.0 (SYNC AXIS)</text>
            </g>

            <!-- 底层机构:物理耦合/独立伺服轴线 -->
            <g opacity="0.2">
                <path d="M 250 200 L 250 600" stroke="var(--sync-green)" stroke-width="3" stroke-dasharray="10 5" />
                <path d="M 1350 200 L 1350 600" stroke="var(--sync-green)" stroke-width="3" stroke-dasharray="10 5" />
            </g>

            <!-- =================底部主传输同步带================= -->
            <g id="mech-bottom">
                <!-- 支撑架 -->
                <rect x="250" y="520" width="1100" height="20" fill="var(--mech-dark)" rx="10" />
                <!-- 传动轮 -->
                <circle cx="250" cy="530" r="50" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="6" />
                <circle cx="1350" cy="530" r="50" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="6" />
                <circle cx="250" cy="530" r="12" fill="var(--glass-cyan)" />
                
                <!-- 轮辐 (用于旋转动画) -->
                <g id="wheel-bot-L" transform="translate(250,530)">
                    <line x1="-35" y1="0" x2="35" y2="0" stroke="var(--mech-light)" stroke-width="6"/>
                    <line x1="0" y1="-35" x2="0" y2="35" stroke="var(--mech-light)" stroke-width="6"/>
                </g>
                <g id="wheel-bot-R" transform="translate(1350,530)">
                    <line x1="-35" y1="0" x2="35" y2="0" stroke="var(--mech-light)" stroke-width="6"/>
                    <line x1="0" y1="-35" x2="0" y2="35" stroke="var(--mech-light)" stroke-width="6"/>
                </g>

                <!-- 皮带本体 -->
                <!-- 上表面 Y = 530 - 50 = 480 -->
                <path d="M 250 480 L 1350 480 A 50 50 0 0 1 1400 530 A 50 50 0 0 1 1350 580 L 250 580 A 50 50 0 0 1 200 530 A 50 50 0 0 1 250 480 Z" fill="none" stroke="url(#grad-belt)" stroke-width="16" />
                
                <!-- 运动齿纹 -->
                <rect id="belt-teeth-bot" x="250" y="472" width="1100" height="16" fill="url(#pat-teeth)" />
                
                <text x="800" y="620" class="svg-text" text-anchor="middle" fill="var(--text-dim)">底部刚性主同步带 (V1)</text>
            </g>

            <!-- =================顶部柔性跟随同步带================= -->
            <g id="mech-top">
                <!-- 支撑架 -->
                <rect x="250" y="260" width="1100" height="20" fill="var(--mech-dark)" rx="10" />
                <!-- 传动轮 -->
                <circle cx="250" cy="270" r="50" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="6" />
                <circle cx="1350" cy="270" r="50" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="6" />
                <circle cx="1350" cy="270" r="12" fill="var(--sync-green)" /> <!-- 独立伺服标识 -->
                
                <!-- 轮辐 -->
                <g id="wheel-top-L" transform="translate(250,270)">
                    <line x1="-35" y1="0" x2="35" y2="0" stroke="var(--mech-light)" stroke-width="6"/>
                    <line x1="0" y1="-35" x2="0" y2="35" stroke="var(--mech-light)" stroke-width="6"/>
                </g>
                <g id="wheel-top-R" transform="translate(1350,270)">
                    <line x1="-35" y1="0" x2="35" y2="0" stroke="var(--mech-light)" stroke-width="6"/>
                    <line x1="0" y1="-35" x2="0" y2="35" stroke="var(--mech-light)" stroke-width="6"/>
                </g>

                <!-- 顶部刚性皮带本体 -->
                <!-- 下表面 Y = 270 + 50 = 320 -->
                <path d="M 250 220 L 1350 220 A 50 50 0 0 1 1400 270 A 50 50 0 0 1 1350 320 L 250 320 A 50 50 0 0 1 200 270 A 50 50 0 0 1 250 220 Z" fill="none" stroke="url(#grad-belt)" stroke-width="16" />
                
                <rect id="belt-teeth-top" x="250" y="312" width="1100" height="16" fill="url(#pat-teeth)" />

                <!-- 高回弹 PU 海绵层 (动态路径绘制) -->
                <!-- 基准顶边: 320, 基准底边(未压缩): 380 (代表厚度60像素,约20mm比例) -->
                <path id="sponge-path" d="" fill="url(#pat-sponge)" stroke="var(--sponge-amber)" stroke-width="1.5" opacity="0.9" />
                
                <text x="800" y="200" class="svg-text" text-anchor="middle" fill="var(--text-dim)">顶置伺服/物理耦合同步带 (V2)</text>
                <text x="800" y="350" class="svg-text" text-anchor="middle" fill="var(--sponge-amber)" font-weight="700">高回弹聚氨酯(PU)柔性层 15~20mm</text>
            </g>

            <!-- =================负载实体:精密玻璃================= -->
            <g id="load-glass">
                <!-- 玻璃本体,置于底层皮带表面 Y=480 上 -->
                <!-- 高度 24px,所以顶面 Y = 456 -->
                <!-- 注意:为了表现压缩,PU海绵未压缩底面设为 470,玻璃顶面设为 456,形成 14px 视觉压缩量 -->
                <!-- 发光底层代表压紧受力状态 -->
                <rect id="glass-glow" x="0" y="456" width="400" height="24" fill="var(--glass-cyan)" opacity="0" filter="url(#glow-cyan)" />
                <rect id="glass-body" x="0" y="456" width="400" height="24" fill="url(#grad-glass)" stroke="rgba(0, 229, 255, 0.8)" stroke-width="2" />
                <line x1="20" y1="462" x2="380" y2="462" stroke="#fff" stroke-width="1" opacity="0.4" />
                <text x="200" y="473" class="svg-text" fill="#fff" text-anchor="middle" dominant-baseline="middle" font-weight="700" letter-spacing="2">精密玻璃基板</text>

                <!-- 相对速度矢量线 (绑定在玻璃上移动,突出零滑动) -->
                <g id="vector-arrows" opacity="0">
                    <!-- 上方同步矢量 -->
                    <line x1="200" y1="440" x2="300" y2="440" stroke="var(--sync-green)" stroke-width="3" marker-end="url(#arrow-green)" filter="url(#glow-green)"/>
                    <text x="210" y="432" class="svg-mono" fill="var(--sync-green)" font-weight="700">V_top</text>
                    
                    <!-- 下方同步矢量 -->
                    <line x1="200" y1="496" x2="300" y2="496" stroke="var(--sync-green)" stroke-width="3" marker-end="url(#arrow-green)" filter="url(#glow-green)"/>
                    <text x="210" y="512" class="svg-mono" fill="var(--sync-green)" font-weight="700">V_bot</text>

                    <!-- 绝对静止连线 -->
                    <line x1="200" y1="440" x2="200" y2="496" stroke="var(--sync-green)" stroke-width="2" stroke-dasharray="4 4"/>
                    <rect x="230" y="458" width="80" height="20" fill="var(--bg-base)" rx="4" stroke="var(--sync-green)" stroke-width="1"/>
                    <text x="270" y="472" class="svg-mono" fill="var(--sync-green)" text-anchor="middle" font-weight="bold">ΔV = 0</text>
                </g>

                <!-- 压缩量标注 -->
                <g id="comp-annotation" opacity="0">
                    <line x1="-30" y1="456" x2="0" y2="456" stroke="var(--sponge-amber)" stroke-width="1" stroke-dasharray="2 2"/>
                    <line x1="-30" y1="470" x2="0" y2="470" stroke="var(--sponge-amber)" stroke-width="1" stroke-dasharray="2 2"/>
                    <line x1="-15" y1="456" x2="-15" y2="470" stroke="var(--sponge-amber)" stroke-width="2" marker-start="url(#arrow-amber)" marker-end="url(#arrow-amber)"/>
                    <text x="-35" y="468" class="svg-mono" fill="var(--sponge-amber)" text-anchor="end" id="comp-text">3mm压缩</text>
                </g>
            </g>

            <!-- 喇叭状倒角入口指示 (静态,高亮区域) -->
            <g id="funnel-indicator" opacity="0.6">
                <path d="M 180 390 Q 250 400 280 430" fill="none" stroke="var(--text-dim)" stroke-width="2" stroke-dasharray="5 5" marker-end="url(#arrow-amber)" />
                <text x="180" y="380" class="svg-text" text-anchor="middle">帧1:喇叭状倒角引导入口</text>
            </g>

        </svg>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // == DOM 元素获取 ==
            const glassGroup = document.getElementById('load-glass');
            const glassGlow = document.getElementById('glass-glow');
            const spongePath = document.getElementById('sponge-path');
            const beltTeethTop = document.getElementById('belt-teeth-top');
            const beltTeethBot = document.getElementById('belt-teeth-bot');
            
            const wTopL = document.getElementById('wheel-top-L');
            const wTopR = document.getElementById('wheel-top-R');
            const wBotL = document.getElementById('wheel-bot-L');
            const wBotR = document.getElementById('wheel-bot-R');
            
            const vectorArrows = document.getElementById('vector-arrows');
            const compAnnotation = document.getElementById('comp-annotation');
            const funnelIndicator = document.getElementById('funnel-indicator');
            const compText = document.getElementById('comp-text');

            // 控件与遥测面板
            const ctrlSpeed = document.getElementById('ctrl-speed');
            const ctrlComp = document.getElementById('ctrl-comp');
            const dispSpeedVal = document.getElementById('disp-speed-val');
            const dispCompVal = document.getElementById('disp-comp-val');
            
            const dispV1 = document.getElementById('disp-v1');
            const dispV2 = document.getElementById('disp-v2');

            // == 系统物理常量 ==
            const simWidth = 1600;
            const glassWidth = 400;
            
            const topBeltY = 320;        // 刚性皮带底边
            const uncompressedY = 470;   // 海绵未压缩状态底边
            const glassTopY = 456;       // 玻璃表面Y (底层皮带 Y480 - 玻璃厚度24)
            
            const spongeStartX = 250;
            const spongeEndX = 1350;
            const wheelRadius = 50;
            const circumference = 2 * Math.PI * wheelRadius;

            // == 状态变量 ==
            let glassX = -500;
            let baseVelocity = 4; // px/frame
            let currentCompression = parseFloat(ctrlComp.value);
            
            // 控件监听
            ctrlSpeed.addEventListener('input', (e) => {
                dispSpeedVal.innerText = parseFloat(e.target.value).toFixed(1) + 'x';
            });
            ctrlComp.addEventListener('input', (e) => {
                currentCompression = parseFloat(e.target.value);
                dispCompVal.innerText = currentCompression.toFixed(1) + ' mm';
                compText.innerText = currentCompression.toFixed(1) + 'mm 压缩';
            });

            // == 核心计算函数 ==
            
            // 动态生成 PU 海绵的形变路径
            function updateSpongeDeformation(gX) {
                // 视觉映射:滑块3mm对应实际14px的视觉压缩(470-456=14)。
                // 这里进行比例转换,让滑块动态改变视觉压缩深度。
                // 假设 max compression = 8mm -> 24px, 1mm -> 3px.
                const visualCompression = currentCompression * 3;
                const dynamicUncompressedY = glassTopY + visualCompression; // 抬高未压缩底边以适应滑块

                let d = `M ${spongeStartX} ${topBeltY} `;
                
                const steps = 100;
                const stepWidth = (spongeEndX - spongeStartX) / steps;
                
                // 下边缘生成
                let bottomPoints = [];
                for(let i=0; i<=steps; i++) {
                    let px = spongeStartX + i * stepWidth;
                    let py = dynamicUncompressedY;

                    // 1. 进出料两端的喇叭状倒角 (自然缓和形变)
                    const chamferLen = 80;
                    if (px < spongeStartX + chamferLen) {
                        let ratio = (px - spongeStartX) / chamferLen;
                        py = topBeltY + (dynamicUncompressedY - topBeltY) * Math.sin(ratio * Math.PI / 2);
                    } else if (px > spongeEndX - chamferLen) {
                        let ratio = (spongeEndX - px) / chamferLen;
                        py = topBeltY + (dynamicUncompressedY - topBeltY) * Math.sin(ratio * Math.PI / 2);
                    }

                    // 2. 玻璃挤压逻辑 (核心破矛盾机制)
                    const gLeft = gX;
                    const gRight = gX + glassWidth;
                    const ramp = 40; // 海绵形变的平滑坡度
                    
                    if (px >= gLeft && px <= gRight) {
                        // 绝对面接触,压平到玻璃表面
                        py = Math.min(py, glassTopY);
                    } else if (px >= gLeft - ramp && px < gLeft) {
                        // 前端挤压坡度
                        let ratio = (px - (gLeft - ramp)) / ramp;
                        // Smoothstep ease
                        ratio = ratio * ratio * (3 - 2 * ratio);
                        let targetY = dynamicUncompressedY - (dynamicUncompressedY - glassTopY) * ratio;
                        py = Math.min(py, targetY);
                    } else if (px > gRight && px <= gRight + ramp) {
                        // 后端回弹坡度
                        let ratio = ((gRight + ramp) - px) / ramp;
                        ratio = ratio * ratio * (3 - 2 * ratio);
                        let targetY = dynamicUncompressedY - (dynamicUncompressedY - glassTopY) * ratio;
                        py = Math.min(py, targetY);
                    }

                    bottomPoints.push(`${px},${py}`);
                }

                d += `L ${spongeEndX} ${topBeltY} `;
                // 反向连接下边缘形成闭合多边形
                for(let i = bottomPoints.length - 1; i >= 0; i--) {
                    d += `L ${bottomPoints[i]} `;
                }
                d += "Z";
                
                spongePath.setAttribute('d', d);
            }

            // == 主动画循环 ==
            let lastTime = 0;
            function animate(time) {
                // 帧率独立化计算基础
                const deltaTime = time - lastTime;
                lastTime = time;
                
                // 应用速度倍率
                let speedMult = parseFloat(ctrlSpeed.value);
                let currentVel = baseVelocity * speedMult;
                
                // 推进玻璃位置
                glassX += currentVel;
                if(glassX > simWidth + 200) {
                    glassX = -600; // 循环
                }

                const gCenterX = glassX + glassWidth / 2;

                // 1. 移动载荷
                glassGroup.setAttribute('transform', `translate(${glassX}, 0)`);

                // 2. 更新海绵形变(IFR核心展示)
                updateSpongeDeformation(glassX);

                // 3. 同步运动机构 (车轮和皮带纹理)
                // 距离转角度
                let rotation = (glassX / circumference) * 360;
                wTopL.setAttribute('transform', `translate(250,270) rotate(${rotation})`);
                wTopR.setAttribute('transform', `translate(1350,270) rotate(${rotation})`);
                wBotL.setAttribute('transform', `translate(250,530) rotate(${rotation})`);
                wBotR.setAttribute('transform', `translate(1350,530) rotate(${rotation})`);

                // 纹理平移 (使用取模保持在 pattern width = 24 内无缝循环)
                let beltOffset = -(glassX % 24);
                beltTeethTop.setAttribute('x', 250 + beltOffset);
                beltTeethBot.setAttribute('x', 250 + beltOffset);

                // 4. 时序逻辑与视觉引导控制
                // 进料前
                if (glassX < 150) {
                    funnelIndicator.setAttribute('opacity', '0.6');
                    vectorArrows.setAttribute('opacity', '0');
                    compAnnotation.setAttribute('opacity', '0');
                    glassGlow.setAttribute('opacity', '0');
                } 
                // 咬合瞬态 & 高速稳定运输区 (展示理想解状态)
                else if (glassX >= 150 && glassX <= 1000) {
                    funnelIndicator.setAttribute('opacity', '0');
                    
                    // 淡入矢量与标注
                    let fade = Math.min((glassX - 150) / 100, 1);
                    vectorArrows.setAttribute('opacity', fade.toString());
                    compAnnotation.setAttribute('opacity', fade.toString());
                    
                    // 施加面压时的辉光闪烁感
                    let pulse = 0.4 + 0.2 * Math.sin(time / 200);
                    glassGlow.setAttribute('opacity', (fade * pulse).toString());
                } 
                // 出料与回弹
                else {
                    vectorArrows.setAttribute('opacity', '0');
                    compAnnotation.setAttribute('opacity', '0');
                    glassGlow.setAttribute('opacity', '0');
                }

                // 5. 更新遥测数字面板
                let realSpeed = (1.5 * speedMult).toFixed(3);
                dispV1.innerText = `${realSpeed} m/s`;
                dispV2.innerText = `${realSpeed} m/s`;

                requestAnimationFrame(animate);
            }

            // 初始化启动
            requestAnimationFrame(animate);
        });
    </script>
</body>
</html>
<!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>
        /* ==========================================================================
           Aesthetic Theme: Precision Industrial / Cleanroom Telemetry
           Tone: Dark, High-contrast, Analytical, Technical
           ========================================================================== */
        @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@400;500;700&display=swap');

        :root {
            --bg-base: #06090e;
            --grid-line: rgba(30, 41, 59, 0.6);
            --cyan-glow: #00f0ff;
            --cyan-dim: rgba(0, 240, 255, 0.2);
            --orange-alert: #ffaa00;
            --orange-dim: rgba(255, 170, 0, 0.2);
            --green-safe: #00ff88;
            --red-danger: #ff2a6d;
            --mech-dark: #0f172a;
            --mech-mid: #1e293b;
            --mech-light: #334155;
            --text-main: #e2e8f0;
            --text-muted: #64748b;
            
            --panel-bg: rgba(6, 9, 14, 0.85);
            --panel-border: rgba(51, 65, 85, 0.8);
        }

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

        body {
            background-color: var(--bg-base);
            color: var(--text-main);
            font-family: 'Noto Sans SC', sans-serif;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
            background-image: 
                linear-gradient(var(--grid-line) 1px, transparent 1px),
                linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
            background-size: 30px 30px;
            background-position: center center;
        }

        /* Ambient scanline overlay */
        body::after {
            content: "";
            position: fixed;
            top: 0; left: 0; width: 100vw; height: 100vh;
            background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%);
            background-size: 100% 4px;
            pointer-events: none;
            z-index: 999;
            opacity: 0.3;
        }

        #app-container {
            width: 100%;
            max-width: 1440px;
            height: 85vh;
            position: relative;
            display: flex;
            justify-content: center;
            align-items: center;
            box-shadow: inset 0 0 100px rgba(0, 240, 255, 0.05);
            border: 1px solid var(--panel-border);
            border-radius: 8px;
            background: radial-gradient(circle at center, rgba(30, 41, 59, 0.2) 0%, transparent 70%);
        }

        /* --------------------------------------------------------------------------
           HUD & Data Panels (Strict spatial control, small fonts)
           -------------------------------------------------------------------------- */
        .hud-panel {
            position: absolute;
            background: var(--panel-bg);
            backdrop-filter: blur(10px);
            border: 1px solid var(--panel-border);
            padding: 12px 16px;
            border-radius: 6px;
            z-index: 10;
            font-size: 12px;
            pointer-events: none;
            box-shadow: 0 4px 20px rgba(0,0,0,0.5);
        }

        .hud-title {
            font-family: 'JetBrains Mono', monospace;
            font-size: 10px;
            color: var(--text-muted);
            text-transform: uppercase;
            letter-spacing: 1px;
            margin-bottom: 8px;
            border-bottom: 1px solid rgba(255,255,255,0.1);
            padding-bottom: 4px;
        }

        .top-left { top: 20px; left: 20px; border-left: 3px solid var(--cyan-glow); }
        .top-right { top: 20px; right: 20px; border-right: 3px solid var(--green-safe); text-align: right; }
        .bottom-right { bottom: 20px; right: 20px; border-right: 3px solid var(--orange-alert); }

        .data-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 20px;
            margin-bottom: 6px;
        }
        
        .data-label { color: var(--text-main); }
        .data-value { 
            font-family: 'JetBrains Mono', monospace; 
            font-weight: 700; 
        }

        .val-cyan { color: var(--cyan-glow); }
        .val-green { color: var(--green-safe); }
        .val-orange { color: var(--orange-alert); }

        /* --------------------------------------------------------------------------
           Interactive Control Panel
           -------------------------------------------------------------------------- */
        .control-panel {
            position: absolute;
            bottom: 20px;
            left: 20px;
            background: var(--panel-bg);
            backdrop-filter: blur(10px);
            border: 1px solid var(--panel-border);
            border-left: 3px solid var(--text-muted);
            padding: 12px 16px;
            border-radius: 6px;
            z-index: 20;
            display: flex;
            flex-direction: column;
            gap: 12px;
            width: 260px;
        }

        .control-group {
            display: flex;
            flex-direction: column;
            gap: 6px;
        }

        .control-header {
            display: flex;
            justify-content: space-between;
            font-size: 11px;
            color: var(--text-main);
        }

        .control-val-display {
            font-family: 'JetBrains Mono', monospace;
            color: var(--cyan-glow);
        }

        /* Custom Range Slider */
        input[type=range] {
            -webkit-appearance: none;
            width: 100%;
            background: transparent;
        }
        input[type=range]:focus { outline: none; }
        input[type=range]::-webkit-slider-runnable-track {
            width: 100%;
            height: 4px;
            cursor: pointer;
            background: var(--mech-light);
            border-radius: 2px;
        }
        input[type=range]::-webkit-slider-thumb {
            height: 12px;
            width: 12px;
            border-radius: 50%;
            background: var(--cyan-glow);
            cursor: pointer;
            -webkit-appearance: none;
            margin-top: -4px;
            box-shadow: 0 0 10px var(--cyan-dim);
            transition: transform 0.1s;
        }
        input[type=range]::-webkit-slider-thumb:hover {
            transform: scale(1.2);
        }

        /* --------------------------------------------------------------------------
           SVG Canvas
           -------------------------------------------------------------------------- */
        svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        /* Transitions for smooth data updates */
        #ui-status-text { transition: color 0.3s ease; }
    </style>
</head>
<body>

    <div id="app-container">
        
        <!-- Top Left: Concept Identity -->
        <div class="hud-panel top-left">
            <div class="hud-title">System Concept // IFR Architecture</div>
            <div style="font-size: 14px; font-weight: 700; margin-bottom: 4px;">顶置随动柔性同步带系统</div>
            <div style="color: var(--text-muted); max-width: 250px; line-height: 1.4;">
                构建“汉堡包”式双轴同步搬运,通过柔性接触面消除一切相对滑动,达成表面零损伤的最终理想解。
            </div>
        </div>

        <!-- Top Right: Kinematic Telemetry -->
        <div class="hud-panel top-right">
            <div class="hud-title">Live Kinematics // Zero Slip</div>
            <div class="data-row">
                <span class="data-label">主伺服速度 (V_bot):</span>
                <span class="data-value val-cyan" id="disp-v-bot">0.000 mm/s</span>
            </div>
            <div class="data-row">
                <span class="data-label">随动轴速度 (V_top):</span>
                <span class="data-value val-cyan" id="disp-v-top">0.000 mm/s</span>
            </div>
            <div class="data-row" style="margin-top: 4px; border-top: 1px dashed rgba(255,255,255,0.1); padding-top: 4px;">
                <span class="data-label">运转速度差 (|ΔV|):</span>
                <span class="data-value val-green">< 0.01 mm/s</span>
            </div>
            <div class="data-row">
                <span class="data-label">界面摩擦力状态:</span>
                <span class="data-value val-green" id="disp-friction">绝对静止</span>
            </div>
        </div>

        <!-- Bottom Right: Mechanical State -->
        <div class="hud-panel bottom-right">
            <div class="hud-title">Material Physics // PU Sponge</div>
            <div class="data-row">
                <span class="data-label">海绵基础厚度:</span>
                <span class="data-value val-orange">20.0 mm</span>
            </div>
            <div class="data-row">
                <span class="data-label">当前绝对压缩量:</span>
                <span class="data-value val-orange" id="disp-comp">0.0 mm</span>
            </div>
            <div class="data-row">
                <span class="data-label">面接触压强分布:</span>
                <span class="data-value val-cyan">均匀柔性</span>
            </div>
        </div>

        <!-- Bottom Left: Interactive Controls -->
        <div class="control-panel">
            <div class="hud-title" style="margin-bottom: 12px;">System Override // Variables</div>
            
            <div class="control-group">
                <div class="control-header">
                    <span>系统输送线速度</span>
                    <span class="control-val-display" id="val-speed">1.5x</span>
                </div>
                <input type="range" id="ctrl-speed" min="0" max="3" step="0.1" value="1.5">
            </div>

            <div class="control-group">
                <div class="control-header">
                    <span>玻璃基板厚度 (模拟公差)</span>
                    <span class="control-val-display" id="val-thickness">4.0 mm</span>
                </div>
                <input type="range" id="ctrl-thickness" min="1" max="6" step="0.1" value="4.0">
            </div>
        </div>

        <!-- MAIN SVG CANVAS -->
        <svg id="main-svg" viewBox="0 0 1200 600" preserveAspectRatio="xMidYMid meet">
            <defs>
                <!-- Patterns -->
                <pattern id="sponge-pattern" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(15)">
                    <circle cx="6" cy="6" r="1.5" fill="var(--orange-alert)" opacity="0.3" />
                    <circle cx="2" cy="2" r="1" fill="var(--orange-alert)" opacity="0.2" />
                    <circle cx="10" cy="10" r="2" fill="var(--orange-alert)" opacity="0.15" />
                </pattern>

                <pattern id="belt-teeth" width="16" height="8" patternUnits="userSpaceOnUse">
                    <rect x="0" y="0" width="8" height="8" fill="var(--mech-dark)" />
                    <rect x="8" y="0" width="8" height="8" fill="var(--mech-light)" />
                </pattern>

                <pattern id="glass-grid" width="20" height="20" patternUnits="userSpaceOnUse">
                    <path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
                </pattern>

                <!-- Gradients -->
                <linearGradient id="glass-grad" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="rgba(0, 240, 255, 0.4)" />
                    <stop offset="20%" stop-color="rgba(0, 240, 255, 0.1)" />
                    <stop offset="80%" stop-color="rgba(0, 240, 255, 0.1)" />
                    <stop offset="100%" stop-color="rgba(0, 240, 255, 0.3)" />
                </linearGradient>

                <linearGradient id="belt-base-grad" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#475569" />
                    <stop offset="50%" stop-color="#1e293b" />
                    <stop offset="100%" stop-color="#0f172a" />
                </linearGradient>

                <!-- Filters -->
                <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="4" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="6" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>

                <!-- Markers -->
                <marker id="arrow-cyan" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                    <path d="M 0 2 L 10 5 L 0 8 z" fill="var(--cyan-glow)" />
                </marker>
                <marker id="arrow-orange" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                    <path d="M 0 2 L 10 5 L 0 8 z" fill="var(--orange-alert)" />
                </marker>
            </defs>

            <!-- Background Engineering Grid Details -->
            <g opacity="0.15">
                <line x1="200" y1="0" x2="200" y2="600" stroke="#fff" stroke-dasharray="4 4" />
                <line x1="1000" y1="0" x2="1000" y2="600" stroke="#fff" stroke-dasharray="4 4" />
                <text x="210" y="50" fill="#fff" font-family="JetBrains Mono" font-size="10">ENTRANCE BOUNDARY</text>
                <text x="1010" y="50" fill="#fff" font-family="JetBrains Mono" font-size="10">EXIT BOUNDARY</text>
                <!-- Centerline -->
                <line x1="0" y1="300" x2="1200" y2="300" stroke="var(--cyan-glow)" stroke-dasharray="10 10" opacity="0.5" />
            </g>

            <!-- Mechanism Background Linkage -->
            <g opacity="0.4">
                <!-- Physical sync shaft representation -->
                <path d="M 150 150 L 150 450" stroke="var(--text-muted)" stroke-width="2" stroke-dasharray="4 4" />
                <path d="M 1050 150 L 1050 450" stroke="var(--text-muted)" stroke-width="2" stroke-dasharray="4 4" />
                <rect x="146" y="280" width="8" height="40" fill="var(--mech-light)" />
                <rect x="1046" y="280" width="8" height="40" fill="var(--mech-light)" />
                <text x="160" y="305" fill="var(--text-muted)" font-family="JetBrains Mono" font-size="9">GEAR/SHAFT SYNC</text>
            </g>

            <!-- ================= BOTTOM ASSEMBLY ================= -->
            <g id="bottom-assembly" transform="translate(0, 0)">
                <!-- Belt path structure -->
                <rect x="150" y="375" width="900" height="15" fill="url(#belt-base-grad)" rx="7.5" />
                <rect id="belt-bot-anim" x="150" y="367" width="900" height="8" fill="url(#belt-teeth)" />
                
                <!-- Pulleys -->
                <g transform="translate(150, 382.5)">
                    <circle r="35" fill="var(--mech-mid)" stroke="var(--mech-dark)" stroke-width="4" />
                    <circle r="25" fill="var(--mech-light)" />
                    <g id="pulley-bot-l"><line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-dark)" stroke-width="6"/><line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-dark)" stroke-width="6"/></g>
                    <circle r="8" fill="var(--cyan-glow)" filter="url(#glow-cyan)" opacity="0.5"/>
                </g>
                <g transform="translate(1050, 382.5)">
                    <circle r="35" fill="var(--mech-mid)" stroke="var(--mech-dark)" stroke-width="4" />
                    <circle r="25" fill="var(--mech-light)" />
                    <g id="pulley-bot-r"><line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-dark)" stroke-width="6"/><line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-dark)" stroke-width="6"/></g>
                    <circle r="8" fill="var(--text-muted)"/>
                </g>
                <text x="150" y="440" fill="var(--text-muted)" font-family="JetBrains Mono" font-size="10" text-anchor="middle">MAIN SERVO</text>
            </g>

            <!-- ================= TOP ASSEMBLY ================= -->
            <g id="top-assembly" transform="translate(0, 0)">
                <!-- Rigid belt backing -->
                <rect x="150" y="210" width="900" height="15" fill="url(#belt-base-grad)" rx="7.5" />
                <rect id="belt-top-anim" x="150" y="225" width="900" height="8" fill="url(#belt-teeth)" />
                
                <!-- Dynamic PU Sponge Layer (Rendered via JS) -->
                <!-- The top flat line is at Y=233 (225 + 8) -->
                <!-- The uncompressed bottom line is at Y=263 (30mm thickness logic mapped to 20mm physical) -->
                <path id="pu-sponge" d="" fill="var(--orange-dim)" stroke="var(--orange-alert)" stroke-width="1.5" />
                <path id="pu-sponge-pattern" d="" fill="url(#sponge-pattern)" style="pointer-events: none; mix-blend-mode: overlay;" />

                <!-- Pulleys -->
                <g transform="translate(150, 217.5)">
                    <circle r="35" fill="var(--mech-mid)" stroke="var(--mech-dark)" stroke-width="4" />
                    <circle r="25" fill="var(--mech-light)" />
                    <g id="pulley-top-l"><line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-dark)" stroke-width="6"/><line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-dark)" stroke-width="6"/></g>
                    <circle r="8" fill="var(--text-muted)"/>
                </g>
                <g transform="translate(1050, 217.5)">
                    <circle r="35" fill="var(--mech-mid)" stroke="var(--mech-dark)" stroke-width="4" />
                    <circle r="25" fill="var(--mech-light)" />
                    <g id="pulley-top-r"><line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-dark)" stroke-width="6"/><line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-dark)" stroke-width="6"/></g>
                    <circle r="8" fill="var(--text-muted)"/>
                </g>
                <text x="150" y="160" fill="var(--text-muted)" font-family="JetBrains Mono" font-size="10" text-anchor="middle">SYNC FOLLOWER</text>
            </g>

            <!-- ================= THE GLASS (Payload) ================= -->
            <!-- Glass moves left to right. Bottom rests on bottom belt (Y=367) -->
            <g id="glass-group" transform="translate(-300, 0)">
                <!-- Glowing contact surface effect (top) -->
                <rect id="glass-contact-glow" x="-5" y="0" width="260" height="4" fill="var(--cyan-glow)" filter="url(#glow-cyan)" opacity="0" />
                
                <!-- Main Glass Body -->
                <!-- Y and Height will be updated by JS based on thickness -->
                <rect id="glass-body" x="0" y="347" width="250" height="20" fill="url(#glass-grad)" stroke="var(--cyan-glow)" stroke-width="1.5" />
                <rect id="glass-pattern" x="0" y="347" width="250" height="20" fill="url(#glass-grid)" opacity="0.3" pointer-events="none" />
                
                <text id="glass-label" x="125" y="360" fill="#fff" font-family="Noto Sans SC" font-size="11" font-weight="700" text-anchor="middle" dominant-baseline="middle" letter-spacing="2">GLASS</text>
            </g>

            <!-- ================= DYNAMIC ANNOTATIONS (Vectors & Highlights) ================= -->
            <g id="annotations" opacity="0" style="transition: opacity 0.3s ease;">
                
                <!-- Top Vector -->
                <g id="vec-top-group">
                    <path d="M -40 0 L 30 0" stroke="var(--orange-alert)" stroke-width="2" fill="none" marker-end="url(#arrow-orange)" filter="url(#glow-orange)"/>
                    <text x="-45" y="4" fill="var(--orange-alert)" font-family="JetBrains Mono" font-size="11" text-anchor="end">v1</text>
                </g>

                <!-- Bottom Vector -->
                <g id="vec-bot-group">
                    <path d="M -40 0 L 30 0" stroke="var(--cyan-glow)" stroke-width="2" fill="none" marker-end="url(#arrow-cyan)" filter="url(#glow-cyan)"/>
                    <text x="-45" y="4" fill="var(--cyan-glow)" font-family="JetBrains Mono" font-size="11" text-anchor="end">v2</text>
                </g>

                <!-- Equals Sign -->
                <g id="vec-equals">
                    <line x1="-15" y1="-8" x2="-15" y2="8" stroke="#fff" stroke-width="1" stroke-dasharray="2 2" opacity="0.5"/>
                    <text x="-15" y="0" fill="#fff" font-family="JetBrains Mono" font-size="12" font-weight="bold" text-anchor="middle" dominant-baseline="middle" filter="url(#glow-cyan)">=</text>
                </g>

                <!-- Compression Macro View Indicator -->
                <g id="compression-indicator" opacity="0">
                    <path d="M 0 0 Q 30 -30 60 -10" fill="none" stroke="var(--orange-alert)" stroke-width="1" stroke-dasharray="2 2" />
                    <rect x="60" y="-20" width="80" height="20" fill="rgba(6,9,14,0.9)" border="1px solid var(--orange-alert)"/>
                    <text x="100" y="-6" fill="var(--orange-alert)" font-family="Noto Sans SC" font-size="10" text-anchor="middle">柔性形变包覆</text>
                </g>
            </g>

            <!-- Funnel Infeed Indicator -->
            <g id="funnel-indicator" opacity="0">
                <path d="M 120 263 Q 150 263 180 255" fill="none" stroke="var(--text-main)" stroke-width="1.5" stroke-dasharray="3 3" marker-end="url(#arrow-cyan)"/>
                <text x="110" y="266" fill="var(--text-main)" font-family="Noto Sans SC" font-size="10" text-anchor="end">倒角入口</text>
            </g>

        </svg>
    </div>

    <script>
        document.addEventListener("DOMContentLoaded", () => {
            // --- Elements ---
            const ctrlSpeed = document.getElementById('ctrl-speed');
            const ctrlThickness = document.getElementById('ctrl-thickness');
            const valSpeed = document.getElementById('val-speed');
            const valThickness = document.getElementById('val-thickness');
            
            const dispVBot = document.getElementById('disp-v-bot');
            const dispVTop = document.getElementById('disp-v-top');
            const dispComp = document.getElementById('disp-comp');
            const dispFriction = document.getElementById('disp-friction');

            const svgGlassGroup = document.getElementById('glass-group');
            const svgGlassBody = document.getElementById('glass-body');
            const svgGlassPattern = document.getElementById('glass-pattern');
            const svgGlassLabel = document.getElementById('glass-label');
            const svgGlassContactGlow = document.getElementById('glass-contact-glow');
            
            const puSponge = document.getElementById('pu-sponge');
            const puSpongePattern = document.getElementById('pu-sponge-pattern');
            
            const beltBotAnim = document.getElementById('belt-bot-anim');
            const beltTopAnim = document.getElementById('belt-top-anim');
            const pulleyBotL = document.getElementById('pulley-bot-l');
            const pulleyBotR = document.getElementById('pulley-bot-r');
            const pulleyTopL = document.getElementById('pulley-top-l');
            const pulleyTopR = document.getElementById('pulley-top-r');

            const annotations = document.getElementById('annotations');
            const vecTopGroup = document.getElementById('vec-top-group');
            const vecBotGroup = document.getElementById('vec-bot-group');
            const vecEquals = document.getElementById('vec-equals');
            const compIndicator = document.getElementById('compression-indicator');
            const funnelIndicator = document.getElementById('funnel-indicator');

            // --- Geometric Constants ---
            const beltTopBaseY = 233; // Where sponge attaches
            const spongeRestY = 265;  // Default sponge bottom (Thickness = 32 visual units mapping to 20mm)
            const beltBotY = 367;     // Where glass sits
            const glassWidth = 250;
            const routeStartX = -300;
            const routeEndX = 1300;
            const spongeStartX = 150;
            const spongeEndX = 1050;
            
            // Base physical mapping
            // UI Thickness 1.0mm ~ 6.0mm -> Visual Height 10px ~ 60px (multiplier x10)
            const thicknessVisualMult = 10;
            // UI Speed 0.0x ~ 3.0x -> Visual Pixels/frame base = 2
            const speedVisualBase = 2.5;

            // --- State ---
            let state = {
                glassX: routeStartX,
                speedMult: parseFloat(ctrlSpeed.value),
                glassThicknessBase: parseFloat(ctrlThickness.value), // mm
                time: 0
            };

            // --- Event Listeners ---
            ctrlSpeed.addEventListener('input', (e) => {
                state.speedMult = parseFloat(e.target.value);
                valSpeed.innerText = state.speedMult.toFixed(1) + 'x';
            });

            ctrlThickness.addEventListener('input', (e) => {
                state.glassThicknessBase = parseFloat(e.target.value);
                valThickness.innerText = state.glassThicknessBase.toFixed(1) + ' mm';
            });

            // --- Path Generation Algorithm ---
            // Calculates the dynamic deformation of the PU sponge
            function updateSpongePath(glassX, glassTopY) {
                let d = `M ${spongeStartX} ${beltTopBaseY} L ${spongeEndX} ${beltTopBaseY} L ${spongeEndX} ${spongeRestY} `;
                
                const pointsCount = 100;
                const step = (spongeEndX - spongeStartX) / pointsCount;
                const gLeft = glassX;
                const gRight = glassX + glassWidth;
                
                // Funnel / Chamfer settings
                const chamferDist = 60; 

                // Generate bottom edge points right to left
                let bottomPoints = [];
                for(let i = pointsCount; i >= 0; i--) {
                    let px = spongeStartX + i * step;
                    let py = spongeRestY;

                    // Deformation logic
                    if (px >= gLeft && px <= gRight) {
                        // Directly above glass -> Fully compressed to glassTopY
                        // Ensure it doesn't compress past its base (safety)
                        py = Math.max(beltTopBaseY + 5, Math.min(spongeRestY, glassTopY));
                    } else if (px < gLeft && px > gLeft - chamferDist) {
                        // Front ramp (smoothstep interpolation)
                        let t = (gLeft - px) / chamferDist;
                        let ease = t * t * (3 - 2 * t); // Smoothstep
                        let targetY = Math.max(beltTopBaseY + 5, Math.min(spongeRestY, glassTopY));
                        py = targetY + (spongeRestY - targetY) * ease;
                    } else if (px > gRight && px < gRight + chamferDist) {
                        // Back ramp
                        let t = (px - gRight) / chamferDist;
                        let ease = t * t * (3 - 2 * t);
                        let targetY = Math.max(beltTopBaseY + 5, Math.min(spongeRestY, glassTopY));
                        py = targetY + (spongeRestY - targetY) * ease;
                    }
                    
                    bottomPoints.push(`${px},${py}`);
                }

                d += `L ${bottomPoints.join(' L ')} Z`;
                
                puSponge.setAttribute('d', d);
                puSpongePattern.setAttribute('d', d);
            }

            // --- Main Animation Loop ---
            function animate() {
                state.time++;

                // 1. Calculate Core Kinematics
                let moveAmount = speedVisualBase * state.speedMult;
                state.glassX += moveAmount;
                if (state.glassX > routeEndX) {
                    state.glassX = routeStartX; // Reset loop
                }

                // 2. Geometry Updates
                let visualHeight = state.glassThicknessBase * thicknessVisualMult;
                let glassTopY = beltBotY - visualHeight;
                
                // Update Glass Object
                svgGlassGroup.setAttribute('transform', `translate(${state.glassX}, 0)`);
                svgGlassBody.setAttribute('y', glassTopY);
                svgGlassBody.setAttribute('height', visualHeight);
                svgGlassPattern.setAttribute('y', glassTopY);
                svgGlassPattern.setAttribute('height', visualHeight);
                svgGlassLabel.setAttribute('y', glassTopY + visualHeight/2);
                svgGlassContactGlow.setAttribute('y', glassTopY - 2);

                // 3. Update Sponge Deformation
                updateSpongePath(state.glassX, glassTopY);

                // 4. Update Mechanical Rotations & Belt Patterns
                // Belt pattern repeats every 16px
                let beltOffset = -(state.glassX % 16); 
                beltBotAnim.setAttribute('x', spongeStartX + beltOffset);
                beltTopAnim.setAttribute('x', spongeStartX + beltOffset);

                // Pulley rotation (circumference logic approximation)
                let rotation = (state.glassX * 1.5) % 360; 
                pulleyBotL.setAttribute('transform', `rotate(${rotation})`);
                pulleyBotR.setAttribute('transform', `rotate(${rotation})`);
                // Top pulleys sync (same speed, opposite direction visually or same depending on gear routing. Assuming same direction transport)
                pulleyTopL.setAttribute('transform', `rotate(${rotation})`);
                pulleyTopR.setAttribute('transform', `rotate(${rotation})`);

                // 5. Calculate Live Telemetry & Phase State
                // Calculate physical compression
                let compressionVisual = Math.max(0, spongeRestY - glassTopY);
                let compressionPhysical = compressionVisual / 10; // map back to mm

                let realSpeed = 800 * state.speedMult; // mapping to real-world mm/s
                
                let phase = "standby";
                let gCenter = state.glassX + glassWidth/2;

                if (gCenter > 0 && gCenter < spongeStartX + 50) {
                    phase = "infeed";
                } else if (gCenter >= spongeStartX + 50 && gCenter <= spongeEndX - 50) {
                    phase = "transport";
                } else if (gCenter > spongeEndX - 50 && gCenter < 1200) {
                    phase = "outfeed";
                }

                // 6. Visual Directives (IFR Focus)
                if (phase === "transport") {
                    annotations.style.opacity = "1";
                    
                    // Position Vectors
                    let vecX = state.glassX + glassWidth/2 + 30;
                    vecTopGroup.setAttribute('transform', `translate(${vecX}, ${glassTopY - 10})`);
                    vecBotGroup.setAttribute('transform', `translate(${vecX}, ${beltBotY + 15})`);
                    vecEquals.setAttribute('transform', `translate(${vecX - 60}, ${(glassTopY + beltBotY)/2})`);

                    // Glow effect mapping
                    svgGlassContactGlow.setAttribute('opacity', compressionPhysical > 0 ? '1' : '0');

                    // Data Update
                    dispVBot.innerText = realSpeed.toFixed(3) + " mm/s";
                    dispVTop.innerText = realSpeed.toFixed(3) + " mm/s"; // Identical
                    dispComp.innerText = compressionPhysical.toFixed(1) + " mm";
                    
                    if (compressionPhysical > 0) {
                        dispFriction.innerText = "绝对静止 (夹持)";
                        dispFriction.style.color = "var(--green-safe)";
                        dispComp.style.color = "var(--orange-alert)";
                        compIndicator.setAttribute('opacity', '1');
                        compIndicator.setAttribute('transform', `translate(${state.glassX + 20}, ${glassTopY})`);
                    } else {
                        dispFriction.innerText = "无接触";
                        dispFriction.style.color = "var(--text-muted)";
                        dispComp.style.color = "var(--text-muted)";
                        compIndicator.setAttribute('opacity', '0');
                    }
                    
                    funnelIndicator.setAttribute('opacity', '0');

                } else if (phase === "infeed") {
                    annotations.style.opacity = "0";
                    svgGlassContactGlow.setAttribute('opacity', '0');
                    funnelIndicator.setAttribute('opacity', '1');
                    dispComp.innerText = "进入倒角区...";
                    dispComp.style.color = "var(--text-main)";
                    dispVBot.innerText = realSpeed.toFixed(3) + " mm/s";
                    dispVTop.innerText = realSpeed.toFixed(3) + " mm/s";
                } else {
                    annotations.style.opacity = "0";
                    svgGlassContactGlow.setAttribute('opacity', '0');
                    funnelIndicator.setAttribute('opacity', '0');
                    if(state.speedMult === 0) {
                        dispVBot.innerText = "0.000 mm/s";
                        dispVTop.innerText = "0.000 mm/s";
                    } else {
                        dispVBot.innerText = realSpeed.toFixed(3) + " mm/s";
                        dispVTop.innerText = realSpeed.toFixed(3) + " mm/s";
                    }
                    dispComp.innerText = "0.0 mm";
                    dispComp.style.color = "var(--text-muted)";
                    dispFriction.innerText = "无工作负载";
                    dispFriction.style.color = "var(--text-muted)";
                }

                // Vector arrows scaling based on speed
                let arrowLen = 30 + state.speedMult * 20;
                vecTopGroup.querySelector('path').setAttribute('d', `M -40 0 L ${arrowLen} 0`);
                vecBotGroup.querySelector('path').setAttribute('d', `M -40 0 L ${arrowLen} 0`);

                requestAnimationFrame(animate);
            }

            // Start loop
            requestAnimationFrame(animate);
        });
    </script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分