分享图
A
动画渲染工坊
就绪
<!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>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=JetBrains+Mono:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
  :root {
    --bg: #060a12;
    --panel: rgba(10,18,32,0.92);
    --border: rgba(0,229,255,0.15);
    --accent: #00e5ff;
    --accent2: #ff6d00;
    --accent3: #00e676;
    --text: #c8d6e5;
    --muted: #4a5e78;
    --danger: #ff5252;
    --copper: #cd7f32;
    --rail: #2a3550;
  }
  * { margin:0; padding:0; box-sizing:border-box; }
  body {
    background: var(--bg);
    font-family: 'JetBrains Mono', monospace;
    color: var(--text);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    overflow-x: hidden;
  }
  .main-title {
    font-family: 'Orbitron', sans-serif;
    font-weight: 900;
    font-size: 1.3rem;
    letter-spacing: 0.15em;
    color: var(--accent);
    text-transform: uppercase;
    margin: 18px 0 4px;
    text-shadow: 0 0 24px rgba(0,229,255,0.35);
  }
  .sub-title {
    font-size: 0.72rem;
    color: var(--muted);
    letter-spacing: 0.08em;
    margin-bottom: 10px;
  }
  .svg-wrap {
    width: 96vw;
    max-width: 1440px;
    aspect-ratio: 16/9;
    position: relative;
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    background: linear-gradient(180deg, #080e1a 0%, #0b1220 60%, #0d1528 100%);
    box-shadow: 0 0 60px rgba(0,229,255,0.06), inset 0 0 80px rgba(0,0,0,0.4);
  }
  .svg-wrap svg { width:100%; height:100%; display:block; }
  .controls {
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    margin: 14px 0 20px;
    padding: 12px 24px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 10px;
  }
  .ctrl-group {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .ctrl-label {
    font-size: 0.68rem;
    color: var(--muted);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
  }
  .ctrl-value {
    font-size: 0.72rem;
    color: var(--accent);
    min-width: 52px;
    text-align: right;
    font-weight: 700;
  }
  button {
    font-family: 'Orbitron', sans-serif;
    font-size: 0.65rem;
    letter-spacing: 0.1em;
    padding: 6px 16px;
    border: 1px solid var(--accent);
    background: rgba(0,229,255,0.08);
    color: var(--accent);
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.2s;
    text-transform: uppercase;
  }
  button:hover { background: rgba(0,229,255,0.2); box-shadow: 0 0 12px rgba(0,229,255,0.3); }
  button:active { transform: scale(0.96); }
  button.active { background: rgba(0,229,255,0.25); box-shadow: 0 0 16px rgba(0,229,255,0.4); }
  button.orange { border-color: var(--accent2); color: var(--accent2); background: rgba(255,109,0,0.08); }
  button.orange:hover { background: rgba(255,109,0,0.2); box-shadow: 0 0 12px rgba(255,109,0,0.3); }
  button.green { border-color: var(--accent3); color: var(--accent3); background: rgba(0,230,118,0.08); }
  button.green:hover { background: rgba(0,230,118,0.2); box-shadow: 0 0 12px rgba(0,230,118,0.3); }
  input[type=range] {
    -webkit-appearance: none;
    width: 120px;
    height: 4px;
    background: var(--muted);
    border-radius: 2px;
    outline: none;
  }
  input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--accent);
    cursor: pointer;
    box-shadow: 0 0 8px rgba(0,229,255,0.5);
  }
  @keyframes pulseGlow {
    0%,100% { opacity:0.6; }
    50% { opacity:1; }
  }
  @keyframes dashFlow {
    to { stroke-dashoffset: -20; }
  }
  @media (max-width: 768px) {
    .main-title { font-size: 0.95rem; }
    .controls { padding: 8px 12px; gap: 8px; }
    input[type=range] { width: 80px; }
  }
</style>
</head>
<body>

<div class="main-title">IFR 原理动画 — 场控减振与直驱定位</div>
<div class="sub-title">电磁涡流阻尼器 & 音圈电机直驱系统 · 最终理想解可视化</div>

<div class="svg-wrap">
<svg id="mainSvg" viewBox="0 0 1400 800" xmlns="http://www.w3.org/2000/svg">
<defs>
  <!-- 网格图案 -->
  <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
    <path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(0,229,255,0.04)" stroke-width="0.5"/>
  </pattern>
  <!-- 发光滤镜 -->
  <filter id="glowCyan" x="-50%" y="-50%" width="200%" height="200%">
    <feGaussianBlur stdDeviation="6" result="blur"/>
    <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
  </filter>
  <filter id="glowOrange" x="-50%" y="-50%" width="200%" height="200%">
    <feGaussianBlur stdDeviation="5" result="blur"/>
    <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
  </filter>
  <filter id="glowGreen" x="-50%" y="-50%" width="200%" height="200%">
    <feGaussianBlur stdDeviation="4" result="blur"/>
    <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
  </filter>
  <filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
    <feGaussianBlur stdDeviation="3" result="blur"/>
    <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
  </filter>
  <!-- 涡流渐变 -->
  <radialGradient id="eddyGrad" cx="50%" cy="50%" r="50%">
    <stop offset="0%" stop-color="#00e5ff" stop-opacity="0.6"/>
    <stop offset="70%" stop-color="#00bcd4" stop-opacity="0.2"/>
    <stop offset="100%" stop-color="#006064" stop-opacity="0"/>
  </radialGradient>
  <!-- 磁铁渐变 N极 -->
  <linearGradient id="magN" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0%" stop-color="#ff5252"/>
    <stop offset="100%" stop-color="#b71c1c"/>
  </linearGradient>
  <!-- 磁铁渐变 S极 -->
  <linearGradient id="magS" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0%" stop-color="#448aff"/>
    <stop offset="100%" stop-color="#1565c0"/>
  </linearGradient>
  <!-- 轨道侧面渐变 -->
  <linearGradient id="railSide" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0%" stop-color="#37474f"/>
    <stop offset="100%" stop-color="#1a2530"/>
  </linearGradient>
  <!-- 箭头标记 -->
  <marker id="arrowCyan" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M 0 0 L 10 5 L 0 10 z" fill="#00e5ff"/>
  </marker>
  <marker id="arrowOrange" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M 0 0 L 10 5 L 0 10 z" fill="#ff6d00"/>
  </marker>
  <marker id="arrowGreen" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M 0 0 L 10 5 L 0 10 z" fill="#00e676"/>
  </marker>
  <!-- 噪声纹理 -->
  <filter id="noiseFilter">
    <feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
    <feColorMatrix type="saturate" values="0"/>
  </filter>
