分享图
A
动画渲染工坊
就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>微流通单向释压阀 · 最终理想解原理动画</title>
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@300;400;600;700&family=Fira+Code:wght@300;400;500&display=swap" rel="stylesheet">
<style>
:root{
  --bg:#060a13;--surface:#0b1019;--card:#101825;--border:#1a2a42;
  --text:#c0cede;--dim:#465a73;--cyan:#00e5ff;--amber:#f59e0b;
  --pink:#ff4081;--green:#10b981;--metal:#7b8fa0;
}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--text);font-family:'Fira Code',monospace;
  min-height:100vh;display:flex;flex-direction:column;align-items:center;overflow-x:hidden}
.page{width:100%;max-width:1100px;padding:20px 16px 32px}
header{text-align:center;margin-bottom:16px}
header h1{font-family:'Chakra Petch',sans-serif;font-weight:700;font-size:1.75rem;
  color:var(--cyan);letter-spacing:.06em;text-shadow:0 0 24px rgba(0,229,255,.25)}
header .sub{font-size:.78rem;color:var(--dim);margin-top:2px;letter-spacing:.12em}
.svg-wrap{width:100%;background:var(--surface);border:1px solid var(--border);
  border-radius:12px;overflow:hidden;position:relative}
.svg-wrap svg{display:block;width:100%;height:auto}
.controls{display:flex;align-items:center;justify-content:center;gap:10px;
  margin-top:16px;flex-wrap:wrap}
.btn{font-family:'Chakra Petch',sans-serif;font-weight:600;font-size:.8rem;
  padding:9px 18px;border:1px solid var(--border);border-radius:7px;
  background:var(--card);color:var(--text);cursor:pointer;transition:all .2s;
  letter-spacing:.04em;user-select:none}
.btn:hover{border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 12px rgba(0,229,255,.18)}
.btn.primary{background:rgba(0,229,255,.12);border-color:var(--cyan);color:var(--cyan)}
.btn.primary:hover{background:rgba(0,229,255,.22);box-shadow:0 0 18px rgba(0,229,255,.28)}
.btn:disabled{opacity:.35;cursor:not-allowed;pointer-events:none}
.btn.success{border-color:var(--green);color:var(--green);background:rgba(16,185,129,.1)}
.sld{display:flex;align-items:center;gap:6px;margin-left:10px}
.sld label{font-size:.7rem;color:var(--dim);white-space:nowrap}
.sld input[type=range]{-webkit-appearance:none;width:110px;height:3px;
  background:var(--border);border-radius:2px;outline:none}
.sld input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;
  border-radius:50%;background:var(--cyan);cursor:pointer;box-shadow:0 0 8px rgba(0,229,255,.45)}
.sld .val{font-size:.72rem;color:var(--cyan);min-width:38px}
.info{margin-top:14px;padding:10px 16px;background:var(--card);border:1px solid var(--border);
  border-radius:8px;display:flex;align-items:center;gap:14px;font-size:.76rem;flex-wrap:wrap}
.steps{display:flex;gap:6px;align-items:center}
.sdot{width:7px;height:7px;border-radius:50%;background:var(--border);transition:all .35s}
.sdot.on{background:var(--cyan);box-shadow:0 0 8px rgba(0,229,255,.5)}
.sdot.ok{background:var(--green)}
.stxt{color:var(--dim);flex:1;min-width:160px}
.badge{padding:3px 9px;border-radius:4px;font-size:.66rem;font-weight:600;letter-spacing:.08em;
  background:rgba(16,185,129,.13);color:var(--green);border:1px solid rgba(16,185,129,.28);
  white-space:nowrap}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{
  animation-duration:.01ms!important;transition-duration:.01ms!important}}
