分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>超前预判防跑浆控制系统 (IFR)</title>
<style>
    :root {
        --bg-dark: #0a0d14;
        --panel-bg: rgba(16, 22, 32, 0.85);
        --grid-color: rgba(45, 60, 85, 0.2);
        --cyan-glow: #00f0ff;
        --alert-red: #ff3366;
        --warn-amber: #ffb000;
        --text-main: #e2e8f0;
        --text-muted: #94a3b8;
        --pipe-color: #1e293b;
        --mud-color: #d97706;
        --mech-metal: #64748b;
        
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    }

    body, html {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
        background-color: var(--bg-dark);
        display: flex;
        justify-content: center;
        align-items: center;
        overflow: hidden;
        color: var(--text-main);
    }

    /* Container setup for center focus */
    #animation-container {
        position: relative;
        width: 100%;
        max-width: 1200px;
        aspect-ratio: 16 / 9;
        background: radial-gradient(circle at center, #111827 0%, #030712 100%);
        border: 1px solid rgba(255, 255, 255, 0.05);
        box-shadow: 0 20px 50px rgba(0,0,0,0.5);
        overflow: hidden;
    }

    /* Corner Data Panels - strict adherence to small size and non-obstruction */
    .data-panel {
        position: absolute;
        background: var(--panel-bg);
        border: 1px solid rgba(0, 240, 255, 0.2);
        backdrop-filter: blur(10px);
        padding: 12px 16px;
        border-radius: 8px;
        z-index: 10;
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
    }

    #plc-dashboard {
        top: 20px;
        right: 20px;
        min-width: 200px;
    }

    #legend-panel {
        bottom: 20px;
        left: 20px;
        max-width: 280px;
    }

    .panel-title {
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: 1px;
        color: var(--cyan-glow);
        margin-bottom: 8px;
        border-bottom: 1px solid rgba(0, 240, 255, 0.2);
        padding-bottom: 4px;
    }

    .data-row {
        display: flex;
        justify-content: space-between;
        margin: 6px 0;
        font-size: 12px;
    }

    .data-label { color: var(--text-muted); }
    .data-value { 
        font-family: 'Courier New', Courier, monospace; 
        font-weight: bold;
        transition: color 0.3s;
    }

    .desc-text {
        font-size: 11px;
        line-height: 1.5;
        color: var(--text-muted);
    }
    .highlight { color: var(--cyan-glow); font-weight: bold; }

    /* SVG Elements Styling */
    svg {
        width: 100%;
        height: 100%;
        display: block;
    }

    /* Animations */
    .data-stream {
        stroke-dasharray: 4 8;
        animation: flowData 1s linear infinite;
    }
    
    .mud-flow {
        stroke-dasharray: 10 15;
        animation: flowData 0.5s linear infinite reverse;
        transition: stroke-width 0.5s, stroke-dasharray 0.5s;
    }

    @keyframes flowData {
        to { stroke-dashoffset: -24; }
    }

    @keyframes pulseGlow {
        0%, 100% { filter: drop-shadow(0 0 4px var(--cyan-glow)); }
        50% { filter: drop-shadow(0 0 12px var(--cyan-glow)); }
    }
    @keyframes alertGlow {
        0%, 100% { filter: drop-shadow(0 0 6px var(--alert-red)); }
        50% { filter: drop-shadow(0 0 16px var(--alert-red)); }
    }

    /* Dynamic Classes controlled by JS */
    .status-normal .indicator { fill: var(--cyan-glow); }
    .status-normal .data-value.dqdt { color: var(--cyan-glow); }
    
    .status-pre .indicator { fill: var(--warn-amber); }
    .status-pre .data-value.dqdt { color: var(--warn-amber); }
    .status-pre .sensor-ring { stroke: var(--warn-amber); filter: drop-shadow(0 0 8px var(--warn-amber)); }
    
    .status-emergency .indicator { fill: var(--alert-red); animation: alertGlow 1s infinite; }
    .status-emergency .data-value.dqdt { color: var(--alert-red); }
    .status-emergency .sensor-ring { stroke: var(--alert-red); animation: alertGlow 0.5s infinite; }

    /* Smooth transitions for mechanical parts */
    #shaker-screen {
        transform-origin: 750px 420px;
        transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
    }
    #weir-plate {
        transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
    }
    #servo-rod {
        transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
    }
    #mud-pool {
        transition: d 0.6s ease-in-out, fill 0.6s;
    }
    .data-line {
        transition: stroke 0.3s;
    }

    #system-status-msg {
        position: absolute;
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
        font-size: 14px;
        font-weight: bold;
        letter-spacing: 2px;
        padding: 6px 16px;
        border-radius: 20px;
        background: rgba(0, 240, 255, 0.1);
        border: 1px solid var(--cyan-glow);
        color: var(--cyan-glow);
        transition: all 0.3s;
        z-index: 10;
        pointer-events: none;
    }