</defs>

<!-- 背景网格 -->
<rect width="1400" height="800" fill="url(#grid)"/>

<!-- 噪声纹理叠加 -->
<rect width="1400" height="800" filter="url(#noiseFilter)" opacity="0.025"/>

<!-- ============ 轨道系统 ============ -->
<g id="railSystem">
  <!-- 轨道顶面 -->
  <rect x="0" y="460" width="1400" height="10" fill="#455a64" rx="1"/>
  <!-- 轨道侧面(涡流作用面)-->
  <rect x="0" y="470" width="1400" height="40" fill="url(#railSide)"/>
  <!-- 轨道底面 -->
  <rect x="0" y="510" width="1400" height="6" fill="#263238" rx="1"/>
  <!-- 轨道接缝 -->
  <g id="railJoints" fill="none" stroke="#0d1520" stroke-width="2.5">
    <line x1="350" y1="460" x2="350" y2="516"/>
    <line x1="700" y1="460" x2="700" y2="516"/>
    <line x1="1050" y1="460" x2="1050" y2="516"/>
  </g>
  <!-- 轨道标签 -->
  <text x="60" y="540" fill="#37474f" font-size="11" font-family="JetBrains Mono" letter-spacing="0.1em">RAIL · 导轨</text>
</g>

<!-- ============ 涡流效果层 ============ -->
<g id="eddyLayer"></g>

<!-- ============ 磁场线层 ============ -->
<g id="fieldLineLayer"></g>

<!-- ============ 穿梭车组 ============ -->
<g id="shuttleGroup">
  <!-- 车体主结构 -->
  <rect id="shuttleBody" x="0" y="345" width="240" height="112" rx="8" fill="#151e2e" stroke="#263850" stroke-width="1.5"/>
  <!-- 车体装饰线 -->
  <line x1="12" y1="370" x2="228" y2="370" stroke="#1e3048" stroke-width="1"/>
  <line x1="12" y1="430" x2="228" y2="430" stroke="#1e3048" stroke-width="1"/>
  <!-- 车体中心标签 -->
  <text x="120" y="396" text-anchor="middle" fill="#3a5068" font-size="10" font-family="Orbitron" letter-spacing="0.15em">SHUTTLE</text>
  <text x="120" y="416" text-anchor="middle" fill="#2a3e55" font-size="8" font-family="JetBrains Mono">穿梭车体</text>

  <!-- 伺服驱动轮 -->
  <g id="wheelGroup">
    <!-- 驱动轮1 -->
    <circle cx="55" cy="458" r="18" fill="#263238" stroke="#455a64" stroke-width="2"/>
    <circle cx="55" cy="458" r="10" fill="#1a2530" stroke="#37474f" stroke-width="1"/>
    <line cx="55" cy="458" x1="55" y1="448" x2="55" y2="468" stroke="#546e7a" stroke-width="1" id="spoke1"/>
    <!-- 驱动轮2 -->
    <circle cx="185" cy="458" r="18" fill="#263238" stroke="#455a64" stroke-width="2"/>
    <circle cx="185" cy="458" r="10" fill="#1a2530" stroke="#37474f" stroke-width="1"/>
    <line x1="185" y1="448" x2="185" y2="468" stroke="#546e7a" stroke-width="1" id="spoke2"/>
  </g>
  <!-- 同步带 -->
  <rect x="55" y="450" width="130" height="4" fill="#37474f" rx="2" opacity="0.5"/>
  <!-- 伺服电机指示 -->
  <rect x="100" y="440" width="30" height="14" rx="3" fill="#1a2530" stroke="#ff6d00" stroke-width="0.8" opacity="0.7"/>
  <text x="115" y="450" text-anchor="middle" fill="#ff6d00" font-size="6" font-family="JetBrains Mono">SERVO</text>

  <!-- 侧向永磁体 - 左 -->
  <g id="magnetLeft">
    <rect x="35" y="462" width="12" height="28" rx="2" fill="url(#magN)"/>
    <text x="41" y="480" text-anchor="middle" fill="#ffcdd2" font-size="8" font-weight="700" font-family="JetBrains Mono">N</text>
    <rect x="47" y="462" width="12" height="28" rx="2" fill="url(#magS)"/>
    <text x="53" y="480" text-anchor="middle" fill="#bbdefb" font-size="8" font-weight="700" font-family="JetBrains Mono">S</text>
  </g>
  <!-- 侧向永磁体 - 右 -->
  <g id="magnetRight">
    <rect x="165" y="462" width="12" height="28" rx="2" fill="url(#magN)"/>
    <text x="171" y="480" text-anchor="middle" fill="#ffcdd2" font-size="8" font-weight="700" font-family="JetBrains Mono">N</text>
    <rect x="177" y="462" width="12" height="28" rx="2" fill="url(#magS)"/>
    <text x="183" y="480" text-anchor="middle" fill="#bbdefb" font-size="8" font-weight="700" font-family="JetBrains Mono">S</text>
  </g>

  <!-- 间隙标注 -->
  <g id="gapAnnotation" opacity="0">
    <line x1="59" y1="475" x2="59" y2="475" stroke="#00e5ff" stroke-width="0.8" stroke-dasharray="2,2"/>
    <text x="70" y="498" fill="#00e5ff" font-size="9" font-family="JetBrains Mono" opacity="0.9">0.5mm</text>
    <line x1="59" y1="490" x2="59" y2="470" stroke="#00e5ff" stroke-width="0.6" stroke-dasharray="3,2"/>
    <!-- 间隙指示线 -->
    <line x1="59" y1="470" x2="59" y2="470" stroke="#00e5ff" stroke-width="0.5"/>
  </g>

  <!-- ============ 音圈电机 & 货叉 ============ -->
  <g id="vcmGroup">
    <!-- VCM外壳 -->
    <rect x="220" y="365" width="60" height="70" rx="4" fill="#1a1a2e" stroke="#cd7f32" stroke-width="1.2"/>
    <!-- VCM线圈 -->
    <rect id="vcmCoil" x="228" y="373" width="44" height="54" rx="3" fill="none" stroke="#cd7f32" stroke-width="2" opacity="0.7"/>
    <!-- VCM永磁体 -->
    <rect x="232" y="377" width="8" height="46" rx="1" fill="url(#magN)" opacity="0.8"/>
    <rect x="260" y="377" width="8" height="46" rx="1" fill="url(#magS)" opacity="0.8"/>
    <!-- VCM标签 -->
    <text x="250" y="400" text-anchor="middle" fill="#cd7f32" font-size="7" font-family="Orbitron" letter-spacing="0.08em">VCM</text>
    <!-- VCM力箭头层 -->
    <g id="vcmForceLayer"></g>
  </g>

  <!-- 货叉 -->
  <g id="forkGroup">
    <!-- 货叉主体 -->
    <rect id="forkBody" x="280" y="380" width="0" height="40" rx="3" fill="#5d4037" stroke="#8d6e63" stroke-width="1"/>
    <!-- 货叉尖端 -->
    <g id="forkTip" opacity="0">
      <polygon points="0,380 20,390 0,400 6,395 6,385" fill="#8d6e63"/>
    </g>
    <!-- 货叉标签 -->
    <g id="forkLabel" opacity="0">
      <text x="0" y="374" fill="#8d6e63" font-size="8" font-family="JetBrains Mono">FORK</text>
    </g>
  </g>