</style>
</head>
<body>
<div class="page">
  <header>
    <h1>微流通单向释压阀</h1>
    <div class="sub">IDEAL FINAL RESULT &mdash; 原理动画</div>
  </header>

  <div class="svg-wrap">
    <svg id="svg" viewBox="0 0 960 640" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <!-- 金属渐变 -->
        <linearGradient id="gMetal" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#a0b0be"/><stop offset="45%" stop-color="#6d8293"/>
          <stop offset="100%" stop-color="#4a5e6e"/>
        </linearGradient>
        <linearGradient id="gMetalH" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="#7b8fa0"/><stop offset="50%" stop-color="#9db0be"/>
          <stop offset="100%" stop-color="#7b8fa0"/>
        </linearGradient>
        <!-- 硅胶渐变 -->
        <linearGradient id="gSilicone" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#ff6b9d"/><stop offset="100%" stop-color="#d81b60"/>
        </linearGradient>
        <!-- 真空辉光 -->
        <radialGradient id="gVacuum" cx=".5" cy=".45" r=".55">
          <stop offset="0%" stop-color="#00e5ff" stop-opacity=".22"/>
          <stop offset="100%" stop-color="#00e5ff" stop-opacity="0"/>
        </radialGradient>
        <!-- 发光滤镜 -->
        <filter id="fGlow"><feGaussianBlur stdDeviation="4" result="b"/>
          <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
        <filter id="fGlow2"><feGaussianBlur stdDeviation="6" result="b"/>
          <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
        <!-- 网格 -->
        <pattern id="pGrid" width="24" height="24" patternUnits="userSpaceOnUse">
          <path d="M24 0L0 0 0 24" fill="none" stroke="#12203a" stroke-width=".4"/>
        </pattern>
        <!-- 瓶内填充 -->
        <linearGradient id="gContent" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#0a1420" stop-opacity="0"/>
          <stop offset="60%" stop-color="#0f1e30" stop-opacity=".6"/>
          <stop offset="100%" stop-color="#14283c" stop-opacity=".8"/>
        </linearGradient>
      </defs>

      <!-- 背景网格 -->
      <rect width="960" height="640" fill="url(#pGrid)" opacity=".6"/>

      <!-- ====== 真空辉光 ====== -->
      <g id="vacuumGlow" opacity="1">
        <ellipse cx="480" cy="460" rx="120" ry="100" fill="url(#gVacuum)"/>
      </g>

      <!-- ====== 瓶体剖面 ====== -->
      <g id="bottle">
        <!-- 左壁 -->
        <path d="M340,360 L340,570 Q340,590 360,590 L370,590 Q370,590 370,570 L370,360"
              fill="#3a5068" stroke="#4a6580" stroke-width="1"/>
        <!-- 右壁 -->
        <path d="M590,360 L590,570 Q590,590 570,590 L560,590 Q560,590 560,570 L560,360"
              fill="#3a5068" stroke="#4a6580" stroke-width="1"/>
        <!-- 底部连接 -->
        <path d="M370,590 Q370,600 380,600 L550,600 Q560,600 560,590"
              fill="none" stroke="#4a6580" stroke-width="1.2"/>
        <!-- 内部空间 -->
        <rect x="370" y="360" width="190" height="240" fill="url(#gContent)" opacity=".5"/>
        <!-- 瓶口螺纹 (外侧) -->
        <g fill="#4a6580" stroke="none">
          <rect x="334" y="375" width="8" height="5" rx="1"/>
          <rect x="334" y="405" width="8" height="5" rx="1"/>
          <rect x="334" y="435" width="8" height="5" rx="1"/>
          <rect x="588" y="375" width="8" height="5" rx="1"/>
          <rect x="588" y="405" width="8" height="5" rx="1"/>
          <rect x="588" y="435" width="8" height="5" rx="1"/>
        </g>
        <!-- 内容物提示 -->
        <text x="465" y="560" font-size="11" fill="#2a4058" text-anchor="middle"
              font-family="'Fira Code',monospace" letter-spacing=".1em">VACUUM</text>
      </g>

      <!-- ====== 密封胶圈 ====== -->
      <rect x="345" y="354" width="270" height="7" rx="2"
            fill="#2a3848" stroke="#3a5060" stroke-width=".8" id="gasket"/>

      <!-- ====== 瓶盖剖面 ====== -->
      <g id="capGroup">
        <!-- 顶板 -->
        <rect x="320" y="310" width="320" height="30" rx="3" fill="url(#gMetalH)" stroke="#5a7a90" stroke-width="1"/>
        <!-- 左裙 -->
        <rect x="320" y="340" width="28" height="160" rx="2" fill="url(#gMetal)" stroke="#5a7a90" stroke-width=".8"/>
        <!-- 右裙 -->
        <rect x="612" y="340" width="28" height="160" rx="2" fill="url(#gMetal)" stroke="#5a7a90" stroke-width=".8"/>
        <!-- 盖裙内螺纹 -->
        <g fill="#5a7a90" stroke="none">
          <rect x="346" y="376" width="7" height="5" rx="1"/>
          <rect x="346" y="406" width="7" height="5" rx="1"/>
          <rect x="346" y="436" width="7" height="5" rx="1"/>
          <rect x="607" y="376" width="7" height="5" rx="1"/>
          <rect x="607" y="406" width="7" height="5" rx="1"/>
          <rect x="607" y="436" width="7" height="5" rx="1"/>
        </g>
        <!-- 顶板中心孔(背景) -->
        <rect x="445" y="310" width="70" height="30" fill="#0c1520" stroke="none"/>
      </g>

      <!-- ====== 硅胶微阀 ====== -->
      <g id="valveGroup">
        <!-- 阀体伞面(闭合态) -->
        <path id="valveFlange" d="M448,340 Q465,335 480,334 Q495,335 512,340 L512,338
              Q495,332 480,331 Q465,332 448,338 Z"
              fill="url(#gSilicone)" stroke="#c2185b" stroke-width=".8" opacity=".92"/>
        <!-- 阀芯(按压凸起) -->
        <g id="valveStem">
          <rect x="466" y="296" width="28" height="38" rx="6" fill="url(#gSilicone)"
                stroke="#c2185b" stroke-width=".8" opacity=".92" id="stemRect"/>
          <!-- 顶部按压指示环 -->
          <ellipse cx="480" cy="298" rx="10" ry="3" fill="none" stroke="#ff80ab"
                   stroke-width="1" opacity=".7" id="stemRing"/>
        </g>
        <!-- 密封边缘标注线 -->
        <line id="sealLine1" x1="448" y1="339" x2="440" y2="348" stroke="#ff80ab"
              stroke-width=".6" stroke-dasharray="2,2" opacity=".5"/>
        <line id="sealLine2" x1="512" y1="339" x2="520" y2="348" stroke="#ff80ab"
              stroke-width=".6" stroke-dasharray="2,2" opacity=".5"/>
      </g>

      <!-- ====== 手指 ====== -->
      <g id="finger" opacity="0" transform="translate(0,0)">
        <path d="M462,230 Q462,210 480,205 Q498,210 498,230 L498,275 Q498,282 492,282
                 L468,282 Q462,282 462,275 Z"
              fill="#e8b89d" stroke="#c9956e" stroke-width="1.2" rx="8"/>
        <path d="M466,240 Q466,218 480,213 Q494,218 494,240"
              fill="none" stroke="#d4a07a" stroke-width=".6" opacity=".5"/>
        <!-- 按压力箭头 -->
        <g id="forceArrow" opacity=".8">
          <line x1="480" y1="188" x2="480" y2="218" stroke="var(--amber)" stroke-width="2.5"
                filter="url(#fGlow)"/>
          <polygon points="474,218 480,228 486,218" fill="var(--amber)" filter="url(#fGlow)"/>
          <text x="480" y="183" font-size="10" fill="var(--amber)" text-anchor="middle"
                font-family="'Chakra Petch',sans-serif" font-weight="600">F</text>
        </g>
      </g>

      <!-- ====== 真空力箭头 ====== -->
      <g id="vacuumArrows" opacity=".7">
        <g class="vArr" transform="translate(420,400)">
          <line x1="0" y1="0" x2="0" y2="30" stroke="#00e5ff" stroke-width="1.5" opacity=".6"/>
          <polygon points="-4,30 0,38 4,30" fill="#00e5ff" opacity=".6"/>
        </g>
        <g class="vArr" transform="translate(480,390)">
          <line x1="0" y1="0" x2="0" y2="35" stroke="#00e5ff" stroke-width="1.5" opacity=".7"/>
          <polygon points="-4,35 0,43 4,35" fill="#00e5ff" opacity=".7"/>
        </g>
        <g class="vArr" transform="translate(540,400)">
          <line x1="0" y1="0" x2="0" y2="30" stroke="#00e5ff" stroke-width="1.5" opacity=".6"/>
          <polygon points="-4,30 0,38 4,30" fill="#00e5ff" opacity=".6"/>
        </g>
        <!-- 向上吸力箭头(作用于盖) -->
        <g class="vArr" transform="translate(400,330)">
          <line x1="0" y1="20" x2="0" y2="-5" stroke="#00bcd4" stroke-width="1.2" opacity=".5"/>
          <polygon points="-3,-5 0,-12 3,-5" fill="#00bcd4" opacity=".5"/>
        </g>
        <g class="vArr" transform="translate(560,330)">
          <line x1="0" y1="20" x2="0" y2="-5" stroke="#00bcd4" stroke-width="1.2" opacity=".5"/>
          <polygon points="-3,-5 0,-12 3,-5" fill="#00bcd4" opacity=".5"/>
        </g>
      </g>

      <!-- ====== 进气粒子容器 ====== -->
      <g id="particles"></g>

      <!-- ====== 声波涟漪容器 ====== -->
      <g id="ripples"></g>

      <!-- ====== 压差仪表 ====== -->
      <g id="pressureGauge" transform="translate(830,200)">
        <rect x="-40" y="-10" width="80" height="260" rx="6" fill="#0d1520"
              stroke="#1e2d4a" stroke-width="1"/>
        <text x="0" y="8" font-size="9" fill="#465a73" text-anchor="middle"
              font-family="'Chakra Petch',sans-serif" letter-spacing=".08em">压差 ΔP</text>
        <!-- 刻度 -->
        <line x1="-25" y1="30" x2="25" y2="30" stroke="#1e2d4a" stroke-width=".6"/>
        <text x="30" y="34" font-size="8" fill="#465a73" font-family="'Fira Code',monospace">1.0</text>
        <line x1="-25" y1="82" x2="25" y2="82" stroke="#1e2d4a" stroke-width=".6"/>
        <text x="30" y="86" font-size="8" fill="#465a73" font-family="'Fira Code',monospace">0.5</text>
        <line x1="-25" y1="134" x2="25" y2="134" stroke="#1e2d4a" stroke-width=".6"/>
        <text x="30" y="138" font-size="8" fill="#465a73" font-family="'Fira Code',monospace">0</text>
        <!-- 柱状指示 -->
        <rect id="pBar" x="-18" y="30" width="36" height="104" rx="3"
              fill="rgba(0,229,255,.25)" stroke="none"/>
        <rect id="pBarFill" x="-18" y="30" width="36" height="104" rx="3"
              fill="rgba(0,229,255,.4)" stroke="none"/>
        <!-- 数值 -->
        <text id="pVal" x="0" y="165" font-size="14" fill="#00e5ff" text-anchor="middle"
              font-family="'Chakra Petch',sans-serif" font-weight="700">0.80</text>
        <text x="0" y="180" font-size="8" fill="#465a73" text-anchor="middle"
              font-family="'Fira Code',monospace">atm</text>
        <!-- 状态标签 -->
        <g id="pStatus">
          <rect x="-28" y="195" width="56" height="20" rx="4" fill="rgba(0,229,255,.12)"
                stroke="rgba(0,229,255,.3)" stroke-width=".8"/>
          <text id="pStatusText" x="0" y="209" font-size="8" fill="#00e5ff" text-anchor="middle"
                font-family="'Chakra Petch',sans-serif" font-weight="600">负压锁定</text>
        </g>
      </g>

      <!-- ====== 标注文字 ====== -->
      <g id="annotations" font-family="'Fira Code',monospace">
        <!-- 硅胶微阀标注 -->
        <g id="annValve" opacity=".85">
          <line x1="512" y1="315" x2="575" y2="280" stroke="#ff80ab" stroke-width=".7"
                stroke-dasharray="3,2"/>
          <circle cx="512" cy="315" r="2" fill="#ff80ab"/>
          <text x="580" y="278" font-size="10" fill="#ff80ab" font-weight="500">硅胶微阀</text>
          <text x="580" y="292" font-size="8" fill="#9c5070">Umbrella Valve</text>
        </g>
        <!-- 密封面标注 -->
        <g id="annSeal" opacity=".7">
          <line x1="448" y1="345" x2="395" y2="375" stroke="#ff80ab" stroke-width=".5"
                stroke-dasharray="2,2"/>
          <text x="340" y="383" font-size="8.5" fill="#9c5070">密封边缘</text>
        </g>
        <!-- 马口铁瓶盖标注 -->
        <g opacity=".6">
          <line x1="620" y1="325" x2="660" y2="310" stroke="#5a7a90" stroke-width=".5"
                stroke-dasharray="2,2"/>
          <text x="665" y="314" font-size="8.5" fill="#5a7a90">马口铁盖体</text>
        </g>
        <!-- 螺纹标注 -->
        <g opacity=".5">
          <line x1="352" y1="440" x2="290" y2="460" stroke="#5a7a90" stroke-width=".5"
                stroke-dasharray="2,2"/>
          <text x="240" y="464" font-size="8" fill="#5a7a90">螺纹摩擦</text>
        </g>
        <!-- 真空区标注 -->
        <g id="annVacuum" opacity=".7">
          <text x="465" y="475" font-size="11" fill="#0097a7" text-anchor="middle"
                font-family="'Chakra Petch',sans-serif" font-weight="600"
                filter="url(#fGlow)">负压区</text>
        </g>
        <!-- IFR 解耦标注 -->
        <g id="annIFR" opacity="0" transform="translate(160,460)">
          <rect x="-10" y="-14" width="185" height="72" rx="6" fill="rgba(16,185,129,.08)"
                stroke="rgba(16,185,129,.25)" stroke-width=".8"/>
          <text x="0" y="0" font-size="9.5" fill="#10b981"
                font-family="'Chakra Petch',sans-serif" font-weight="600">IFR 解耦原理</text>
          <text x="0" y="18" font-size="8" fill="#4a8a70">❶ 微力破真空 → ΔP=0</text>
          <text x="0" y="33" font-size="8" fill="#4a8a70">❷ 常规力拧盖 → 轻松旋开</text>
          <text x="0" y="48" font-size="7.5" fill="#3a6a55" font-style="italic">
            复合难题 → 两步简单动作</text>
        </g>
      </g>

      <!-- ====== 旋开箭头 ====== -->
      <g id="unscrewArrow" opacity="0">
        <path d="M640,380 Q660,360 650,340" fill="none" stroke="var(--green)"
              stroke-width="2" stroke-linecap="round" filter="url(#fGlow2)"/>
        <polygon points="646,342 650,332 656,342" fill="var(--green)" filter="url(#fGlow2)"/>
        <text x="668" y="358" font-size="9" fill="var(--green)"
              font-family="'Chakra Petch',sans-serif" font-weight="600">轻松旋开</text>
      </g>

      <!-- ====== 点击提示 ====== -->
      <g id="clickHint" opacity=".6" transform="translate(480,270)">
        <ellipse cx="0" cy="0" rx="22" ry="8" fill="none" stroke="#ff80ab"
                 stroke-width="1" stroke-dasharray="3,2">
          <animate attributeName="rx" values="22;26;22" dur="2s" repeatCount="indefinite"/>
          <animate attributeName="ry" values="8;10;8" dur="2s" repeatCount="indefinite"/>
        </ellipse>
        <text x="0" y="-16" font-size="7.5" fill="#ff80ab" text-anchor="middle"
              font-family="'Fira Code',monospace">点击按压</text>
      </g>

      <!-- 交互热区 -->
      <rect id="hotspot" x="450" y="280" width="60" height="60" fill="transparent"
            cursor="pointer" style="pointer-events:all"/>

    </svg>
  </div>

  <!-- 控制面板 -->
  <div class="controls">
    <button class="btn primary" id="btnPlay" onclick="playDemo()">开始演示</button>
    <button class="btn" id="btnPress" onclick="doPress()" disabled>按压微阀</button>
    <button class="btn" id="btnOpen" onclick="doOpen()" disabled>旋开瓶盖</button>
    <button class="btn" id="btnReset" onclick="doReset()">重置</button>
    <div class="sld">
      <label>初始压差</label>
      <input type="range" id="sldPressure" min="0.2" max="1" step="0.05" value="0.8"
             oninput="updatePressure(this.value)"/>
      <span class="val" id="sldVal">0.80 atm</span>
    </div>
  </div>

  <!-- 信息栏 -->
  <div class="info">
    <div class="steps">
      <div class="sdot" id="d0"></div>
      <div class="sdot" id="d1"></div>
      <div class="sdot" id="d2"></div>
      <div class="sdot" id="d3"></div>
    </div>
    <span class="stxt" id="statusText">等待操作 — 点击「开始演示」或直接点击瓶盖阀门区域</span>
    <span class="badge" id="ifrBadge" style="opacity:0">IFR 理想解</span>
  </div>
