分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自复位倾斜排浆台 - TRIZ 原理演示</title>
    <style>
        :root {
            --bg-color: #060b14;
            --panel-bg: rgba(11, 17, 32, 0.75);
            --border-color: rgba(56, 189, 248, 0.2);
            --accent-cyan: #00F0FF;
            --accent-amber: #F59E0B;
            --accent-red: #FF3366;
            --accent-green: #10B981;
            --text-main: #F8FAFC;
            --text-muted: #94A3B8;
        }

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

        body {
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            overflow: hidden;
            background-image: 
                radial-gradient(circle at 50% 0%, #1e293b 0%, transparent 70%),
                radial-gradient(circle at 50% 100%, #0f172a 0%, #060b14 100%);
        }

        #app-container {
            position: relative;
            width: 100%;
            max-width: 1200px;
            aspect-ratio: 16/9;
            background: rgba(4, 9, 20, 0.6);
            border-radius: 16px;
            box-shadow: 0 0 0 1px var(--border-color), 
                        0 25px 50px -12px rgba(0,0,0,0.8),
                        inset 0 0 40px rgba(0, 240, 255, 0.05);
            overflow: hidden;
            display: flex;
        }

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

        .hud-panel {
            position: absolute;
            top: 30px;
            left: 30px;
            width: 340px;
            background: var(--panel-bg);
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border: 1px solid var(--border-color);
            border-radius: 12px;
            padding: 24px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.5);
            z-index: 10;
        }

        .hud-title {
            font-size: 1.1rem;
            font-weight: 700;
            color: var(--accent-cyan);
            margin-bottom: 8px;
            letter-spacing: 1px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .hud-title::before {
            content: '';
            display: block;
            width: 8px;
            height: 8px;
            background: var(--accent-cyan);
            border-radius: 50%;
            box-shadow: 0 0 8px var(--accent-cyan);
        }

        .hud-desc {
            font-size: 0.85rem;
            color: var(--text-muted);
            line-height: 1.6;
            margin-bottom: 20px;
            padding-bottom: 16px;
            border-bottom: 1px dashed rgba(148, 163, 184, 0.2);
        }

        .hud-desc strong {
            color: #E2E8F0;
        }

        .data-grid {
            display: grid;
            grid-template-columns: 1fr;
            gap: 12px;
            margin-bottom: 24px;
        }

        .data-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-size: 0.9rem;
            background: rgba(255,255,255,0.03);
            padding: 8px 12px;
            border-radius: 6px;
        }

        .data-label {
            color: var(--text-muted);
        }

        .data-value {
            font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
            font-weight: 600;
            font-size: 1.05rem;
        }

        .value-angle { color: var(--accent-amber); }
        .value-cg { color: var(--text-main); }
        
        .status-badge {
            padding: 4px 10px;
            border-radius: 20px;
            font-size: 0.8rem;
            font-weight: 600;
            letter-spacing: 0.5px;
            transition: all 0.3s ease;
        }

        .controls {
            margin-top: 20px;
        }

        .control-header {
            display: flex;
            justify-content: space-between;
            margin-bottom: 12px;
            font-size: 0.85rem;
            color: var(--text-muted);
        }

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

        input[type=range]:focus {
            outline: none;
        }

        input[type=range]::-webkit-slider-runnable-track {
            width: 100%;
            height: 6px;
            background: rgba(255,255,255,0.1);
            border-radius: 3px;
        }

        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            height: 16px;
            width: 16px;
            border-radius: 50%;
            background: var(--accent-cyan);
            margin-top: -5px;
            box-shadow: 0 0 10px rgba(0, 240, 255, 0.5);
            transition: transform 0.1s;
        }

        input[type=range]::-webkit-slider-thumb:hover {
            transform: scale(1.2);
        }

        .watermark {
            position: absolute;
            bottom: 20px;
            right: 30px;
            font-size: 1.5rem;
            font-weight: 900;
            color: rgba(255,255,255,0.03);
            pointer-events: none;
            letter-spacing: 4px;
        }

        /* SVG Specific Styles */
        .glow { filter: drop-shadow(0 0 8px rgba(0, 240, 255, 0.6)); }
        .glow-red { filter: drop-shadow(0 0 8px rgba(255, 51, 102, 0.6)); }
        .mesh-line { stroke-dasharray: 4 4; animation: march 1s linear infinite; }
        
        @keyframes march {
            from { stroke-dashoffset: 8; }
            to { stroke-dashoffset: 0; }
        }
        
        .pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }
    </style>