</g>

<!-- ============ 目标料箱 ============ -->
<g id="targetBin" opacity="0">
  <rect x="0" y="355" width="50" height="80" rx="3" fill="none" stroke="#00e676" stroke-width="1.5" stroke-dasharray="6,3"/>
  <rect x="0" y="375" width="50" height="40" rx="2" fill="rgba(0,230,118,0.06)"/>
  <text x="25" y="400" text-anchor="middle" fill="#00e676" font-size="8" font-family="JetBrains Mono" opacity="0.7">目标孔</text>
</g>

<!-- ============ 阻尼力指示 ============ -->
<g id="dampingForceLayer"></g>

<!-- ============ 振动指示层 ============ -->
<g id="vibrationLayer"></g>

<!-- ============ 信息面板 ============ -->
<g id="infoPanel">
  <!-- 阶段指示 -->
  <rect x="30" y="20" width="260" height="44" rx="6" fill="rgba(6,10,18,0.85)" stroke="rgba(0,229,255,0.2)" stroke-width="1"/>
  <text x="42" y="38" fill="#4a5e78" font-size="9" font-family="JetBrains Mono" letter-spacing="0.08em">PHASE</text>
  <text id="phaseText" x="42" y="54" fill="#00e5ff" font-size="13" font-family="Orbitron" font-weight="700" letter-spacing="0.1em">STANDBY</text>

  <!-- 参数面板 -->
  <rect x="30" y="76" width="260" height="160" rx="6" fill="rgba(6,10,18,0.85)" stroke="rgba(0,229,255,0.12)" stroke-width="1"/>
  <text x="42" y="96" fill="#4a5e78" font-size="9" font-family="JetBrains Mono" letter-spacing="0.1em">PARAMETERS</text>

  <text x="42" y="118" fill="#607d8b" font-size="9" font-family="JetBrains Mono">直行速度</text>
  <text id="paramSpeed" x="248" y="118" text-anchor="end" fill="#00e5ff" font-size="11" font-family="JetBrains Mono" font-weight="700">0.0 m/s</text>

  <text x="42" y="138" fill="#607d8b" font-size="9" font-family="JetBrains Mono">涡流阻尼力</text>
  <text id="paramDamping" x="248" y="138" text-anchor="end" fill="#00e5ff" font-size="11" font-family="JetBrains Mono" font-weight="700">0 N</text>

  <text x="42" y="158" fill="#607d8b" font-size="9" font-family="JetBrains Mono">叉速</text>
  <text id="paramForkSpeed" x="248" y="158" text-anchor="end" fill="#ff6d00" font-size="11" font-family="JetBrains Mono" font-weight="700">0.0 m/s</text>

  <text x="42" y="178" fill="#607d8b" font-size="9" font-family="JetBrains Mono">VCM推力</text>
  <text id="paramVcmForce" x="248" y="178" text-anchor="end" fill="#ff6d00" font-size="11" font-family="JetBrains Mono" font-weight="700">0 N</text>

  <text x="42" y="198" fill="#607d8b" font-size="9" font-family="JetBrains Mono">VCM电流</text>
  <text id="paramVcmCurrent" x="248" y="198" text-anchor="end" fill="#cd7f32" font-size="11" font-family="JetBrains Mono" font-weight="700">0.0 A</text>

  <text x="42" y="218" fill="#607d8b" font-size="9" font-family="JetBrains Mono">车体振幅</text>
  <text id="paramVibration" x="248" y="218" text-anchor="end" fill="#ff5252" font-size="11" font-family="JetBrains Mono" font-weight="700">0.0 mm</text>
</g>

<!-- ============ IFR 原理说明 ============ -->
<g id="ifrPanel">
  <rect x="1100" y="20" width="270" height="100" rx="6" fill="rgba(6,10,18,0.85)" stroke="rgba(0,230,118,0.2)" stroke-width="1"/>
  <text x="1112" y="40" fill="#00e676" font-size="9" font-family="Orbitron" font-weight="700" letter-spacing="0.12em">IFR · 最终理想解</text>
  <text x="1112" y="58" fill="#78909c" font-size="8.5" font-family="JetBrains Mono">以场代实 · 非接触阻尼</text>
  <text x="1112" y="74" fill="#78909c" font-size="8.5" font-family="JetBrains Mono">洛伦兹力 · 毫秒级响应</text>
  <text x="1112" y="90" fill="#546e7a" font-size="7.5" font-family="JetBrains Mono">无磨损 · 无润滑 · 零接触</text>
  <text x="1112" y="108" fill="#37474f" font-size="7" font-family="JetBrains Mono">场控代替机械传动</text>
</g>

<!-- ============ 电流波形显示 ============ -->
<g id="waveformPanel">
  <rect x="1100" y="132" width="270" height="105" rx="6" fill="rgba(6,10,18,0.85)" stroke="rgba(205,127,50,0.2)" stroke-width="1"/>
  <text x="1112" y="150" fill="#cd7f32" font-size="9" font-family="Orbitron" font-weight="700" letter-spacing="0.1em">VCM CURRENT WAVEFORM</text>
  <!-- 波形画布 -->
  <rect x="1112" y="158" width="246" height="68" rx="3" fill="rgba(0,0,0,0.3)"/>
  <line x1="1112" y1="192" x2="1358" y2="192" stroke="#1a2530" stroke-width="0.5"/>
  <path id="waveformPath" d="" fill="none" stroke="#cd7f32" stroke-width="1.5" filter="url(#softGlow)"/>
</g>

