分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>高保真 SVG 原理动画 - TRIZ 最终理想解</title>
    <style>
        :root {
            --bg-color: #0b1120;
            --panel-bg: rgba(15, 23, 42, 0.75);
            --border-color: rgba(51, 65, 85, 0.6);
            --text-main: #f8fafc;
            --text-muted: #94a3b8;
            --accent-cyan: #06b6d4;
            --accent-orange: #f97316;
            --accent-green: #10b981;
        }

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

        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            background-color: var(--bg-color);
            color: var(--text-main);
            display: flex;
            height: 100vh;
            overflow: hidden;
            background-image: 
                radial-gradient(circle at 50% 50%, rgba(6, 182, 212, 0.05) 0%, transparent 50%),
                linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
                linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
            background-size: 100% 100%, 40px 40px, 40px 40px;
        }

        .canvas-container {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            padding: 2rem;
        }

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

        .panel-container {
            width: 380px;
            background: var(--panel-bg);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            border-left: 1px solid var(--border-color);
            display: flex;
            flex-direction: column;
            padding: 2rem;
            z-index: 10;
            overflow-y: auto;
            box-shadow: -10px 0 30px rgba(0,0,0,0.5);
        }

        .badge {
            display: inline-block;
            padding: 0.25rem 0.75rem;
            background: linear-gradient(135deg, #10b98122, #05966922);
            color: var(--accent-green);
            border: 1px solid rgba(16, 185, 129, 0.3);
            border-radius: 20px;
            font-size: 0.75rem;
            font-weight: 600;
            letter-spacing: 1px;
            margin-bottom: 1rem;
            text-transform: uppercase;
        }

        h1 {
            font-size: 1.5rem;
            font-weight: 700;
            line-height: 1.3;
            margin-bottom: 1.5rem;
            background: linear-gradient(to right, #fff, #94a3b8);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .section-title {
            font-size: 0.85rem;
            color: var(--text-muted);
            text-transform: uppercase;
            letter-spacing: 2px;
            margin-bottom: 0.75rem;
            margin-top: 1.5rem;
            border-bottom: 1px solid var(--border-color);
            padding-bottom: 0.5rem;
        }

        .triz-card {
            background: rgba(255,255,255,0.03);
            border: 1px solid var(--border-color);
            border-radius: 8px;
            padding: 1rem;
            margin-bottom: 1rem;
        }

        .triz-card p {
            font-size: 0.9rem;
            line-height: 1.6;
            color: #cbd5e1;
        }

        .triz-card strong {
            color: var(--accent-orange);
            font-weight: 600;
        }

        .param-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 0.75rem;
            margin-bottom: 1.5rem;
        }

        .param-item {
            background: rgba(0,0,0,0.3);
            padding: 0.75rem;
            border-radius: 6px;
            border: 1px solid rgba(255,255,255,0.05);
        }

        .param-value {
            font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
            color: var(--accent-cyan);
            font-weight: 700;
            font-size: 1.1rem;
            margin-bottom: 0.2rem;
        }

        .param-label {
            font-size: 0.7rem;
            color: var(--text-muted);
        }

        .controls {
            margin-top: auto;
            background: rgba(0,0,0,0.3);
            padding: 1.25rem;
            border-radius: 8px;
            border: 1px solid var(--border-color);
        }

        .control-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 1rem;
        }

        .control-title {
            font-size: 0.9rem;
            font-weight: 600;
        }

        .btn-toggle {
            background: transparent;
            border: 1px solid var(--accent-cyan);
            color: var(--accent-cyan);
            padding: 0.4rem 1rem;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.8rem;
            font-weight: 600;
            transition: all 0.2s ease;
        }

        .btn-toggle:hover {
            background: rgba(6, 182, 212, 0.1);
        }

        .btn-toggle.active {
            background: var(--accent-cyan);
            color: #000;
        }

        .slider-container {
            display: flex;
            flex-direction: column;
            gap: 0.5rem;
        }

        .slider-labels {
            display: flex;
            justify-content: space-between;
            font-size: 0.75rem;
            color: var(--text-muted);
        }

        input[type=range] {
            -webkit-appearance: none;
            width: 100%;
            background: transparent;
        }

        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            height: 16px;
            width: 16px;
            border-radius: 50%;
            background: var(--accent-cyan);
            cursor: pointer;
            margin-top: -6px;
            box-shadow: 0 0 10px rgba(6, 182, 212, 0.5);
        }

        input[type=range]::-webkit-slider-runnable-track {
            width: 100%;
            height: 4px;
            cursor: pointer;
            background: var(--border-color);
            border-radius: 2px;
        }

        /* 隐藏滚动条 */
        ::-webkit-scrollbar {
            width: 6px;
        }
        ::-webkit-scrollbar-track {
            background: transparent;
        }
        ::-webkit-scrollbar-thumb {
            background: rgba(255,255,255,0.1);
            border-radius: 3px;
        }
    </style>