</div>

<script>
/* ===== 状态管理 ===== */
const S={IDLE:0,PRESSING:1,OPENING:2,EQUALIZED:3,UNSCREWING:4,DONE:5};
let state=S.IDLE, pressure=0.80, animT=0, animTarget=0, rafId=null;
let particles=[], ripples=[], vacPulse=0;

/* ===== DOM 引用 ===== */
const $=id=>document.getElementById(id);
const svg=$('svg');
const valveFlange=$('valveFlange');
const valveStem=$('valveStem');
const stemRect=$('stemRect');
const stemRing=$('stemRing');
const fingerG=$('finger');
const vacuumGlow=$('vacuumGlow');
const vacuumArrows=$('vacuumArrows');
const pBarFill=$('pBarFill');
const pVal=$('pVal');
const pStatusText=$('pStatusText');
const pStatus=$('pStatus');
const annIFR=$('annIFR');
const annVacuum=$('annVacuum');
const unscrewArrow=$('unscrewArrow');
const clickHint=$('clickHint');
const capGroup=$('capGroup');
const particlesG=$('particles');
const ripplesG=$('ripples');
const hotspot=$('hotspot');

/* ===== 瓶盖偏移(旋开时) ===== */
let capOffsetY=0, capRotation=0;

/* ===== 辅助函数 ===== */
function lerp(a,b,t){return a+(b-a)*t}
function clamp(v,mn,mx){return Math.max(mn,Math.min(mx,v))}