<!-- ============ 涡流阻尼详情 ============ -->
<g id="eddyDetailPanel">
  <rect x="1100" y="250" width="270" height="130" rx="6" fill="rgba(6,10,18,0.85)" stroke="rgba(0,229,255,0.15)" stroke-width="1"/>
  <text x="1112" y="270" fill="#00e5ff" font-size="9" font-family="Orbitron" font-weight="700" letter-spacing="0.1em">EDDY CURRENT DAMPER</text>
  <!-- 涡流示意 -->
  <g transform="translate(1140,300)">
    <!-- 轨道截面 -->
    <rect x="0" y="20" width="80" height="50" rx="2" fill="#263238" stroke="#37474f" stroke-width="1"/>
    <text x="40" y="50" text-anchor="middle" fill="#455a64" font-size="7" font-family="JetBrains Mono">轨道侧面</text>
    <!-- 永磁体 -->
    <rect x="88" y="28" width="20" height="34" rx="2" fill="url(#magN)"/>
    <rect x="108" y="28" width="20" height="34" rx="2" fill="url(#magS)"/>
    <text x="98" y="49" text-anchor="middle" fill="#ffcdd2" font-size="7" font-weight="700" font-family="JetBrains Mono">N</text>
    <text x="118" y="49" text-anchor="middle" fill="#bbdefb" font-size="7" font-weight="700" font-family="JetBrains Mono">S</text>
    <!-- 间隙标注 -->
    <line x1="80" y1="32" x2="88" y2="32" stroke="#00e5ff" stroke-width="0.8"/>
    <line x1="80" y1="58" x2="88" y2="58" stroke="#00e5ff" stroke-width="0.8"/>
    <text x="84" y="26" text-anchor="middle" fill="#00e5ff" font-size="7" font-family="JetBrains Mono">0.5mm</text>
    <!-- 涡流环 -->
    <g id="eddyDetailSwirls" opacity="0.6">
      <circle cx="30" cy="42" r="10" fill="none" stroke="#00e5ff" stroke-width="1" stroke-dasharray="4,3" opacity="0.7">
        <animateTransform attributeName="transform" type="rotate" from="0 30 42" to="360 30 42" dur="1.5s" repeatCount="indefinite"/>
      </circle>
      <circle cx="55" cy="48" r="8" fill="none" stroke="#00e5ff" stroke-width="1" stroke-dasharray="3,3" opacity="0.5">
        <animateTransform attributeName="transform" type="rotate" from="360 55 48" to="0 55 48" dur="2s" repeatCount="indefinite"/>
      </circle>
    </g>
    <!-- 磁力线 -->
    <path d="M88 35 Q78 35 78 45 Q78 55 88 55" fill="none" stroke="#00bcd4" stroke-width="0.8" stroke-dasharray="3,2" opacity="0.6">
      <animate attributeName="stroke-dashoffset" from="0" to="-10" dur="0.8s" repeatCount="indefinite"/>
    </path>
    <path d="M108 35 Q98 35 98 45 Q98 55 108 55" fill="none" stroke="#00bcd4" stroke-width="0.8" stroke-dasharray="3,2" opacity="0.5">
      <animate attributeName="stroke-dashoffset" from="0" to="-10" dur="0.8s" repeatCount="indefinite"/>
    </path>
  </g>
  <!-- 说明文字 -->
  <text x="1112" y="366" fill="#546e7a" font-size="7.5" font-family="JetBrains Mono">阻尼力 ∝ 振动速度</text>
  <text x="1112" y="378" fill="#37474f" font-size="7" font-family="JetBrains Mono">高速强阻尼 · 低速自然衰减</text>
</g>

<!-- ============ 洛伦兹力公式 ============ -->
<g id="lorentzPanel">
  <rect x="1100" y="394" width="270" height="76" rx="6" fill="rgba(6,10,18,0.85)" stroke="rgba(255,109,0,0.15)" stroke-width="1"/>
  <text x="1112" y="414" fill="#ff6d00" font-size="9" font-family="Orbitron" font-weight="700" letter-spacing="0.1em">LORENTZ FORCE</text>
  <text x="1235" y="442" text-anchor="middle" fill="#ffab00" font-size="16" font-family="JetBrains Mono" font-weight="700">F = BIL</text>
  <text x="1112" y="460" fill="#546e7a" font-size="7.5" font-family="JetBrains Mono">推力≥500N · 加速度≥10m/s²</text>
</g>

<!-- ============ 动态标注层 ============ -->
<g id="annotationLayer"></g>

</svg>
</div>

<!-- 控制面板 -->
<div class="controls">
  <div class="ctrl-group">
    <button id="btnPlay" class="active">AUTO PLAY</button>
    <button id="btnReset">RESET</button>
  </div>
  <div class="ctrl-group">
    <span class="ctrl-label">速度倍率</span>
    <input type="range" id="speedMul" min="0.3" max="2" step="0.1" value="1">
    <span class="ctrl-value" id="speedMulVal">1.0x</span>
  </div>
  <div class="ctrl-group">
    <button id="btnManual" class="orange">MANUAL</button>
  </div>
  <div class="ctrl-group" id="manualControls" style="display:none;">
    <span class="ctrl-label">车速度</span>
    <input type="range" id="manualSpeed" min="0" max="5" step="0.1" value="0">
    <span class="ctrl-value" id="manualSpeedVal">0.0</span>
    <span class="ctrl-label">叉行程</span>
    <input type="range" id="manualFork" min="0" max="100" step="1" value="0">
    <span class="ctrl-value" id="manualForkVal">0%</span>
  </div>
</div>

<script>
// ============================
// 动画引擎
// ============================
const SVG_NS = 'http://www.w3.org/2000/svg';
const svg = document.getElementById('mainSvg');

// 获取关键DOM元素
const shuttleGroup = document.getElementById('shuttleGroup');
const forkBody = document.getElementById('forkBody');
const forkTip = document.getElementById('forkTip');
const forkLabel = document.getElementById('forkLabel');
const targetBin = document.getElementById('targetBin');
const gapAnnotation = document.getElementById('gapAnnotation');
const eddyLayer = document.getElementById('eddyLayer');
const fieldLineLayer = document.getElementById('fieldLineLayer');
const dampingForceLayer = document.getElementById('dampingForceLayer');
const vibrationLayer = document.getElementById('vibrationLayer');
const vcmForceLayer = document.getElementById('vcmForceLayer');
const vcmCoil = document.getElementById('vcmCoil');
const annotationLayer = document.getElementById('annotationLayer');
const waveformPath = document.getElementById('waveformPath');
const spoke1 = document.getElementById('spoke1');
const spoke2 = document.getElementById('spoke2');