</style>
</head>
<body>

<div id="animation-container" class="status-normal">
    
    <!-- Top Center Status Badge -->
    <div id="system-status-msg">系统状态:平稳运行</div>

    <!-- Top Right PLC Data -->
    <div id="plc-dashboard" class="data-panel">
        <div class="panel-title">PLC 预测控制中枢</div>
        <div class="data-row">
            <span class="data-label">实时流量 (Q):</span>
            <span class="data-value" id="val-flow">45.0 L/s</span>
        </div>
        <div class="data-row">
            <span class="data-label">流量变率 (dQ/dt):</span>
            <span class="data-value dqdt" id="val-dqdt">0.02 L/s²</span>
        </div>
        <div class="data-row">
            <span class="data-label">进料压力:</span>
            <span class="data-value" id="val-pressure">102 kPa</span>
        </div>
        <hr style="border: 0; border-top: 1px solid rgba(255,255,255,0.1); margin: 8px 0;">
        <div class="data-row">
            <span class="data-label">预警阈值:</span>
            <span class="data-value" style="color:var(--text-muted)">0.6 L/s²</span>
        </div>
        <div class="data-row">
            <span class="data-label">伺服抬升量:</span>
            <span class="data-value" id="val-lift">0 mm</span>
        </div>
        <div class="data-row">
            <span class="data-label">堰板开度:</span>
            <span class="data-value" id="val-weir">100%</span>
        </div>
    </div>

    <!-- Bottom Left Legend / IFR description -->
    <div id="legend-panel" class="data-panel">
        <div class="panel-title">TRIZ 最终理想解 (IFR)</div>
        <div class="desc-text">
            利用<span class="highlight">流量前兆特征 (dQ/dt)</span>,在跑浆发生前实现超前干预。<br>
            • <b style="color:var(--cyan-glow)">正常:</b> 流量平稳,全开度<br>
            • <b style="color:var(--warn-amber)">预调节:</b> dQ/dt>0.6,提前2s抬升30mm,关堰板至80%<br>
            • <b style="color:var(--alert-red)">紧急兜底:</b> 液位超限,全行程抬升120mm,关至40%<br>
            <i style="opacity:0.7; margin-top:4px; display:block;">* 巧妙利用现有资源,极小化系统改变实现零跑浆。</i>
        </div>
    </div>

    <!-- Main SVG Canvas -->
    <svg viewBox="0 0 1000 562.5" preserveAspectRatio="xMidYMid meet">
        <defs>
            <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                <feGaussianBlur stdDeviation="3" result="blur" />
                <feComposite in="SourceGraphic" in2="blur" operator="over" />
            </filter>
            
            <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                <path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="0.5"/>
            </pattern>
            
            <!-- Mud Gradient -->
            <linearGradient id="mud-grad" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0%" stop-color="#f59e0b" stop-opacity="0.9"/>
                <stop offset="100%" stop-color="#b45309" stop-opacity="0.9"/>
            </linearGradient>
            
            <!-- Metal Gradient -->
            <linearGradient id="metal" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0%" stop-color="#94a3b8"/>
                <stop offset="50%" stop-color="#475569"/>
                <stop offset="100%" stop-color="#1e293b"/>
            </linearGradient>
        </defs>

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

        <!-- Static Background Structures -->
        <!-- Pipeline Base -->
        <path d="M 0 250 L 150 250 L 150 350 L 0 350 Z" fill="var(--pipe-color)" stroke="#334155" stroke-width="2"/>
        <path d="M 150 250 C 200 250, 220 280, 250 300 L 250 380 C 200 360, 150 350, 150 350 Z" fill="var(--pipe-color)" stroke="#334155" stroke-width="2"/>
        
        <!-- Tank Base -->
        <path d="M 250 380 L 250 480 L 800 480 L 800 420 L 750 420 L 750 460 L 270 460 Z" fill="#1e293b" stroke="#475569" stroke-width="2"/>
        
        <!-- ================= MUD DYNAMICS ================= -->
        <!-- Incoming Mud Stream -->
        <path id="mud-stream" d="M 0 300 L 150 300 C 180 300, 210 320, 230 350 L 250 400" fill="none" stroke="var(--mud-color)" stroke-width="20" stroke-linecap="round" class="mud-flow"/>
        
        <!-- Dynamic Mud Pool -->
        <!-- Start state: small pool -->
        <path id="mud-pool" d="M 250 460 L 250 440 Q 500 440, 750 420 L 750 460 Z" fill="url(#mud-grad)" />

        <!-- ================= MECHANICAL PARTS ================= -->
        
        <!-- Shaker Screen (Rotates) -->
        <g id="shaker-screen">
            <line x1="250" y1="420" x2="750" y2="420" stroke="#cbd5e1" stroke-width="6" stroke-linecap="round"/>
            <!-- Mesh pattern -->
            <path d="M 300 420 L 300 430 M 350 420 L 350 430 M 400 420 L 400 430 M 450 420 L 450 430 M 500 420 L 500 430 M 550 420 L 550 430 M 600 420 L 600 430 M 650 420 L 650 430 M 700 420 L 700 430" stroke="#64748b" stroke-width="2"/>
            <!-- Hinge -->
            <circle cx="750" cy="420" r="8" fill="#e2e8f0" stroke="#475569" stroke-width="3"/>
        </g>

        <!-- Servo Mechanism -->
        <g id="servo-group" transform="translate(250, 480)">
            <rect x="-15" y="0" width="30" height="60" fill="url(#metal)" rx="4"/>
            <!-- Servo Rod (moves up/down based on screen lift) -->
            <rect id="servo-rod" x="-6" y="-60" width="12" height="60" fill="#cbd5e1"/>
            <circle cx="0" cy="30" r="8" fill="var(--cyan-glow)" opacity="0.5"/>
            <!-- Encoder detail -->
            <rect x="15" y="10" width="10" height="20" fill="#334155" rx="2"/>
            <circle cx="20" cy="20" r="3" fill="var(--cyan-glow)"/>
        </g>

        <!-- Weir Plate (Moves Down) -->
        <g id="weir-group" transform="translate(230, 220)">
            <rect x="0" y="0" width="10" height="80" fill="#475569" rx="2"/>
            <rect id="weir-plate" x="-2" y="40" width="14" height="60" fill="url(#metal)" rx="2"/>
        </g>


        <!-- ================= SENSORS & PLC ================= -->
        
        <!-- Electromagnetic Flowmeter (DN200) -->
        <g id="sensor-flow" transform="translate(100, 300)">
            <rect x="-20" y="-60" width="40" height="120" fill="none" stroke="#64748b" stroke-width="4" rx="4"/>
            <ellipse class="sensor-ring" cx="0" cy="0" rx="10" ry="40" fill="none" stroke="var(--cyan-glow)" stroke-width="3"/>
            <rect x="-15" y="-75" width="30" height="15" fill="#334155" rx="2"/>
            <circle class="indicator" cx="0" cy="-67" r="3"/>
            <text x="0" y="-85" font-size="10" fill="var(--text-muted)" text-anchor="middle">电磁流量计</text>
        </g>

        <!-- Pressure Transmitter -->
        <g id="sensor-pressure" transform="translate(60, 250)">
            <rect x="-10" y="-30" width="20" height="30" fill="#334155" rx="2"/>
            <line x1="0" y1="0" x2="0" y2="10" stroke="#cbd5e1" stroke-width="3"/>
            <circle class="indicator" cx="0" cy="-15" r="3"/>
            <text x="0" y="-38" font-size="10" fill="var(--text-muted)" text-anchor="middle">压力变送器</text>
        </g>
        
        <!-- Level Sensor (Alarm Line) -->
        <g id="sensor-level" transform="translate(350, 350)">
            <rect x="-8" y="-20" width="16" height="20" fill="#334155" rx="2"/>
            <line x1="0" y1="0" x2="0" y2="40" stroke="#94a3b8" stroke-width="2" stroke-dasharray="2 2"/>
            <!-- Alarm Threshold Line -->
            <line x1="-30" y1="40" x2="150" y2="40" stroke="var(--alert-red)" stroke-width="1" stroke-dasharray="4 4" opacity="0.6"/>
            <text x="60" y="35" font-size="10" fill="var(--alert-red)" opacity="0.8">紧急动作液位阈值</text>
            <circle class="indicator" cx="0" cy="-10" r="3"/>
        </g>

        <!-- PLC Unit (Abstracted representation connected to dashboard) -->
        <g id="plc-unit" transform="translate(500, 150)">
            <rect x="-40" y="-30" width="80" height="60" fill="rgba(15, 23, 42, 0.8)" stroke="var(--cyan-glow)" stroke-width="1" rx="6" filter="url(#glow-cyan)"/>
            <text x="0" y="-5" font-size="12" fill="#fff" text-anchor="middle" font-weight="bold">PLC</text>
            <text x="0" y="12" font-size="9" fill="var(--cyan-glow)" text-anchor="middle">预测模型</text>
            <circle class="indicator" cx="-25" cy="-15" r="3"/>
            <circle class="indicator" cx="-15" cy="-15" r="3"/>
            <circle class="indicator" cx="-5" cy="-15" r="3"/>
        </g>

        <!-- ================= DATA PATHS (Animated) ================= -->
        <!-- Sensors to PLC -->
        <path id="path-flow-plc" d="M 100 225 L 100 150 L 460 150" fill="none" stroke="var(--cyan-glow)" stroke-width="1.5" class="data-line data-stream" opacity="0.6"/>
        <path id="path-level-plc" d="M 350 330 L 350 160 L 460 160" fill="none" stroke="var(--cyan-glow)" stroke-width="1.5" class="data-line data-stream" opacity="0.6"/>
        
        <!-- PLC to Actuators -->
        <!-- To Servo -->
        <path id="path-plc-servo" d="M 540 160 L 780 160 L 780 500 L 280 500 L 280 490" fill="none" stroke="var(--warn-amber)" stroke-width="1.5" class="data-line data-stream" opacity="0.6"/>
        <!-- To Weir -->
        <path id="path-plc-weir" d="M 500 120 L 500 80 L 235 80 L 235 220" fill="none" stroke="var(--warn-amber)" stroke-width="1.5" class="data-line data-stream" opacity="0.6"/>

    </svg>