/* 伞面路径生成: t=0闭合, t=1全开 */
function flangePath(t){
  const cx=480, yBase=339;
  const edgeLift=t*14, centerDip=t*8;
  const halfW=32;
  const pts=[
    {x:cx-halfW, y:yBase-edgeLift},
    {x:cx-halfW*0.6, y:yBase+centerDip*0.3},
    {x:cx, y:yBase+centerDip},
    {x:cx+halfW*0.6, y:yBase+centerDip*0.3},
    {x:cx+halfW, y:yBase-edgeLift}
  ];
  return `M${pts[0].x},${pts[0].y} Q${pts[1].x},${pts[1].y} ${pts[2].x},${pts[2].y}
          Q${pts[3].x},${pts[3].y} ${pts[4].x},${pts[4].y}
          L${pts[4].x},${pts[4].y-2}
          Q${pts[3].x},${pts[3].y-2} ${pts[2].x},${pts[2].y-2}
          Q${pts[1].x},${pts[1].y-2} ${pts[0].x},${pts[0].y-2} Z`;
}

/* ===== 压差仪表更新 ===== */
function updateGauge(p){
  const h=clamp(p,0,1)*104;
  pBarFill.setAttribute('height',h);
  pBarFill.setAttribute('y',134-h);
  pVal.textContent=p.toFixed(2);
  if(p>0.3){
    pStatusText.textContent='负压锁定';
    pStatus.querySelector('rect').setAttribute('fill','rgba(0,229,255,.12)');
    pStatus.querySelector('rect').setAttribute('stroke','rgba(0,229,255,.3)');
    pStatusText.setAttribute('fill','#00e5ff');
  }else if(p>0.05){
    pStatusText.textContent='正在释压';
    pStatus.querySelector('rect').setAttribute('fill','rgba(245,158,11,.12)');
    pStatus.querySelector('rect').setAttribute('stroke','rgba(245,158,11,.3)');
    pStatusText.setAttribute('fill','#f59e0b');
  }else{
    pStatusText.textContent='气压平衡';
    pStatus.querySelector('rect').setAttribute('fill','rgba(16,185,129,.12)');
    pStatus.querySelector('rect').setAttribute('stroke','rgba(16,185,129,.3)');
    pStatusText.setAttribute('fill','#10b981');
  }
}