// 参数显示元素
const paramSpeed = document.getElementById('paramSpeed');
const paramDamping = document.getElementById('paramDamping');
const paramForkSpeed = document.getElementById('paramForkSpeed');
const paramVcmForce = document.getElementById('paramVcmForce');
const paramVcmCurrent = document.getElementById('paramVcmCurrent');
const paramVibration = document.getElementById('paramVibration');
const phaseText = document.getElementById('phaseText');

// 控制元素
const btnPlay = document.getElementById('btnPlay');
const btnReset = document.getElementById('btnReset');
const btnManual = document.getElementById('btnManual');
const speedMulSlider = document.getElementById('speedMul');
const speedMulVal = document.getElementById('speedMulVal');
const manualControls = document.getElementById('manualControls');
const manualSpeedSlider = document.getElementById('manualSpeed');
const manualSpeedVal = document.getElementById('manualSpeedVal');
const manualForkSlider = document.getElementById('manualFork');
const manualForkVal = document.getElementById('manualForkVal');

// ============================
// 动画状态
// ============================
let state = {
  mode: 'auto', // 'auto' | 'manual'
  phase: 0,     // 0:待机 1:高速直行 2:减速 3:伸叉 4:柔性制动 5:完成
  time: 0,
  shuttleX: 80,
  shuttleSpeed: 0,     // m/s
  vibration: 0,        // 振幅 mm
  vibrationPhase: 0,
  dampingForce: 0,     // N
  forkExtension: 0,    // 0~1
  forkSpeed: 0,        // m/s
  vcmForce: 0,         // N
  vcmCurrent: 0,       // A
  eddyIntensity: 0,    // 0~1
  speedMul: 1,
  running: true,
  wheelAngle: 0,
  // 涡流圆环
  eddyCircles: [],
  // 波形历史
  waveformHistory: [],
  // 磁场线
  fieldLines: [],
  // 动态标注
  annotations: []
};

// 阶段配置
const PHASES = [
  { name: 'STANDBY', duration: 1.5, label: '系统待机' },
  { name: 'HIGH-SPEED', duration: 4, label: '高速直行 5m/s' },
  { name: 'DECELERATE', duration: 1.5, label: '减速停靠' },
  { name: 'FORK-EXTEND', duration: 2, label: '音圈电机伸叉' },
  { name: 'SOFT-BRAKE', duration: 1.8, label: '柔性制动对接' },
  { name: 'COMPLETE', duration: 2, label: '对接完成' }
];

// ============================
// 工具函数
// ============================
function lerp(a, b, t) { return a + (b - a) * Math.max(0, Math.min(1, t)); }
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
function easeInOut(t) { return t < 0.5 ? 2*t*t : -1+(4-2*t)*t; }
function easeOut(t) { return 1 - Math.pow(1 - t, 3); }
function easeIn(t) { return t * t * t; }

// 创建SVG元素
function createSvgEl(tag, attrs) {
  const el = document.createElementNS(SVG_NS, tag);
  for (const [k, v] of Object.entries(attrs)) {
    el.setAttribute(k, v);
  }
  return el;
}

// ============================
// 涡流效果管理
// ============================
const MAX_EDDY_CIRCLES = 12;

function initEddyCircles() {
  for (let i = 0; i < MAX_EDDY_CIRCLES; i++) {
    const circle = createSvgEl('ellipse', {
      cx: 0, cy: 0, rx: 8, ry: 5,
      fill: 'none',
      stroke: '#00e5ff',
      'stroke-width': '1.2',
      'stroke-dasharray': '4,3',
      opacity: 0
    });
    circle.style.transformOrigin = 'center';
    eddyLayer.appendChild(circle);
    state.eddyCircles.push({ el: circle, active: false, x: 0, y: 0, rot: 0, life: 0 });
  }
}

function updateEddyCircles(dt, shuttleScreenX) {
  const intensity = state.eddyIntensity;
  // 在轨道侧面上生成涡流效果
  const railY = 485; // 轨道侧面中心Y

  // 根据强度激活/管理涡流圆
  for (let i = 0; i < state.eddyCircles.length; i++) {
    const ec = state.eddyCircles[i];
    if (intensity > 0.1 && !ec.active && Math.random() < intensity * dt * 3) {
      // 激活一个涡流圆
      const magnetOffsetX = (i % 2 === 0) ? 47 : 183; // 左磁体或右磁体
      ec.x = shuttleScreenX + magnetOffsetX + (Math.random() - 0.5) * 30;
      ec.y = railY + (Math.random() - 0.5) * 20;
      ec.active = true;
      ec.life = 1;
      ec.rot = Math.random() * 360;
      const scale = 0.5 + Math.random() * 0.8;
      ec.el.setAttribute('rx', 8 * scale);
      ec.el.setAttribute('ry', 5 * scale);
    }

    if (ec.active) {
      ec.life -= dt * (0.8 + intensity * 0.5);
      ec.rot += dt * (120 + intensity * 200) * (i % 2 === 0 ? 1 : -1);
      if (ec.life <= 0) {
        ec.active = false;
        ec.el.setAttribute('opacity', 0);
      } else {
        ec.el.setAttribute('cx', ec.x);
        ec.el.setAttribute('cy', ec.y);
        ec.el.setAttribute('opacity', ec.life * intensity * 0.7);
        ec.el.setAttribute('transform', `rotate(${ec.rot} ${ec.x} ${ec.y})`);
      }
    }
  }
}

// ============================
// 磁场线管理
// ============================
function updateFieldLines(dt, shuttleScreenX) {
  // 清除旧的磁场线
  while (fieldLineLayer.firstChild) fieldLineLayer.removeChild(fieldLineLayer.firstChild);

  const intensity = state.eddyIntensity;
  if (intensity < 0.05) return;

  const railTop = 470;
  const magnetY = 475;

  // 左磁体磁场线
  const magPositions = [shuttleScreenX + 47, shuttleScreenX + 183];
  magPositions.forEach((mx, idx) => {
    for (let i = 0; i < 3; i++) {
      const spread = (i - 1) * 6;
      const path = createSvgEl('path', {
        d: `M${mx + spread} ${magnetY} Q${mx + spread - 8 - i*3} ${magnetY - 12 - i*4} ${mx + spread} ${railTop}`,
        fill: 'none',
        stroke: '#00bcd4',
        'stroke-width': 0.8,
        'stroke-dasharray': '3,3',
        opacity: intensity * 0.5 * (1 - i * 0.2)
      });
      // 动画dashoffset
      const anim = createSvgEl('animate', {
        attributeName: 'stroke-dashoffset',
        from: '0', to: '-12', dur: `${0.6 + i * 0.2}s`,
        repeatCount: 'indefinite'
      });
      path.appendChild(anim);
      fieldLineLayer.appendChild(path);
    }
  });
}