</head>
<body>

    <div id="app-container">
        <!-- HUD Panel -->
        <div class="hud-panel">
            <div class="hud-title">IFR 自动倾斜排浆系统</div>
            <div class="hud-desc">
                <strong>最终理想解 (IFR):</strong> 利用跑浆液体的自身重量作为驱动源。当浆液堆积改变系统重心并跨越临界支点时,系统自动克服配重力矩发生倾覆,实现<strong>无源自动化排浆</strong>。
            </div>

            <div class="data-grid">
                <div class="data-row">
                    <span class="data-label">系统状态</span>
                    <span id="ui-status" class="status-badge" style="background: rgba(16, 185, 129, 0.1); color: var(--accent-green);">正常运行</span>
                </div>
                <div class="data-row">
                    <span class="data-label">实时倾角 (±4°)</span>
                    <span id="ui-angle" class="data-value value-angle">0.0°</span>
                </div>
                <div class="data-row">
                    <span class="data-label">系统重心X轴坐标</span>
                    <span id="ui-cg" class="data-value value-cg">590.0</span>
                </div>
            </div>

            <div class="controls">
                <div class="control-header">
                    <span>手动注入浆液 (破坏平衡)</span>
                    <span id="ui-mass-label" style="color: var(--accent-cyan); font-weight: bold;">0 kg</span>
                </div>
                <input type="range" id="massSlider" min="0" max="400" value="0" step="1">
                <div style="text-align: right; margin-top: 8px; font-size: 0.7rem; color: var(--text-muted);">
                    *操作滑块可暂停自动演示
                </div>
            </div>
        </div>

        <div class="watermark">TRIZ KINEMATICS</div>

        <!-- SVG Animation Canvas -->
        <svg viewBox="0 0 1200 675" preserveAspectRatio="xMidYMid slice">
            <defs>
                <!-- Background Grid -->
                <pattern id="grid" 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"/>
                    <circle cx="0" cy="0" r="1" fill="rgba(255,255,255,0.1)"/>
                </pattern>

                <!-- Gradients -->
                <linearGradient id="fluid-grad" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#00F0FF" stop-opacity="0.95"/>
                    <stop offset="100%" stop-color="#0284C7" stop-opacity="0.8"/>
                </linearGradient>

                <linearGradient id="cw-grad" x1="0" y1="0" x2="1" y2="1">
                    <stop offset="0%" stop-color="#700B27"/>
                    <stop offset="100%" stop-color="#E11D48"/>
                </linearGradient>

                <linearGradient id="frame-grad" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#94A3B8"/>
                    <stop offset="100%" stop-color="#475569"/>
                </linearGradient>

                <!-- Markers -->
                <marker id="arrow-down" markerWidth="10" markerHeight="10" refX="5" refY="10" orient="auto">
                    <path d="M 0 0 L 5 10 L 10 0 Z" fill="var(--accent-red)"/>
                </marker>
                
                <marker id="arrow-up" markerWidth="10" markerHeight="10" refX="5" refY="0" orient="auto">
                    <path d="M 0 10 L 5 0 L 10 10 Z" fill="var(--accent-green)"/>
                </marker>
            </defs>

            <!-- Grid Background -->
            <rect width="100%" height="100%" fill="url(#grid)" />

            <!-- Pivot / Center of Curvature Visualization -->
            <g transform="translate(600, 360)" opacity="0.6">
                <line x1="0" y1="-800" x2="0" y2="800" stroke="rgba(255,255,255,0.1)" stroke-width="1" stroke-dasharray="10 5"/>
                <line x1="-15" y1="0" x2="15" y2="0" stroke="var(--accent-cyan)" stroke-width="1.5"/>
                <line x1="0" y1="-15" x2="0" y2="15" stroke="var(--accent-cyan)" stroke-width="1.5"/>
                <circle cx="0" cy="0" r="4" fill="var(--accent-cyan)" class="glow"/>
                <text x="12" y="-12" fill="var(--accent-cyan)" font-size="12" letter-spacing="1">曲率中心(几何支点)</text>
                
                <!-- Center line indicator -->
                <rect x="-70" y="-300" width="140" height="24" fill="var(--bg-color)" stroke="rgba(255,255,255,0.1)" rx="12"/>
                <text x="0" y="-283" fill="var(--text-muted)" font-size="12" text-anchor="middle">系统临界翻转线</text>
            </g>

            <!-- IFR Alert Overlay -->
            <g id="ifr-alert" transform="translate(600, 120)" opacity="0" style="transition: opacity 0.3s ease;">
                <rect x="-150" y="-20" width="300" height="40" fill="rgba(255, 51, 102, 0.15)" stroke="var(--accent-red)" stroke-width="1.5" rx="20"/>
                <text x="0" y="4" fill="var(--accent-red)" font-size="14" font-weight="bold" text-anchor="middle" alignment-baseline="middle" class="pulse">
                    ⚠ 重心越界,触发自驱倾覆排浆
                </text>
            </g>

            <!-- Base Structure -->
            <g id="ground-structures">
                <!-- Ground Rail -->
                <rect x="100" y="520" width="1000" height="12" fill="#1E293B" rx="6"/>
                <path d="M 150 532 L 130 570 L 1070 570 L 1050 532 Z" fill="#0F172A" opacity="0.8"/>
                
                <!-- Left Damper (Buffer) -->
                <g transform="translate(250, 465)">
                    <rect x="-20" y="0" width="40" height="55" fill="#064E3B" stroke="var(--accent-green)" stroke-width="1.5" rx="4"/>
                    <!-- Spring inside damper visually -->
                    <path d="M -10 10 L 10 15 L -10 25 L 10 30 L -10 40 L 10 45" fill="none" stroke="var(--accent-green)" stroke-width="2" opacity="0.5"/>
                    <rect x="-8" y="-15" width="16" height="15" fill="var(--accent-green)" rx="2"/>
                    <!-- Callout -->
                    <polyline points="-25,20 -60,0 -120,0" fill="none" stroke="var(--accent-green)" stroke-width="1.5" opacity="0.7"/>
                    <text x="-125" y="-5" fill="var(--accent-green)" font-size="13" text-anchor="end">阻尼缓冲器</text>
                </g>

                <!-- Right Limit Pin -->
                <g transform="translate(900, 440)">
                    <rect x="-15" y="0" width="30" height="80" fill="#334155" stroke="#64748B" stroke-width="1.5" rx="4"/>
                    <rect x="-20" y="0" width="40" height="10" fill="#64748B" rx="2"/>
                    <!-- Callout -->
                    <polyline points="20,20 60,0 120,0" fill="none" stroke="#94A3B8" stroke-width="1.5" opacity="0.7"/>
                    <text x="125" y="-5" fill="#E2E8F0" font-size="13" text-anchor="start">水平限位销</text>
                </g>
            </g>

            <!-- Spilling Particles Layer (Rendered underneath frame but moves freely) -->
            <g id="spill-particles"></g>

            <!-- Main Rotating Assembly -->
            <!-- Rotation pivot is exactly at the geometric center of curvature (600, 360) -->
            <g id="rotating-assembly" transform="rotate(0, 600, 360)">
                
                <!-- Rocker Base (Curved Arc sliding on flat plane conceptually, mathematically pivots around center) -->
                <g id="rocker-base">
                    <path d="M 450 440 A 160 160 0 0 0 750 440 L 730 400 L 470 400 Z" fill="rgba(0, 240, 255, 0.1)" stroke="var(--accent-cyan)" stroke-width="2"/>
                    <path d="M 470 400 L 450 440 M 730 400 L 750 440 M 520 400 L 505 450 M 680 400 L 695 450 M 600 400 L 600 456" stroke="var(--accent-cyan)" stroke-width="1" opacity="0.4"/>
                    <!-- Callout -->
                    <polyline points="630,452 680,500 780,500" fill="none" stroke="var(--accent-cyan)" stroke-width="1.5" opacity="0.7"/>
                    <text x="785" y="495" fill="var(--accent-cyan)" font-size="13" text-anchor="start">底部弧形摇座</text>
                </g>

                <!-- Screen Frame -->
                <rect x="200" y="370" width="760" height="30" fill="url(#frame-grad)" stroke="#CBD5E1" stroke-width="1" rx="8"/>
                
                <!-- Screen Mesh -->
                <line x1="220" y1="370" x2="940" y2="370" stroke="#F8FAFC" stroke-width="5" class="mesh-line"/>

                <!-- Counterweight System -->
                <g transform="translate(850, 260)">
                    <!-- Support beam -->
                    <rect x="35" y="60" width="20" height="50" fill="#475569"/>
                    <path d="M 20 110 L 70 110 L 60 70 L 30 70 Z" fill="#334155"/>
                    
                    <!-- Weight Block -->
                    <rect x="0" y="0" width="90" height="70" fill="url(#cw-grad)" stroke="var(--accent-red)" stroke-width="2" rx="6" class="glow-red"/>
                    <text x="45" y="30" fill="#FFF" font-size="18" font-weight="900" text-anchor="middle">200kg</text>
                    <text x="45" y="50" fill="#FECDD3" font-size="12" font-weight="bold" text-anchor="middle">配重块</text>
                    
                    <!-- Callout -->
                    <polyline points="95,35 130,10 200,10" fill="none" stroke="var(--accent-red)" stroke-width="1.5" opacity="0.7"/>
                    <text x="205" y="15" fill="var(--accent-red)" font-size="13" text-anchor="start">提供初始恢复力矩</text>
                </g>

                <!-- Dynamic Fluid Blob -->
                <path id="fluid-blob" d="" fill="url(#fluid-grad)" class="glow"/>

                <!-- Forces Visualization Vectors -->
                <!-- Fluid Gravity Vector -->
                <g id="force-fluid" opacity="0">
                    <line x1="0" y1="0" x2="0" y2="80" stroke="var(--accent-cyan)" stroke-width="3" marker-end="url(#arrow-down)"/>
                    <text x="-10" y="45" fill="var(--accent-cyan)" font-size="14" font-weight="bold" text-anchor="end">浆液自重</text>
                </g>

                <!-- CG (Center of Gravity) Dynamic Marker -->
                <!-- The marker translates along the X axis relative to the rotating frame -->
                <g id="cg-marker" transform="translate(680, 420)">
                    <line id="cg-projection" x1="0" y1="-80" x2="0" y2="0" stroke="var(--accent-amber)" stroke-width="2" stroke-dasharray="4 4"/>
                    
                    <!-- Target icon -->
                    <circle cx="0" cy="0" r="14" fill="none" stroke="#FFF" stroke-width="2"/>
                    <path d="M -20 0 L 20 0 M 0 -20 L 0 20" stroke="#FFF" stroke-width="2"/>
                    <path d="M 0 0 L 14 0 A 14 14 0 0 1 0 14 Z" fill="#FFF"/>
                    <path d="M 0 0 L -14 0 A 14 14 0 0 1 0 -14 Z" fill="#FFF"/>
                    
                    <rect x="-40" y="20" width="80" height="24" fill="var(--bg-color)" rx="4" stroke="rgba(255,255,255,0.2)"/>
                    <text x="0" y="36" fill="#FFF" font-size="13" font-weight="bold" text-anchor="middle">系统重心</text>
                </g>
                
            </g>

            <!-- Decorative incoming slurry flow -->
            <path id="incoming-flow" d="M 320 0 L 340 0 C 340 150, 350 250, 330 360 C 310 250, 320 150, 320 0 Z" fill="url(#fluid-grad)" opacity="0.3"/>

        </svg>
    </div>

    <script>
        // Physics & Animation Configuration
        const config = {
            pivotX: 600,
            massCW: 200,
            posCW: 895, // Center of counterweight X
            massFrame: 400,
            posFrame: 580, // Center of empty frame X
            fluidMaxMass: 400,
            fluidCenterBaseX: 340, // Where fluid roughly centers
            maxAngle: -4, // Degrees
            fillRate: 1.8,
            drainRate: 4.5
        };

        // State Variables
        let state = {
            fluidMass: 0,
            angle: 0,
            phase: 'fill', // fill -> tilt -> drain -> reset
            autoPlay: true,
            userInteracting: false,
            cgX: 0
        };

        // DOM Elements
        const els = {
            rotatingAssembly: document.getElementById('rotating-assembly'),
            fluidBlob: document.getElementById('fluid-blob'),
            cgMarker: document.getElementById('cg-marker'),
            cgProjection: document.getElementById('cg-projection'),
            forceFluid: document.getElementById('force-fluid'),
            ifrAlert: document.getElementById('ifr-alert'),
            uiAngle: document.getElementById('ui-angle'),
            uiCg: document.getElementById('ui-cg'),
            uiStatus: document.getElementById('ui-status'),
            uiMassLabel: document.getElementById('ui-mass-label'),
            slider: document.getElementById('massSlider'),
            incomingFlow: document.getElementById('incoming-flow'),
            particlesContainer: document.getElementById('spill-particles')
        };

        // Calculate Base CG (empty)
        const baseMoment = (config.massCW * config.posCW) + (config.massFrame * config.posFrame);
        const baseMass = config.massCW + config.massFrame;

        // Particle System for Spillage
        const particles = [];
        for (let i = 0; i < 30; i++) {
            let p = document.createElementNS("http://www.w3.org/2000/svg", "circle");
            p.setAttribute("r", Math.random() * 4 + 2);
            p.setAttribute("fill", "#00F0FF");
            p.setAttribute("opacity", "0");
            els.particlesContainer.appendChild(p);
            particles.push({ el: p, active: false, x: 0, y: 0, vx: 0, vy: 0 });
        }

        let interactionTimeout = null;

        // Slider Interaction
        els.slider.addEventListener('input', (e) => {
            state.userInteracting = true;
            state.fluidMass = parseFloat(e.target.value);
            
            // Infer phase logically based on mass to resume correctly later
            if (state.fluidMass > 280) state.phase = 'tilt';
            else if (state.fluidMass < 50) state.phase = 'fill';
            
            clearTimeout(interactionTimeout);
            interactionTimeout = setTimeout(() => {
                state.userInteracting = false;
            }, 3000);
        });

        function updateSpillParticles() {
            // Emit new particles if tilted and draining
            if (state.angle < -2 && state.fluidMass > 30 && Math.random() > 0.2) {
                let p = particles.find(p => !p.active);
                if (p) {
                    p.active = true;
                    // Spawn near the left edge of the tilted frame
                    // Base left edge is x=200, y=370. When rotated -4 deg around (600,360)
                    p.x = 200 + Math.random() * 40;
                    p.y = 350 + Math.random() * 20;
                    p.vx = -2 - Math.random() * 3;
                    p.vy = -1 - Math.random() * 2;
                    p.el.setAttribute("opacity", "0.8");
                }
            }
            
            // Update active particles
            particles.forEach(p => {
                if (p.active) {
                    p.vy += 0.4; // Gravity
                    p.x += p.vx;
                    p.y += p.vy;
                    p.el.setAttribute("cx", p.x);
                    p.el.setAttribute("cy", p.y);
                    
                    // Floor collision (y=520)
                    if (p.y > 520) {
                        p.vy *= -0.3;
                        p.vx *= 0.7;
                        p.y = 520;
                    }
                    
                    // Deactivate
                    if (p.x < 0 || p.y > 530 || (Math.abs(p.vx) < 0.1 && Math.abs(p.vy) < 0.1)) {
                        p.active = false;
                        p.el.setAttribute("opacity", "0");
                    }
                }
            });
        }

        function updatePhysics() {
            if (!state.userInteracting) {
                // Auto-play state machine
                switch (state.phase) {
                    case 'fill':
                        state.fluidMass += config.fillRate;
                        if (state.fluidMass >= 320) state.phase = 'tilt'; // Trigger cascade
                        break;
                    case 'tilt':
                        // Mass continues to increase slightly while tilting starts
                        state.fluidMass += config.fillRate * 0.5;
                        if (state.angle <= config.maxAngle + 0.2) state.phase = 'drain';
                        break;
                    case 'drain':
                        state.fluidMass -= config.drainRate;
                        if (state.fluidMass <= 40) state.phase = 'reset';
                        break;
                    case 'reset':
                        // Ensure it's fully empty and returning
                        if (state.angle >= -0.1) state.phase = 'fill';
                        break;
                }
                
                // Clamp
                if (state.fluidMass < 0) state.fluidMass = 0;
                if (state.fluidMass > config.fluidMaxMass) state.fluidMass = config.fluidMaxMass;
                
                // Sync UI Slider
                els.slider.value = state.fluidMass;
            }

            // 1. Calculate Center of Gravity (CG)
            // Fluid mass center shifts slightly right as it grows
            let currentFluidCenterX = config.fluidCenterBaseX + (state.fluidMass * 0.2);
            let totalMoment = baseMoment + (state.fluidMass * currentFluidCenterX);
            let totalMass = baseMass + state.fluidMass;
            state.cgX = totalMoment / totalMass;

            // 2. Determine Target Angle based on CG crossing the pivot
            let targetAngle = 0;
            if (state.cgX < config.pivotX) {
                // CG crossed left of pivot. Generates counter-clockwise torque.
                // Map the distance crossed to a tilt angle
                let crossover = config.pivotX - state.cgX; // Positive value
                targetAngle = crossover * -0.2; 
                if (targetAngle < config.maxAngle) targetAngle = config.maxAngle;
            }

            // 3. Smooth Angle Transition (Spring/Damping)
            state.angle += (targetAngle - state.angle) * 0.12;

            // Render Updates
            render();
            updateSpillParticles();

            requestAnimationFrame(updatePhysics);
        }

        function render() {
            // Transform main assembly
            els.rotatingAssembly.setAttribute('transform', `rotate(${state.angle}, ${config.pivotX}, 360)`);

            // Update CG Marker Position
            els.cgMarker.setAttribute('transform', `translate(${state.cgX}, 420)`);
            
            // Visual logic for CG projection color and IFR Alert
            if (state.cgX < config.pivotX) {
                els.cgProjection.setAttribute('stroke', 'var(--accent-red)');
                els.ifrAlert.style.opacity = '1';
            } else {
                els.cgProjection.setAttribute('stroke', 'var(--accent-amber)');
                els.ifrAlert.style.opacity = '0';
            }

            // Update Fluid Visuals (Organic shape generation)
            // Starts at x=210 on the screen frame
            let fWidth = Math.min(Math.max(state.fluidMass * 0.7, 0), 300);
            let fHeight = Math.min(state.fluidMass * 0.18, 60);
            
            if (state.fluidMass > 5) {
                // Generate a smooth blob
                let path = `M 210 370 
                            L ${210 + fWidth} 370 
                            C ${210 + fWidth} ${370 - fHeight}, 210 ${370 - fHeight}, 210 370 Z`;
                els.fluidBlob.setAttribute('d', path);
                els.fluidBlob.style.opacity = 1;
                
                // Update Fluid Force Arrow
                els.forceFluid.setAttribute('opacity', Math.min(state.fluidMass / 100, 1));
                // Position arrow at center of fluid mass
                els.forceFluid.setAttribute('transform', `translate(${210 + fWidth/2}, ${370 - fHeight - 60})`);
            } else {
                els.fluidBlob.style.opacity = 0;
                els.forceFluid.setAttribute('opacity', 0);
            }

            // Update Incoming Flow Opacity
            let flowOp = (state.phase === 'fill' || state.phase === 'tilt') && !state.userInteracting ? 0.6 : 0.0;
            els.incomingFlow.setAttribute('opacity', flowOp);

            // Update HUD Texts
            els.uiAngle.innerText = state.angle.toFixed(1) + '°';
            els.uiCg.innerText = state.cgX.toFixed(1);
            els.uiMassLabel.innerText = Math.round(state.fluidMass) + ' kg';

            // Status Logic
            if (state.cgX < config.pivotX) {
                els.uiStatus.innerText = "倾覆排浆";
                els.uiStatus.style.background = "rgba(255, 51, 102, 0.1)";
                els.uiStatus.style.color = "var(--accent-red)";
                els.uiAngle.style.color = "var(--accent-red)";
            } else {
                els.uiStatus.innerText = "水平运行";
                els.uiStatus.style.background = "rgba(16, 185, 129, 0.1)";
                els.uiStatus.style.color = "var(--accent-green)";
                els.uiAngle.style.color = "var(--accent-amber)";
            }
        }

        // Initialize and Start
        window.addEventListener('DOMContentLoaded', () => {
            updatePhysics();
        });

    </script>
</body>
</html>
积分规则:每轮对话扣10分