/* ===== 粒子系统 ===== */
function spawnParticle(){
  const cx=480, startY=295+Math.random()*10;
  const targetY=460+Math.random()*80;
  const spread=(Math.random()-0.5)*50;
  const p={
    x:cx+(Math.random()-0.5)*16,
    y:startY,
    vx:spread*0.02,
    vy:1.5+Math.random()*2,
    targetY:targetY,
    r:1.5+Math.random()*1.5,
    life:1,
    el:document.createElementNS('http://www.w3.org/2000/svg','circle')
  };
  p.el.setAttribute('r',p.r);
  p.el.setAttribute('fill','#00e5ff');
  p.el.setAttribute('opacity','0.8');
  particlesG.appendChild(p.el);
  particles.push(p);
}

function updateParticles(){
  for(let i=particles.length-1;i>=0;i--){
    const p=particles[i];
    p.x+=p.vx;
    p.y+=p.vy;
    p.vx*=0.99;
    if(p.y>p.targetY) p.life-=0.03;
    if(p.life<=0){
      p.el.remove();
      particles.splice(i,1);
    }else{
      p.el.setAttribute('cx',p.x);
      p.el.setAttribute('cy',p.y);
      p.el.setAttribute('opacity',clamp(p.life*0.8,0,1));
    }
  }
}