// ============================
// VCM力箭头管理
// ============================
function updateVcmForces(shuttleScreenX) {
  while (vcmForceLayer.firstChild) vcmForceLayer.removeChild(vcmForceLayer.firstChild);

  const force = state.vcmForce;
  if (Math.abs(force) < 5) return;

  const vcmCenterX = shuttleScreenX + 250;
  const vcmCenterY = 400;
  const arrowLen = clamp(Math.abs(force) / 500 * 50, 10, 60);
  const dir = force > 0 ? 1 : -1;
  const color = Math.abs(force) > 300 ? '#ff6d00' : '#ffab00';

  // 主力箭头
  const arrow = createSvgEl('line', {
    x1: vcmCenterX, y1: vcmCenterY,
    x2: vcmCenterX + arrowLen * dir, y2: vcmCenterY,
    stroke: color, 'stroke-width': 3,
    'marker-end': 'url(#arrowOrange)',
    opacity: 0.9,
    filter: 'url(#glowOrange)'
  });
  vcmForceLayer.appendChild(arrow);

  // 力值标注
  const label = createSvgEl('text', {
    x: vcmCenterX + arrowLen * dir * 0.5,
    y: vcmCenterY - 12,
    'text-anchor': 'middle',
    fill: color,
    'font-size': 10,
    'font-family': 'JetBrains Mono',
    'font-weight': 700
  });
  label.textContent = `${Math.abs(force).toFixed(0)}N`;
  vcmForceLayer.appendChild(label);
}

// ============================
// 阻尼力指示
// ============================
function updateDampingForces(shuttleScreenX) {
  while (dampingForceLayer.firstChild) dampingForceLayer.removeChild(dampingForceLayer.firstChild);

  const force = state.dampingForce;
  if (force < 2) return;

  const arrowLen = clamp(force / 200 * 35, 8, 40);

  // 左磁体阻尼力箭头(向上,表示抑制振动)
  [shuttleScreenX + 47, shuttleScreenX + 183].forEach(mx => {
    const arrow = createSvgEl('line', {
      x1: mx, y1: 462,
      x2: mx, y2: 462 - arrowLen,
      stroke: '#00e5ff', 'stroke-width': 2.5,
      'marker-end': 'url(#arrowCyan)',
      opacity: 0.7,
      filter: 'url(#glowCyan)'
    });
    dampingForceLayer.appendChild(arrow);
  });

  // 阻尼力值
  const label = createSvgEl('text', {
    x: shuttleScreenX + 115,
    y: 450 - arrowLen,
    'text-anchor': 'middle',
    fill: '#00e5ff',
    'font-size': 9,
    'font-family': 'JetBrains Mono',
    'font-weight': 700,
    opacity: 0.8
  });
  label.textContent = `阻尼 ${force.toFixed(0)}N`;
  dampingForceLayer.appendChild(label);
}

// ============================
// 振动效果
// ============================
function updateVibration(dt) {
  while (vibrationLayer.firstChild) vibrationLayer.removeChild(vibrationLayer.firstChild);

  const vib = state.vibration;
  if (vib < 0.3) return;

  // 振动波形线(在车体两侧显示)
  const shuttleScreenX = state.shuttleX;
  const points = [];
  for (let i = 0; i < 20; i++) {
    const x = shuttleScreenX + 10 + i * 11;
    const y = 340 + Math.sin(state.vibrationPhase + i * 0.8) * vib * 1.5;
    points.push(`${x},${y}`);
  }
  const waveLine = createSvgEl('polyline', {
    points: points.join(' '),
    fill: 'none',
    stroke: '#ff5252',
    'stroke-width': 1.2,
    opacity: clamp(vib / 5, 0.2, 0.8)
  });
  vibrationLayer.appendChild(waveLine);

  // 振幅标注
  if (vib > 1) {
    const vLabel = createSvgEl('text', {
      x: shuttleScreenX + 120,
      y: 332,
      'text-anchor': 'middle',
      fill: '#ff5252',
      'font-size': 9,
      'font-family': 'JetBrains Mono',
      opacity: 0.7
    });
    vLabel.textContent = `振幅 ${vib.toFixed(1)}mm`;
    vibrationLayer.appendChild(vLabel);
  }
}

// ============================
// 波形显示
// ============================
function updateWaveform() {
  state.waveformHistory.push(state.vcmCurrent);
  if (state.waveformHistory.length > 120) state.waveformHistory.shift();

  if (state.waveformHistory.length < 2) return;

  const baseX = 1114;
  const baseY = 192;
  const width = 242;
  const height = 30;
  const step = width / 120;

  let d = '';
  state.waveformHistory.forEach((v, i) => {
    const x = baseX + i * step;
    const y = baseY - (v / 50) * height;
    d += (i === 0 ? 'M' : 'L') + `${x},${y}`;
  });
  waveformPath.setAttribute('d', d);
}

// ============================
// 动态标注
// ============================
function updateAnnotations() {
  while (annotationLayer.firstChild) annotationLayer.removeChild(annotationLayer.firstChild);

  const sx = state.shuttleX;

  // 阶段2+:间隙标注
  if (state.phase >= 1 && state.phase <= 2 && state.shuttleSpeed > 0.5) {
    gapAnnotation.setAttribute('opacity', clamp(state.eddyIntensity, 0, 1));
  } else {
    gapAnnotation.setAttribute('opacity', 0);
  }

  // 阶段3-4:VCM标注
  if (state.phase >= 3 && state.phase <= 4) {
    // 推力标注
    const tLabel = createSvgEl('text', {
      x: sx + 250, y: 356,
      'text-anchor': 'middle',
      fill: '#ff6d00',
      'font-size': 9,
      'font-family': 'JetBrains Mono',
      opacity: 0.8
    });
    tLabel.textContent = state.phase === 3 ? '爆发推力' : '柔性制动';
    annotationLayer.appendChild(tLabel);
  }

  // 阶段1:速度标注
  if (state.phase === 1 && state.shuttleSpeed > 2) {
    const sLabel = createSvgEl('text', {
      x: sx + 120, y: 330,
      'text-anchor': 'middle',
      fill: '#00e5ff',
      'font-size': 11,
      'font-family': 'Orbitron',
      'font-weight': 700,
      opacity: 0.6,
      filter: 'url(#softGlow)'
    });
    sLabel.textContent = `→ ${state.shuttleSpeed.toFixed(1)} m/s`;
    annotationLayer.appendChild(sLabel);
  }
}