</div>

<script>
    // State Management for the Animation Loop
    // States: 
    // 0: Normal
    // 1: Spike (dQ/dt > 0.6) -> Pre-action
    // 2: Extreme Spike -> Level Alarm -> Emergency
    // 3: Recovery -> back to Normal

    const elements = {
        container: document.getElementById('animation-container'),
        statusMsg: document.getElementById('system-status-msg'),
        valFlow: document.getElementById('val-flow'),
        valDqdt: document.getElementById('val-dqdt'),
        valPressure: document.getElementById('val-pressure'),
        valLift: document.getElementById('val-lift'),
        valWeir: document.getElementById('val-weir'),
        screen: document.getElementById('shaker-screen'),
        weirPlate: document.getElementById('weir-plate'),
        servoRod: document.getElementById('servo-rod'),
        mudPool: document.getElementById('mud-pool'),
        mudStream: document.getElementById('mud-stream'),
        pathFlowPlc: document.getElementById('path-flow-plc'),
        pathLevelPlc: document.getElementById('path-level-plc'),
    };

    let time = 0;
    
    // Physical simulation parameters (visual representation)
    let currentLift = 0; // 0, 30, 120 mm
    let targetLift = 0;
    let currentWeir = 100; // 100, 80, 40 %
    let targetWeir = 100;
    
    // Animation sequencer
    function updateState() {
        const t = time % 16; // 16 seconds loop

        if (t === 0) {
            // NORMAL STATE
            setMode('normal', '系统状态:平稳运行', 'var(--cyan-glow)', 'var(--cyan-glow)');
            updateData(45.0, 0.02, 102, 0, 100);
            animateMechanics(0, 100, 440);
            updateMudStream(20, '10 15');
        } 
        else if (t === 3) {
            // SPIKE DETECTED
            setMode('pre', '检测到流量突增前兆!', 'var(--warn-amber)', 'var(--warn-amber)');
            updateData(75.5, 0.85, 128, 0, 100);
            updateMudStream(35, '5 8');
        }
        else if (t === 4) {
            // PRE-ACTION (IFR Core)
            setMode('pre', '预调节启动:提前干预!', 'var(--warn-amber)', 'var(--warn-amber)');
            updateData(78.2, 0.65, 130, 30, 80);
            animateMechanics(30, 80, 410); // Screen pivots up, weir closes slightly, mud rises but controlled
        }
        else if (t === 8) {
            // EXTREME SURGE (Alarm hit)
            setMode('emergency', '液位触警:紧急兜底启动!', 'var(--alert-red)', 'var(--alert-red)');
            updateData(95.0, 1.20, 145, 30, 80);
            updateMudStream(45, '2 4');
            elements.pathLevelPlc.style.stroke = 'var(--alert-red)';
        }
        else if (t === 9) {
            // EMERGENCY ACTION
            updateData(92.0, 0.10, 140, 120, 40);
            animateMechanics(120, 40, 390); // Screen max lift, weir closes to 40%
        }
        else if (t === 13) {
            // RECOVERY
            setMode('normal', '工况平稳:延时复位中', 'var(--cyan-glow)', 'var(--cyan-glow)');
            updateData(50.0, -0.40, 105, 120, 40);
            updateMudStream(25, '10 15');
            elements.pathLevelPlc.style.stroke = 'var(--cyan-glow)';
        }
        else if (t === 14) {
            // GRADUAL RESET
            updateData(46.0, -0.05, 102, 0, 100);
            animateMechanics(0, 100, 440);
        }

        time++;
    }

    function setMode(modeClass, msg, color, strokeColor) {
        elements.container.className = `status-${modeClass}`;
        elements.statusMsg.textContent = msg;
        elements.statusMsg.style.color = color;
        elements.statusMsg.style.borderColor = color;
        elements.statusMsg.style.background = `rgba(${modeClass === 'emergency' ? '255,51,102' : (modeClass === 'pre' ? '255,176,0' : '0,240,255')}, 0.1)`;
        elements.pathFlowPlc.style.stroke = strokeColor;
    }

    function updateData(flow, dqdt, press, lift, weir) {
        // Add tiny random fluctuations to make it look alive
        const jitter = () => (Math.random() * 0.4 - 0.2);
        
        elements.valFlow.textContent = (flow + jitter()).toFixed(1) + ' L/s';
        elements.valDqdt.textContent = (dqdt + (jitter()*0.1)).toFixed(2) + ' L/s²';
        elements.valPressure.textContent = Math.round(press + jitter()*2) + ' kPa';
        
        elements.valLift.textContent = lift + ' mm';
        elements.valWeir.textContent = weir + '%';
    }

    function updateMudStream(width, dasharray) {
        elements.mudStream.style.strokeWidth = width;
        elements.mudStream.style.strokeDasharray = dasharray;
    }

    function animateMechanics(liftMm, weirPct, mudHeight) {
        // Map 0-120mm lift to rotation angle
        // Screen pivot is at x=750, y=420. Front is at x=250.
        // Length = 500. Lift of 120mm means sin(theta) = 120/500 -> theta ~ 13.8 deg.
        // We'll scale visual rotation slightly for better aesthetics.
        const maxAngle = 10; 
        const angle = (liftMm / 120) * maxAngle;
        elements.screen.style.transform = `rotate(${angle}deg)`;
        
        // Servo rod moves down as screen front moves up (visually linking them)
        const rodVisualShift = (liftMm / 120) * -30;
        elements.servoRod.style.transform = `translateY(${rodVisualShift}px)`;

        // Weir plate closes
        // 100% open = 0 translate. 40% open = 60% closed = move down visually
        const weirShift = (100 - weirPct) * 0.5; // visual mapping
        elements.weirPlate.style.transform = `translateY(${weirShift}px)`;

        // Mud pool physics
        // As screen rotates up, mud pools higher at the back or maintains level.
        // d="M 250 460 L 250 {mudHeight} Q 500 {mudHeight}, 750 420 L 750 460 Z"
        // Adjust the front corner to match screen lift
        const frontY = 460 - (liftMm / 120) * 35; 
        elements.mudPool.setAttribute('d', `M 250 460 L 250 ${mudHeight} Q 500 ${mudHeight + 10}, 750 420 L 750 460 Z`);
        
        // Mud color shifts slightly darker when deep/surging
        if(mudHeight < 400) {
            elements.mudPool.style.fill = 'url(#mud-grad)';
            elements.mudPool.style.opacity = '1';
        } else {
            elements.mudPool.style.fill = 'url(#mud-grad)';
            elements.mudPool.style.opacity = '0.9';
        }
    }

    // Auto-start loop on load
    document.addEventListener("DOMContentLoaded", () => {
        // Initial setup
        updateState();
        // Run sequencer every 1 second
        setInterval(updateState, 1000);
        
        // Add fast jitter loop for realistic sensor readouts (without changing state)
        setInterval(() => {
            if(time % 16 !== 0 && time % 16 !== 4 && time % 16 !== 9) { // Don't override exact frame changes
               // Add tiny visual flutter to text
               const curFlow = parseFloat(elements.valFlow.textContent);
               elements.valFlow.textContent = (curFlow + (Math.random()*0.2-0.1)).toFixed(1) + ' L/s';
            }
        }, 300);
    });

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