/* ===== 涟漪系统 ===== */
function spawnRipple(){
  const r={
    cx:480, cy:310, radius:5, maxRadius:60+Math.random()*30,
    life:1,
    el:document.createElementNS('http://www.w3.org/2000/svg','circle')
  };
  r.el.setAttribute('fill','none');
  r.el.setAttribute('stroke','#00e5ff');
  r.el.setAttribute('stroke-width','1.5');
  ripplesG.appendChild(r.el);
  ripples.push(r);
}

function updateRipples(){
  for(let i=ripples.length-1;i>=0;i--){
    const r=ripples[i];
    r.radius+=1.2;
    r.life=1-(r.radius/r.maxRadius);
    if(r.life<=0){
      r.el.remove();
      ripples.splice(i,1);
    }else{
      r.el.setAttribute('cx',r.cx);
      r.el.setAttribute('cy',r.cy);
      r.el.setAttribute('r',r.radius);
      r.el.setAttribute('opacity',clamp(r.life*0.6,0,1));
      r.el.setAttribute('stroke-width',clamp(r.life*1.5,0.3,2));
    }
  }
}

/* ===== 真空脉冲 ===== */
function updateVacuumPulse(){
  vacPulse+=0.03;
  const s=1+Math.sin(vacPulse)*0.05;
  const o=0.5+Math.sin(vacPulse)*0.15;
  vacuumGlow.setAttribute('transform',`translate(480,460) scale(${s}) translate(-480,-460)`);
  vacuumGlow.setAttribute('opacity',clamp(o*pressure,0,1));
}