// ============================
// 主动画更新
// ============================
function updateAutoPhase(dt) {
  const p = state.phase;
  const t = state.time;
  const cfg = PHASES[p] || PHASES[0];
  const progress = clamp(t / cfg.duration, 0, 1);

  switch (p) {
    case 0: // 待机
      state.shuttleSpeed = 0;
      state.vibration = 0;
      state.dampingForce = 0;
      state.forkExtension = 0;
      state.forkSpeed = 0;
      state.vcmForce = 0;
      state.vcmCurrent = 0;
      state.eddyIntensity = 0;
      break;

    case 1: { // 高速直行
      const accelT = clamp(t / 1.0, 0, 1);
      state.shuttleSpeed = easeOut(accelT) * 5;
      // 无阻尼时的振幅较大,有阻尼时被抑制
      const rawVibration = state.shuttleSpeed * 1.2; // 无阻尼振幅
      const dampedVibration = rawVibration * (1 - state.eddyIntensity * 0.92);
      state.vibration = lerp(state.vibration, dampedVibration, dt * 3);
      // 涡流阻尼力与速度成正比
      state.eddyIntensity = clamp(state.shuttleSpeed / 5, 0, 1);
      state.dampingForce = state.eddyIntensity * 180;
      break;
    }

    case 2: { // 减速
      const decelProgress = easeInOut(progress);
      state.shuttleSpeed = lerp(5, 0, decelProgress);
      state.eddyIntensity = clamp(state.shuttleSpeed / 5, 0, 1);
      state.dampingForce = state.eddyIntensity * 180;
      state.vibration = lerp(state.vibration, state.shuttleSpeed * 0.15, dt * 4);
      // 极低速时显示机械导向提示
      if (state.shuttleSpeed < 0.5 && state.shuttleSpeed > 0.05) {
        state.dampingForce = 0;
      }
      break;
    }

    case 3: { // 音圈电机伸叉
      state.shuttleSpeed = 0;
      state.eddyIntensity = lerp(state.eddyIntensity, 0, dt * 2);
      state.dampingForce = 0;
      state.vibration = lerp(state.vibration, 0, dt * 3);

      const extendProgress = easeIn(clamp(progress * 1.2, 0, 1));
      state.forkExtension = extendProgress;
      state.forkSpeed = progress < 0.8 ? 1.0 : lerp(1.0, 0.3, (progress - 0.8) / 0.2);
      state.vcmForce = progress < 0.8 ? 500 : lerp(500, 200, (progress - 0.8) / 0.2);
      state.vcmCurrent = state.vcmForce / 20; // 简化
      break;
    }

    case 4: { // 柔性制动
      const brakeProgress = easeOut(progress);
      state.forkExtension = lerp(state.forkExtension, 1, dt * 1.5);
      state.forkSpeed = lerp(state.forkSpeed, 0, dt * 2);
      state.vcmForce = lerp(state.vcmForce, 0, dt * 2);
      state.vcmCurrent = lerp(state.vcmCurrent, 0, dt * 2.5);
      // 电流波形逐渐平滑
      break;
    }

    case 5: // 完成
      state.forkExtension = 1;
      state.forkSpeed = 0;
      state.vcmForce = 0;
      state.vcmCurrent = lerp(state.vcmCurrent, 0, dt * 3);
      state.vibration = 0;
      break;
  }

  // 更新穿梭车位置
  state.shuttleX += state.shuttleSpeed * dt * 55; // 缩放到SVG坐标

  // 振动相位
  state.vibrationPhase += dt * 25;

  // 车轮旋转
  state.wheelAngle += state.shuttleSpeed * dt * 200;

  // 阶段切换
  if (t >= cfg.duration) {
    if (p < 5) {
      state.phase = p + 1;
      state.time = 0;
    } else {
      // 循环重置
      state.phase = 0;
      state.time = 0;
      state.shuttleX = 80;
    }
  }
}

function updateManualPhase(dt) {
  const targetSpeed = parseFloat(manualSpeedSlider.value);
  const targetFork = parseFloat(manualForkSlider.value) / 100;

  state.shuttleSpeed = lerp(state.shuttleSpeed, targetSpeed, dt * 3);
  state.forkExtension = lerp(state.forkExtension, targetFork, dt * 4);

  state.eddyIntensity = clamp(state.shuttleSpeed / 5, 0, 1);
  state.dampingForce = state.eddyIntensity * 180;
  const rawVib = state.shuttleSpeed * 1.2;
  state.vibration = rawVib * (1 - state.eddyIntensity * 0.92);

  // VCM
  if (targetFork > state.forkExtension + 0.01) {
    state.vcmForce = lerp(state.vcmForce, 500, dt * 5);
    state.forkSpeed = lerp(state.forkSpeed, 1, dt * 3);
  } else if (targetFork < state.forkExtension - 0.01) {
    state.vcmForce = lerp(state.vcmForce, -300, dt * 5);
    state.forkSpeed = lerp(state.forkSpeed, -0.5, dt * 3);
  } else {
    state.vcmForce = lerp(state.vcmForce, 0, dt * 5);
    state.forkSpeed = lerp(state.forkSpeed, 0, dt * 3);
  }
  state.vcmCurrent = Math.abs(state.vcmForce) / 20;

  state.shuttleX += state.shuttleSpeed * dt * 55;
  state.vibrationPhase += dt * 25;
  state.wheelAngle += state.shuttleSpeed * dt * 200;

  // 位置循环
  if (state.shuttleX > 900) state.shuttleX = 80;
  if (state.shuttleX < 50) state.shuttleX = 50;
}