</head>
<body>

    <div class="canvas-container">
        <svg id="pumpSvg" viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
            <defs>
                <filter id="glowOrange" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="6" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="glowCyan" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="8" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <marker id="arrowhead" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
                    <polygon points="0 0, 6 3, 0 6" fill="var(--accent-green)" />
                </marker>
                
                <radialGradient id="hubGradient" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#334155"/>
                    <stop offset="100%" stop-color="#0f172a"/>
                </radialGradient>
                
                <!-- SVG 图案:背景网格 -->
                <pattern id="gridPattern" width="40" height="40" patternUnits="userSpaceOnUse">
                    <path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
                </pattern>
            </defs>

            <!-- 中心对齐基准:cx=400, cy=400 -->
            
            <!-- 背景外壳 / 泵管定子 -->
            <path id="stator" d="M 580 400 A 180 180 0 0 1 220 400" fill="none" stroke="#1e293b" stroke-width="24" stroke-linecap="round"/>
            <path id="stator-inner" d="M 568 400 A 168 168 0 0 1 232 400" fill="none" stroke="#334155" stroke-width="2" stroke-linecap="round"/>
            
            <!-- 动态流体区域 -->
            <path id="fluidArea" fill="var(--accent-cyan)" fill-opacity="0.3" filter="url(#glowCyan)"/>
            
            <!-- 动态流体气泡(由 JS 插入) -->
            <g id="fluidBubbles"></g>

            <!-- 动态压缩指示箭头(由 JS 插入) -->
            <g id="compressionArrows"></g>

            <!-- 核心创新:柔性中介带 -->
            <path id="isolationBelt" fill="none" stroke="var(--accent-orange)" stroke-width="4" filter="url(#glowOrange)"/>
            
            <!-- 隔离带无滑动标记(证明纯径向运动) -->
            <g id="beltMarkers"></g>

            <!-- 驱动转子组 -->
            <g id="rotorSystem">
                <!-- 中心轮毂 -->
                <circle cx="400" cy="400" r="30" fill="url(#hubGradient)" stroke="#475569" stroke-width="2"/>
                <circle cx="400" cy="400" r="10" fill="#94a3b8"/>
                
                <!-- 转子臂与滚轮(由 JS 控制位置) -->
                <g id="arms"></g>
                <g id="rollers"></g>
            </g>

            <!-- 注释与说明引线 -->
            <g id="annotations" font-family="ui-sans-serif, system-ui" font-size="13">
                <!-- 隔离带标注 -->
                <path d="M 280 230 L 250 200 L 150 200" fill="none" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3,3"/>
                <circle cx="280" cy="230" r="3" fill="#cbd5e1"/>
                <text x="150" y="190" fill="var(--accent-orange)" font-weight="bold">环形柔性隔离带</text>
                <text x="150" y="215" fill="#94a3b8" font-size="11">隔离滚动摩擦</text>

                <!-- 纯垂直挤压标注 -->
                <path d="M 400 550 L 400 620 L 500 620" fill="none" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3,3"/>
                <circle cx="400" cy="550" r="3" fill="#cbd5e1"/>
                <text x="510" y="615" fill="var(--accent-green)" font-weight="bold">纯径向(垂直)挤压</text>
                <text x="510" y="635" fill="#94a3b8" font-size="11">消除切向拉扯,管壁零磨损</text>
                
                <!-- 无相对滑动标注 -->
                <path d="M 525 320 L 580 250 L 650 250" fill="none" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3,3"/>
                <circle cx="525" cy="320" r="3" fill="#cbd5e1"/>
                <text x="575" y="240" fill="#f8fafc" font-weight="bold">固定标记位验证</text>
                <text x="575" y="265" fill="#94a3b8" font-size="11">隔离带只产生径向形变</text>
            </g>

        </svg>
    </div>

    <div class="panel-container">
        <div class="badge">TRIZ 最终理想解 (IFR)</div>
        <h1>随动柔性隔离中介设计</h1>
        
        <div class="triz-card">
            <p><strong>传统矛盾:</strong> 滚轮直接作用于泵管,必须既要“滚动推进流体”(需求),又要“避免切向摩擦撕裂管壁”(限制)。</p>
            <div style="height: 1px; background: rgba(255,255,255,0.1); margin: 0.75rem 0;"></div>
            <p><strong>理想解机理:</strong> 引入一层随动的柔性中介带,系统巧妙地将滚轮的滚动摩擦完全吸收,转化为中介带对泵管的<strong>纯垂直挤压</strong>,彻底根除了管壁的切向拉扯损耗。</p>
        </div>

        <div class="section-title">核心工程参数</div>
        <div class="param-grid">
            <div class="param-item">
                <div class="param-value">1.2<span style="font-size:0.6em">mm</span></div>
                <div class="param-label">隔离带厚度</div>
            </div>
            <div class="param-item">
                <div class="param-value">>15<span style="font-size:0.6em">MPa</span></div>
                <div class="param-label">抗拉伸屈服强度</div>
            </div>
        </div>

        <div class="section-title">动态视觉指引</div>
        <ul style="list-style: none; padding-left: 0; font-size: 0.85rem; color: var(--text-muted); line-height: 1.8; margin-bottom: 2rem;">
            <li><span style="display:inline-block; width:12px; height:12px; border-radius:50%; background:var(--accent-orange); margin-right:8px; box-shadow: 0 0 5px var(--accent-orange);"></span>橙色外环为高强度柔性隔离带</li>
            <li><span style="display:inline-block; width:12px; height:12px; border-radius:50%; background:var(--accent-green); margin-right:8px; box-shadow: 0 0 5px var(--accent-green);"></span>绿色箭头指示纯垂直径向挤压力</li>
            <li><span style="display:inline-block; width:12px; height:12px; border-radius:50%; background:#fb923c; margin-right:8px;"></span>带上定点标记仅随径向涨缩无切向位移</li>
        </ul>

        <div class="controls">
            <div class="control-header">
                <span class="control-title">系统运行控制</span>
                <button id="toggleBtn" class="btn-toggle active">暂停运行</button>
            </div>
            <div class="slider-container">
                <input type="range" id="speedSlider" min="0.5" max="4" step="0.1" value="1.5">
                <div class="slider-labels">
                    <span>慢速演示</span>
                    <span>全速运转</span>
                </div>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // --- 核心物理参数 ---
            const CENTER = 400;
            const R_ARM = 120;       // 转子臂长
            const R_ROLLER = 35;     // 滚轮半径
            const R_IDLE = 125;      // 隔离带非受力时的自然基准半径
            const R_STATOR = 175;    // 定子(外壳)内壁半径
            
            // --- 动画状态 ---
            let isPlaying = true;
            let rotorAngle = 0;      // 转子整体角度
            let speed = 1.5;         // 每帧旋转角度

            // --- DOM 元素引用 ---
            const armsGroup = document.getElementById('arms');
            const rollersGroup = document.getElementById('rollers');
            const beltPathEl = document.getElementById('isolationBelt');
            const fluidAreaEl = document.getElementById('fluidArea');
            const beltMarkersGroup = document.getElementById('beltMarkers');
            const compressionArrowsGroup = document.getElementById('compressionArrows');
            const fluidBubblesGroup = document.getElementById('fluidBubbles');
            
            const toggleBtn = document.getElementById('toggleBtn');
            const speedSlider = document.getElementById('speedSlider');

            // --- 初始化构造 UI 元素 ---
            
            // 1. 创建转子臂和滚轮 (3组,相隔120度)
            for(let i=0; i<3; i++) {
                // 转子臂
                let arm = document.createElementNS("http://www.w3.org/2000/svg", "line");
                arm.setAttribute("id", `arm_${i}`);
                arm.setAttribute("x1", CENTER);
                arm.setAttribute("y1", CENTER);
                arm.setAttribute("stroke", "#64748b");
                arm.setAttribute("stroke-width", "12");
                arm.setAttribute("stroke-linecap", "round");
                armsGroup.appendChild(arm);

                // 滚轮组
                let rollerGrp = document.createElementNS("http://www.w3.org/2000/svg", "g");
                rollerGrp.setAttribute("id", `roller_${i}`);
                
                let rollerOuter = document.createElementNS("http://www.w3.org/2000/svg", "circle");
                rollerOuter.setAttribute("r", R_ROLLER);
                rollerOuter.setAttribute("fill", "#1e293b");
                rollerOuter.setAttribute("stroke", "#94a3b8");
                rollerOuter.setAttribute("stroke-width", "3");
                
                // 滚轮内旋转指示线(表明其自身也在滚动)
                let rollerSpoke = document.createElementNS("http://www.w3.org/2000/svg", "line");
                rollerSpoke.setAttribute("x1", 0);
                rollerSpoke.setAttribute("y1", -R_ROLLER);
                rollerSpoke.setAttribute("x2", 0);
                rollerSpoke.setAttribute("y2", R_ROLLER);
                rollerSpoke.setAttribute("stroke", "#475569");
                rollerSpoke.setAttribute("stroke-width", "4");

                let rollerCenter = document.createElementNS("http://www.w3.org/2000/svg", "circle");
                rollerCenter.setAttribute("r", "6");
                rollerCenter.setAttribute("fill", "#cbd5e1");

                rollerGrp.appendChild(rollerOuter);
                rollerGrp.appendChild(rollerSpoke);
                rollerGrp.appendChild(rollerCenter);
                rollersGroup.appendChild(rollerGrp);
            }

            // 2. 创建隔离带定点标记 (每隔 10 度一个,共 36 个)
            // 这些点只会在当前角度上做径向伸缩,证明隔离带没有滑动
            for(let i=0; i<36; i++) {
                let dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
                dot.setAttribute("id", `marker_${i*10}`);
                dot.setAttribute("r", "3");
                dot.setAttribute("fill", "#fcd34d");
                beltMarkersGroup.appendChild(dot);
            }

            // 3. 创建纯垂直挤压指示箭头 (下半圆工作区:20度到160度)
            for(let i=2; i<=16; i++) {
                let angle = i * 10;
                let rad = angle * Math.PI / 180;
                let r1 = 158; // 箭头起点基准
                let r2 = 170; // 箭头终点基准
                
                let arrow = document.createElementNS("http://www.w3.org/2000/svg", "line");
                arrow.setAttribute("id", `arrow_${angle}`);
                arrow.setAttribute("x1", CENTER + r1 * Math.cos(rad));
                arrow.setAttribute("y1", CENTER + r1 * Math.sin(rad));
                arrow.setAttribute("x2", CENTER + r2 * Math.cos(rad));
                arrow.setAttribute("y2", CENTER + r2 * Math.sin(rad));
                arrow.setAttribute("stroke", "var(--accent-green)");
                arrow.setAttribute("stroke-width", "3");
                arrow.setAttribute("marker-end", "url(#arrowhead)");
                arrow.setAttribute("opacity", "0"); // 默认透明
                compressionArrowsGroup.appendChild(arrow);
            }

            // 4. 初始化流体气泡
            const numBubbles = 30;
            const bubbles = [];
            for(let i=0; i<numBubbles; i++) {
                let bubble = document.createElementNS("http://www.w3.org/2000/svg", "circle");
                let size = Math.random() * 3 + 2;
                bubble.setAttribute("r", size);
                bubble.setAttribute("fill", "#ffffff");
                bubble.setAttribute("opacity", "0.6");
                fluidBubblesGroup.appendChild(bubble);
                bubbles.push({
                    el: bubble,
                    angle: Math.random() * 180, // 只在下半圆 [0, 180] 出现
                    offsetRatio: Math.random() // 在隔离带和定子之间的径向偏移比例
                });
            }

            // --- 核心数学机理:计算任意角度下隔离带被撑开的半径 ---
            // 隔离带是柔性的,被滚轮向外撑开。
            // 由于有 3 个滚轮,我们需要取自然状态和三个滚轮影响的“最大值外包络线”。
            function getBeltRadius(thetaDeg, currentRotorAngles) {
                let maxR = R_IDLE; 
                
                for(let a of currentRotorAngles) {
                    // 计算当前计算点与滚轮中心的夹角差
                    let diff = (thetaDeg - a) * Math.PI / 180;
                    // 归一化到 [-pi, pi]
                    diff = Math.atan2(Math.sin(diff), Math.cos(diff));
                    
                    // 判断该角度射线是否与滚轮圆相交
                    if (Math.abs(R_ARM * Math.sin(diff)) <= R_ROLLER) {
                        // 求解射线与滚轮外缘的交点到圆心的距离
                        let val = Math.max(0, R_ROLLER * R_ROLLER - R_ARM * R_ARM * Math.sin(diff) * Math.sin(diff));
                        let rInter = R_ARM * Math.cos(diff) + Math.sqrt(val);
                        if (rInter > maxR) {
                            maxR = rInter;
                        }
                    }
                }
                return maxR;
            }

            // --- 渲染循环 (高频执行) ---
            function render() {
                if (isPlaying) {
                    rotorAngle = (rotorAngle + speed) % 360;
                }

                // 3个滚轮的绝对角度
                let rAngles = [rotorAngle, (rotorAngle + 120) % 360, (rotorAngle + 240) % 360];

                // 1. 预计算 0~359 度的隔离带半径
                let beltR = new Array(360);
                for(let i=0; i<360; i++) {
                    beltR[i] = getBeltRadius(i, rAngles);
                }

                // 2. 绘制隔离带路径 (连续闭合曲线)
                let bPath = "";
                for(let i=0; i<360; i++) {
                    let rad = i * Math.PI / 180;
                    let x = CENTER + beltR[i] * Math.cos(rad);
                    let y = CENTER + beltR[i] * Math.sin(rad);
                    bPath += (i === 0 ? "M" : "L") + x + "," + y + " ";
                }
                bPath += "Z";
                beltPathEl.setAttribute("d", bPath);

                // 3. 绘制被挤压的流体区域 (仅在下半部分 0 到 180 度有效)
                // 流体存在于 隔离带外壁与定子内壁之间
                let fPath = "";
                // 正向沿隔离带外壁走
                for(let i=0; i<=180; i++) {
                    let rad = i * Math.PI / 180;
                    // 流体内边界是隔离带的半径 + 厚度预留
                    let rInner = beltR[i] + 3;
                    if(rInner > R_STATOR) rInner = R_STATOR; // 完全闭合
                    
                    let x = CENTER + rInner * Math.cos(rad);
                    let y = CENTER + rInner * Math.sin(rad);
                    fPath += (i === 0 ? "M" : "L") + x + "," + y + " ";
                }
                // 反向沿定子内壁走回
                for(let i=180; i>=0; i--) {
                    let rad = i * Math.PI / 180;
                    let x = CENTER + R_STATOR * Math.cos(rad);
                    let y = CENTER + R_STATOR * Math.sin(rad);
                    fPath += "L" + x + "," + y + " ";
                }
                fPath += "Z";
                fluidAreaEl.setAttribute("d", fPath);

                // 4. 更新转子臂和滚轮位置
                for(let i=0; i<3; i++) {
                    let a = rAngles[i];
                    let rad = a * Math.PI / 180;
                    let cx = CENTER + R_ARM * Math.cos(rad);
                    let cy = CENTER + R_ARM * Math.sin(rad);
                    
                    // 更新机械臂末端位置
                    let arm = document.getElementById(`arm_${i}`);
                    arm.setAttribute("x2", cx);
                    arm.setAttribute("y2", cy);

                    // 滚轮自身也在发生逆向或同向滚动(表现其在隔离带内侧滚动)
                    // R_ARM / R_ROLLER = 120 / 35 ≈ 3.4
                    let rollerRotation = a * 3.4;
                    let rGrp = document.getElementById(`roller_${i}`);
                    rGrp.setAttribute("transform", `translate(${cx}, ${cy}) rotate(${rollerRotation})`);
                }

                // 5. 更新隔离带定点标记(纯径向伸缩)
                for(let i=0; i<36; i++) {
                    let angle = i * 10;
                    let rad = angle * Math.PI / 180;
                    let r = beltR[angle];
                    let dot = document.getElementById(`marker_${angle}`);
                    if (dot) {
                        dot.setAttribute("cx", CENTER + r * Math.cos(rad));
                        dot.setAttribute("cy", CENTER + r * Math.sin(rad));
                    }
                }

                // 6. 更新纯垂直挤压箭头 (动态显示挤压力)
                // 只有当隔离带产生明显的向外扩张时,才显示垂直挤压箭头
                for(let i=2; i<=16; i++) {
                    let angle = i * 10;
                    let currentR = beltR[angle];
                    let arrow = document.getElementById(`arrow_${angle}`);
                    
                    if (arrow) {
                        // 半径超过 130 开始显示,达到 155 时最亮
                        let opacity = (currentR - 130) / (155 - 130);
                        opacity = Math.max(0, Math.min(1, opacity));
                        arrow.setAttribute("opacity", opacity.toFixed(2));
                        
                        // 让箭头稍微向外移动,增加压迫感
                        let pushOffset = opacity * 5;
                        let rad = angle * Math.PI / 180;
                        let r1 = 155 + pushOffset;
                        let r2 = 167 + pushOffset;
                        arrow.setAttribute("x1", CENTER + r1 * Math.cos(rad));
                        arrow.setAttribute("y1", CENTER + r1 * Math.sin(rad));
                        arrow.setAttribute("x2", CENTER + r2 * Math.cos(rad));
                        arrow.setAttribute("y2", CENTER + r2 * Math.sin(rad));
                    }
                }

                // 7. 更新流体气泡动画 (展现被封装的流体前移)
                for(let i=0; i<bubbles.length; i++) {
                    let b = bubbles[i];
                    if (isPlaying) {
                        // 流体整体推进速度大致等同于转子扫过角度
                        b.angle = (b.angle + speed) % 180; 
                    }
                    
                    let bAngleInt = Math.floor(b.angle);
                    let currentInnerR = beltR[bAngleInt] || R_IDLE;
                    
                    // 如果该角度被完全闭合(被挤压住了),气泡隐藏
                    if (currentInnerR < R_STATOR - 8) {
                        let rad = b.angle * Math.PI / 180;
                        // 在内外壁之间按比例分布
                        let rDist = currentInnerR + 4 + b.offsetRatio * (R_STATOR - currentInnerR - 8);
                        let bx = CENTER + rDist * Math.cos(rad);
                        let by = CENTER + rDist * Math.sin(rad);
                        b.el.setAttribute("cx", bx);
                        b.el.setAttribute("cy", by);
                        b.el.setAttribute("opacity", b.offsetRatio * 0.6 + 0.2); // 越靠外的越亮
                    } else {
                        b.el.setAttribute("opacity", 0);
                    }
                }

                requestAnimationFrame(render);
            }

            // 启动渲染循环
            render();

            // --- 交互事件 ---
            toggleBtn.addEventListener('click', () => {
                isPlaying = !isPlaying;
                if (isPlaying) {
                    toggleBtn.textContent = "暂停运行";
                    toggleBtn.classList.add('active');
                } else {
                    toggleBtn.textContent = "恢复运行";
                    toggleBtn.classList.remove('active');
                }
            });

            speedSlider.addEventListener('input', (e) => {
                speed = parseFloat(e.target.value);
            });
        });
    </script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分