/* ===== 主动画循环 ===== */
let lastTime=0, pressProgress=0, openProgress=0;
let particleTimer=0, rippleTimer=0;
let currentPressure=0.8;

function animate(time){
  const dt=Math.min((time-lastTime)/1000,0.05);
  lastTime=time;

  /* 真空脉冲(仅IDLE/PRESSING态) */
  if(state<=S.PRESSING) updateVacuumPulse();

  /* 按压动画 */
  if(state===S.PRESSING){
    pressProgress=clamp(pressProgress+dt*2.5,0,1);
    const eased=1-Math.pow(1-pressProgress,3);
    /* 手指下移 */
    const fingerY=lerp(0,28,eased);
    fingerG.setAttribute('transform',`translate(0,${fingerY})`);
    fingerG.setAttribute('opacity',1);
    /* 阀芯下移 */
    const stemY=lerp(0,6,eased);
    valveStem.setAttribute('transform',`translate(0,${stemY})`);
    /* 伞面变形 */
    valveFlange.setAttribute('d',flangePath(eased*0.3));
    /* 点击提示消失 */
    clickHint.setAttribute('opacity',Math.max(0,1-pressProgress*3));

    if(pressProgress>=1){
      state=S.OPENING;
      pressProgress=0;
      setStatus(2,'阀口微变形 → 外部空气涌入瓶内');
      setDots(2);
      $('btnPress').disabled=true;
    }
  }

  /* 开阀进气动画 */
  if(state===S.OPENING){
    openProgress=clamp(openProgress+dt*1.2,0,1);
    const eased=1-Math.pow(1-openProgress,2);
    /* 伞面继续变形 */
    valveFlange.setAttribute('d',flangePath(0.3+eased*0.7));
    /* 阀芯保持下压并微弹 */
    const bounce=Math.sin(openProgress*Math.PI)*2;
    valveStem.setAttribute('transform',`translate(0,${6-bounce})`);
    /* 压差下降 */
    currentPressure=lerp(pressure,0,eased);
    updateGauge(currentPressure);
    /* 真空箭头淡出 */
    vacuumArrows.setAttribute('opacity',clamp(1-eased*1.5,0,0.7));
    /* 真空辉光淡出 */
    vacuumGlow.setAttribute('opacity',clamp((1-eased)*0.7,0,1));
    annVacuum.setAttribute('opacity',clamp(1-eased*1.5,0,0.7));
    /* 生成粒子 */
    particleTimer+=dt;
    if(particleTimer>0.04 && eased<0.9){
      spawnParticle();
      if(Math.random()<0.3) spawnParticle();
      particleTimer=0;
    }
    /* 生成涟漪 */
    rippleTimer+=dt;
    if(rippleTimer>0.15 && eased<0.7){
      spawnRipple();
      rippleTimer=0;
    }

    if(openProgress>=1){
      state=S.EQUALIZED;
      openProgress=0;
      setStatus(3,'压差归零 — 真空吸力已消除,可轻松旋开');
      setDots(3);
      $('btnOpen').disabled=false;
      /* IFR标注渐入 */
      annIFR.setAttribute('opacity',1);
      $('ifrBadge').style.opacity='1';
      /* 延迟自动提示旋开 */
      setTimeout(()=>{
        if(state===S.EQUALIZED){
          unscrewArrow.setAttribute('opacity',1);
        }
      },800);
    }
  }

  /* 旋开动画 */
  if(state===S.UNSCREWING){
    openProgress=clamp(openProgress+dt*0.8,0,1);
    const eased=1-Math.pow(1-openProgress,3);
    /* 瓶盖上移+微旋 */
    capOffsetY=lerp(0,-80,eased);
    capRotation=lerp(0,15,eased);
    capGroup.setAttribute('transform',
      `translate(0,${capOffsetY}) rotate(${capRotation},480,340)`);
    /* 阀门也随盖移动 */
    $('valveGroup').setAttribute('transform',
      `translate(0,${capOffsetY}) rotate(${capRotation},480,340)`);
    /* 手指淡出 */
    fingerG.setAttribute('opacity',Math.max(0,1-eased*2));
    /* 旋开箭头淡出 */
    unscrewArrow.setAttribute('opacity',Math.max(0,1-eased*2));

    if(openProgress>=1){
      state=S.DONE;
      setStatus(4,'完成 — 瓶盖已轻松旋开,IFR 理想解验证成功');
      $('btnOpen').disabled=true;
      $('btnOpen').classList.add('success');
    }
  }

  /* 粒子/涟漪更新 */
  updateParticles();
  updateRipples();

  rafId=requestAnimationFrame(animate);
}