// ============================
// 渲染
// ============================
function render() {
  const sx = state.shuttleX;
  const vibY = Math.sin(state.vibrationPhase) * state.vibration * 1.2;

  // 穿梭车位置 + 振动偏移
  shuttleGroup.setAttribute('transform', `translate(${sx}, ${vibY})`);

  // 车轮旋转
  const wheelRot = state.wheelAngle;
  // 用spoke旋转表示车轮转动
  spoke1.setAttribute('transform', `rotate(${wheelRot} 55 458)`);
  spoke2.setAttribute('transform', `rotate(${wheelRot} 185 458)`);

  // 货叉
  const forkLen = state.forkExtension * 200;
  forkBody.setAttribute('width', Math.max(0, forkLen));
  forkBody.setAttribute('x', 280);

  // 货叉尖端
  if (forkLen > 20) {
    forkTip.setAttribute('opacity', 1);
    forkTip.setAttribute('transform', `translate(${280 + forkLen}, 0)`);
  } else {
    forkTip.setAttribute('opacity', 0);
  }

  // 货叉标签
  if (forkLen > 30) {
    forkLabel.setAttribute('opacity', 0.8);
    forkLabel.setAttribute('transform', `translate(${280 + forkLen * 0.4}, 0)`);
  } else {
    forkLabel.setAttribute('opacity', 0);
  }

  // 目标料箱
  if (state.phase >= 3 || (state.mode === 'manual' && state.forkExtension > 0.05)) {
    targetBin.setAttribute('opacity', 1);
    targetBin.setAttribute('transform', `translate(${sx + 500}, 0)`);
  } else {
    targetBin.setAttribute('opacity', 0);
  }

  // VCM线圈发光
  const coilGlow = clamp(state.vcmCurrent / 25, 0, 1);
  vcmCoil.setAttribute('stroke', coilGlow > 0.3 ? '#ff8f00' : '#cd7f32');
  vcmCoil.setAttribute('stroke-width', 2 + coilGlow * 2);
  vcmCoil.setAttribute('opacity', 0.7 + coilGlow * 0.3);
  if (coilGlow > 0.3) {
    vcmCoil.setAttribute('filter', 'url(#glowOrange)');
  } else {
    vcmCoil.removeAttribute('filter');
  }

  // 更新效果层
  updateEddyCircles(0.016, sx);
  updateFieldLines(0.016, sx);
  updateVcmForces(sx);
  updateDampingForces(sx);
  updateVibration(0.016);
  updateAnnotations();
  updateWaveform();

  // 更新参数显示
  paramSpeed.textContent = state.shuttleSpeed.toFixed(1) + ' m/s';
  paramDamping.textContent = state.dampingForce.toFixed(0) + ' N';
  paramForkSpeed.textContent = state.forkSpeed.toFixed(2) + ' m/s';
  paramVcmForce.textContent = state.vcmForce.toFixed(0) + ' N';
  paramVcmCurrent.textContent = state.vcmCurrent.toFixed(1) + ' A';
  paramVibration.textContent = state.vibration.toFixed(1) + ' mm';

  // 参数颜色动态
  paramVibration.setAttribute('fill', state.vibration > 3 ? '#ff5252' : state.vibration > 1 ? '#ffab00' : '#00e676');

  // 阶段文本
  if (state.mode === 'auto') {
    const cfg = PHASES[state.phase] || PHASES[0];
    phaseText.textContent = cfg.name;
  } else {
    phaseText.textContent = 'MANUAL';
  }

  // 手动模式参数显示
  if (state.mode === 'manual') {
    manualSpeedVal.textContent = state.shuttleSpeed.toFixed(1);
    manualForkVal.textContent = (state.forkExtension * 100).toFixed(0) + '%';
  }
}

// ============================
// 主循环
// ============================
let lastTime = 0;

function animate(timestamp) {
  if (!lastTime) lastTime = timestamp;
  const rawDt = (timestamp - lastTime) / 1000;
  lastTime = timestamp;
  const dt = Math.min(rawDt, 0.05) * state.speedMul;

  if (state.running) {
    state.time += dt;
    if (state.mode === 'auto') {
      updateAutoPhase(dt);
    } else {
      updateManualPhase(dt);
    }
  }

  render();
  requestAnimationFrame(animate);
}

// ============================
// 控制绑定
// ============================
btnPlay.addEventListener('click', () => {
  state.mode = 'auto';
  state.running = true;
  btnPlay.classList.add('active');
  btnManual.classList.remove('active');
  manualControls.style.display = 'none';
});

btnReset.addEventListener('click', () => {
  state.phase = 0;
  state.time = 0;
  state.shuttleX = 80;
  state.shuttleSpeed = 0;
  state.vibration = 0;
  state.dampingForce = 0;
  state.forkExtension = 0;
  state.forkSpeed = 0;
  state.vcmForce = 0;
  state.vcmCurrent = 0;
  state.eddyIntensity = 0;
  state.waveformHistory = [];
  state.running = true;
});

btnManual.addEventListener('click', () => {
  state.mode = 'manual';
  state.running = true;
  btnManual.classList.add('active');
  btnPlay.classList.remove('active');
  manualControls.style.display = 'flex';
  // 重置到合理位置
  state.shuttleX = 200;
  state.phase = 3; // 视觉上显示为手动模式
});

speedMulSlider.addEventListener('input', (e) => {
  state.speedMul = parseFloat(e.target.value);
  speedMulVal.textContent = state.speedMul.toFixed(1) + 'x';
});

manualSpeedSlider.addEventListener('input', (e) => {
  // 手动模式下直接设置目标速度
});

manualForkSlider.addEventListener('input', (e) => {
  // 手动模式下直接设置目标叉行程
});

// ============================
// 初始化
// ============================
initEddyCircles();
requestAnimationFrame(animate);
</script>
</body>
</html>

这段代码实现了一个完整的 IFR 原理动画,以下是核心设计说明:

视觉叙事与 IFR 思想映射

  1. 场代实(核心 IFR):动画全程不展示"删除前的机械结构",而是直接呈现最终理想解——涡流阻尼器和音圈电机的工作状态。涡流效果用旋转的虚线椭圆环表示,在轨道侧面上动态生成,强度与车速成正比,直观呈现"振动速度越快,阻尼力越强"这一自适应性。

  2. 资源利用:永磁体利用穿梭车自身的运动速度作为"资源"——无需外部供能,运动本身即产生阻尼。动画中通过磁场线(从永磁体延伸到轨道侧面的虚线弧)和涡流环的同屏出现,暗示这种"自给自足"的工作机理。

  3. 视觉引导

    • 涡流阻尼:青色(#00e5ff)发光效果 + 阻尼力箭头 + 车体振动波形线,三者联动——涡流越强,振幅波形越小
    • 音圈电机:橙色(#ff6d00)力箭头 + VCM线圈发光强度变化 + 实时电流波形面板
    • 0.5mm间隙:在高速阶段自动高亮标注,强调非接触特性
  4. 交互性

    • AUTO PLAY:自动循环演示完整时序(高速→减速→伸叉→柔性制动→完成)
    • MANUAL:切换手动模式,分别控制"车速度"和"叉行程"滑块,独立体验两个创新点
    • 速度倍率:0.3x~2.0x 可调,便于慢放观察细节
  5. 右侧信息面板:涡流阻尼器截面详图(含间隙标注和旋转涡流)、洛伦兹力公式(F=BIL)、VCM电流波形实时显示,为原理理解提供多维度支撑。

积分规则:第一轮对话扣减6分,后续每轮扣4分