/* ===== 状态UI ===== */
function setStatus(step,txt){
  $('statusText').textContent=txt;
}
function setDots(n){
  for(let i=0;i<4;i++){
    const d=$('d'+i);
    d.className='sdot'+(i<n?' ok':'')+(i===n?' on':'');
  }
}

/* ===== 交互操作 ===== */
function playDemo(){
  if(state!==S.IDLE)return;
  $('btnPlay').disabled=true;
  doPress();
}

function doPress(){
  if(state!==S.IDLE)return;
  state=S.PRESSING;
  pressProgress=0;
  currentPressure=pressure;
  setStatus(1,'手指下压微阀中心 → 伞面阀口产生微观弹性变形');
  setDots(1);
  $('btnPress').disabled=true;
  $('btnPlay').disabled=true;
}

function doOpen(){
  if(state!==S.EQUALIZED)return;
  state=S.UNSCREWING;
  openProgress=0;
  setStatus(4,'摩擦力已足够轻松旋开瓶盖...');
  $('btnOpen').disabled=true;
}

function doReset(){
  state=S.IDLE;
  pressProgress=0;openProgress=0;capOffsetY=0;capRotation=0;
  currentPressure=pressure;

  /* 清除粒子/涟漪 */
  particles.forEach(p=>p.el.remove());particles=[];
  ripples.forEach(r=>r.el.remove());ripples=[];

  /* 复位SVG元素 */
  valveFlange.setAttribute('d',flangePath(0));
  valveStem.setAttribute('transform','translate(0,0)');
  fingerG.setAttribute('opacity',0);
  fingerG.setAttribute('transform','translate(0,0)');
  vacuumArrows.setAttribute('opacity',0.7);
  vacuumGlow.setAttribute('opacity',0.5);
  annVacuum.setAttribute('opacity',0.7);
  annIFR.setAttribute('opacity',0);
  unscrewArrow.setAttribute('opacity',0);
  clickHint.setAttribute('opacity',0.6);
  capGroup.setAttribute('transform','translate(0,0)');
  $('valveGroup').setAttribute('transform','translate(0,0)');

  updateGauge(pressure);
  setStatus(0,'等待操作 — 点击「开始演示」或直接点击瓶盖阀门区域');
  setDots(0);
  $('btnPlay').disabled=false;
  $('btnPress').disabled=false;
  $('btnOpen').disabled=true;
  $('btnOpen').classList.remove('success');
  $('ifrBadge').style.opacity='0';
}

function updatePressure(v){
  pressure=parseFloat(v);
  currentPressure=state>=S.OPENING?currentPressure:pressure;
  $('sldVal').textContent=pressure.toFixed(2)+' atm';
  if(state===S.IDLE) updateGauge(pressure);
}

/* 热区点击 */
hotspot.addEventListener('click',()=>{
  if(state===S.IDLE) doPress();
});
/* 热区悬停 */
hotspot.addEventListener('mouseenter',()=>{
  if(state===S.IDLE) clickHint.setAttribute('opacity','1');
});
hotspot.addEventListener('mouseleave',()=>{
  if(state===S.IDLE) clickHint.setAttribute('opacity','0.6');
});

/* ===== 初始化 ===== */
updateGauge(pressure);
setDots(0);
$('btnOpen').disabled=true;
rafId=requestAnimationFrame(animate);
</script>
</body>
</html>

这个动画实现了一个完整的「微流通单向释压阀」工作原理演示,核心设计思路如下:

视觉引导策略

  • 硅胶微阀使用粉红色高亮,与冷色调金属瓶盖形成鲜明对比,一眼锁定核心创新点
  • 真空负压以青色脉冲辉光+方向箭头可视化,让不可见的压差变得直观可感
  • 进气过程通过粒子流+涟漪波纹双重反馈,配合"哧"声的视觉化(同心圆扩散),强化瞬间释压的"顿悟感"
  • 右侧压差仪表实时变化,从"负压锁定"→"正在释压"→"气压平衡",数值与颜色同步演变

IFR 思想表达

  • 不做前后对比,直接演示理想解如何工作
  • 当压差归零后,自动浮现「IFR 解耦原理」标注框,将核心逻辑一语道破:复合难题 → 两步简单动作
  • 旋开箭头在释压完成后才出现,强调"先破真空、再拧盖"的时序解耦

交互设计

  • 三种触发方式:点击「开始演示」按钮 / 点击「按压微阀」按钮 / 直接点击 SVG 中阀门区域
  • 压差滑块可调节初始真空度(0.2~1.0 atm),体验不同压差下的释压效果
  • 四步状态指示灯 + 底部文字实时解说当前物理过程
积分规则:第一轮对话扣减6分,后续每轮扣4分