分享图
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=Chakra+Petch:wght@300;400;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<style>
  :root {
    --bg: #080c18;
    --bg2: #0d1225;
    --fg: #d8dfe8;
    --muted: #5a6a82;
    --accent: #00e5ff;
    --accent2: #00b8d4;
    --brass: #c9a54e;
    --brass-light: #e0c97f;
    --steel: #7b8fa6;
    --steel-dark: #4a5d73;
    --danger: #ff5252;
    --safe: #00e676;
    --card: #111827;
    --border: #1e2a40;
    --ground: #1a2236;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: var(--bg);
    color: var(--fg);
    font-family: 'Chakra Petch', sans-serif;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    overflow-x: hidden;
  }
  /* 标题栏 */
  .top-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 32px;
    background: linear-gradient(180deg, rgba(13,18,37,0.95), rgba(8,12,24,0.8));
    border-bottom: 1px solid var(--border);
    position: relative;
    z-index: 10;
  }
  .top-bar h1 {
    font-size: 20px;
    font-weight: 600;
    letter-spacing: 2px;
    color: var(--accent);
    text-transform: uppercase;
  }
  .top-bar .subtitle {
    font-family: 'Share Tech Mono', monospace;
    font-size: 12px;
    color: var(--muted);
    letter-spacing: 1px;
  }
  .ifr-badge {
    background: linear-gradient(135deg, var(--accent), #0091ea);
    color: #000;
    font-size: 11px;
    font-weight: 700;
    padding: 4px 12px;
    border-radius: 3px;
    letter-spacing: 2px;
  }
  /* 主容器 */
  .main-wrap {
    flex: 1;
    display: flex;
    position: relative;
    overflow: hidden;
  }
  /* SVG 场景容器 */
  .scene-container {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    min-height: 500px;
  }
  .scene-container svg {
    width: 100%;
    max-width: 1200px;
    height: auto;
    max-height: 75vh;
  }
  /* 右侧数据面板 */
  .data-panel {
    width: 260px;
    background: var(--card);
    border-left: 1px solid var(--border);
    padding: 20px 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    overflow-y: auto;
  }
  .data-panel h2 {
    font-size: 13px;
    font-weight: 600;
    color: var(--accent);
    letter-spacing: 2px;
    text-transform: uppercase;
    margin-bottom: 4px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
  }
  .data-item {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .data-item .label {
    font-family: 'Share Tech Mono', monospace;
    font-size: 10px;
    color: var(--muted);
    letter-spacing: 1px;
    text-transform: uppercase;
  }
  .data-item .value {
    font-family: 'Share Tech Mono', monospace;
    font-size: 22px;
    font-weight: 700;
    color: var(--fg);
    line-height: 1;
  }
  .data-item .value.accent { color: var(--accent); }
  .data-item .value.danger { color: var(--danger); }
  .data-item .value.safe { color: var(--safe); }
  .data-item .unit {
    font-size: 12px;
    color: var(--muted);
    margin-left: 4px;
  }
  .force-bar-wrap {
    width: 100%;
    height: 8px;
    background: var(--border);
    border-radius: 4px;
    overflow: hidden;
    margin-top: 4px;
  }
  .force-bar {
    height: 100%;
    border-radius: 4px;
    transition: width 0.6s ease, background 0.6s ease;
  }
  .phase-indicator {
    background: rgba(0,229,255,0.06);
    border: 1px solid rgba(0,229,255,0.15);
    border-radius: 6px;
    padding: 10px 12px;
    margin-top: 4px;
  }
  .phase-indicator .phase-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--accent);
    margin-bottom: 4px;
  }
  .phase-indicator .phase-desc {
    font-size: 11px;
    color: var(--muted);
    line-height: 1.5;
  }
  .lock-badge {
    display: inline-block;
    font-family: 'Share Tech Mono', monospace;
    font-size: 11px;
    font-weight: 700;
    padding: 3px 10px;
    border-radius: 3px;
    letter-spacing: 1px;
    margin-top: 4px;
  }
  .lock-badge.active {
    background: rgba(0,230,118,0.15);
    color: var(--safe);
    border: 1px solid rgba(0,230,118,0.3);
  }
  .lock-badge.inactive {
    background: rgba(90,106,130,0.15);
    color: var(--muted);
    border: 1px solid rgba(90,106,130,0.2);
  }
  /* 底部控制栏 */
  .control-bar {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 14px 32px;
    background: var(--card);
    border-top: 1px solid var(--border);
    flex-wrap: wrap;
  }
  .ctrl-btn {
    font-family: 'Chakra Petch', sans-serif;
    font-size: 13px;
    font-weight: 600;
    padding: 8px 20px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg2);
    color: var(--fg);
    cursor: pointer;
    transition: all 0.2s;
    letter-spacing: 1px;
    user-select: none;
  }
  .ctrl-btn:hover { border-color: var(--accent); color: var(--accent); }
  .ctrl-btn:active { transform: scale(0.96); }
  .ctrl-btn.primary {
    background: linear-gradient(135deg, var(--accent2), var(--accent));
    color: #000;
    border: none;
  }
  .ctrl-btn.primary:hover { opacity: 0.85; }
  .ctrl-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
  }
  .slider-group {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .slider-group label {
    font-family: 'Share Tech Mono', monospace;
    font-size: 11px;
    color: var(--muted);
    white-space: nowrap;
  }
  .slider-group input[type=range] {
    width: 120px;
    accent-color: var(--accent);
    cursor: pointer;
  }
  .slider-group .slider-val {
    font-family: 'Share Tech Mono', monospace;
    font-size: 12px;
    color: var(--accent);
    min-width: 48px;
  }
  /* 阶段时间线 */
  .timeline {
    display: flex;
    align-items: center;
    gap: 0;
    margin-left: auto;
  }
  .timeline .step {
    display: flex;
    align-items: center;
    gap: 0;
  }
  .timeline .dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--border);
    border: 2px solid var(--steel-dark);
    transition: all 0.3s;
    cursor: pointer;
  }
  .timeline .dot.active {
    background: var(--accent);
    border-color: var(--accent);
    box-shadow: 0 0 8px var(--accent);
  }
  .timeline .dot.done {
    background: var(--accent2);
    border-color: var(--accent2);
  }
  .timeline .line {
    width: 20px;
    height: 2px;
    background: var(--border);
    transition: background 0.3s;
  }
  .timeline .line.done { background: var(--accent2); }
  /* 提示文本 */
  .hint-text {
    font-family: 'Share Tech Mono', monospace;
    font-size: 11px;
    color: var(--muted);
    font-style: italic;
  }
  @media (max-width: 900px) {
    .data-panel { display: none; }
    .control-bar { padding: 10px 16px; gap: 8px; }
  }
</style>
</head>
<body>

<div class="top-bar">
  <div>
    <h1>气浮滑块 + 差动螺旋千斤顶</h1>
    <div class="subtitle">Air-Floating Slide & Differential Screw Jack &mdash; Principle Animation</div>
  </div>
  <div class="ifr-badge">IFR 最终理想解</div>
</div>

<div class="main-wrap">
  <div class="scene-container">
    <svg id="scene" viewBox="0 0 1100 680" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <!-- 背景网格 -->
        <pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
          <path d="M 30 0 L 0 0 0 30" fill="none" stroke="rgba(30,42,64,0.4)" stroke-width="0.5"/>
        </pattern>
        <!-- 气膜辉光 -->
        <filter id="airGlow" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur stdDeviation="6" result="blur"/>
          <feComposite in="SourceGraphic" in2="blur" operator="over"/>
        </filter>
        <filter id="airGlowStrong" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur stdDeviation="12" result="blur"/>
          <feMerge>
            <feMergeNode in="blur"/>
            <feMergeNode in="SourceGraphic"/>
          </feMerge>
        </filter>
        <!-- 金属渐变 -->
        <linearGradient id="brassGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#e0c97f"/>
          <stop offset="50%" stop-color="#c9a54e"/>
          <stop offset="100%" stop-color="#a07e30"/>
        </linearGradient>
        <linearGradient id="steelGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#8fa4b8"/>
          <stop offset="100%" stop-color="#5a7088"/>
        </linearGradient>
        <linearGradient id="fixtureGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#6b7d92"/>
          <stop offset="100%" stop-color="#4a5d73"/>
        </linearGradient>
        <linearGradient id="groundGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#1e2a40"/>
          <stop offset="100%" stop-color="#141c2e"/>
        </linearGradient>
        <!-- 气膜粒子渐变 -->
        <radialGradient id="particleGrad">
          <stop offset="0%" stop-color="#00e5ff" stop-opacity="0.8"/>
          <stop offset="100%" stop-color="#00e5ff" stop-opacity="0"/>
        </radialGradient>
        <!-- 螺纹图案 -->
        <pattern id="threadPattern" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(60)">
          <line x1="0" y1="0" x2="0" y2="8" stroke="#a07e30" stroke-width="1.5" opacity="0.6"/>
        </pattern>
        <!-- 风机叶片 -->
        <clipPath id="fanClip">
          <circle cx="0" cy="0" r="16"/>
        </clipPath>
      </defs>

      <!-- 背景网格 -->
      <rect width="1100" height="680" fill="var(--bg)"/>
      <rect width="1100" height="680" fill="url(#grid)"/>

      <!-- 地面 -->
      <rect x="0" y="530" width="1100" height="150" fill="url(#groundGrad)"/>
      <line x1="0" y1="530" x2="1100" y2="530" stroke="#2a3a54" stroke-width="2"/>
      <!-- 地面纹理线 -->
      <g opacity="0.15">
        <line x1="0" y1="560" x2="1100" y2="560" stroke="#3a4a64" stroke-width="0.5" stroke-dasharray="4,8"/>
        <line x1="0" y1="590" x2="1100" y2="590" stroke="#3a4a64" stroke-width="0.5" stroke-dasharray="4,8"/>
        <line x1="0" y1="620" x2="1100" y2="620" stroke="#3a4a64" stroke-width="0.5" stroke-dasharray="4,8"/>
      </g>
      <text x="550" y="655" text-anchor="middle" fill="#2a3a54" font-family="'Share Tech Mono', monospace" font-size="11" letter-spacing="3">HIGH-PRECISION FLAT FLOOR · SEAMLESS SURFACE</text>

      <!-- 气膜效果组 -->
      <g id="airFilmGroup" opacity="0">
        <!-- 气膜辉光条 -->
        <rect id="airGlow1" x="195" y="524" width="90" height="8" rx="4" fill="#00e5ff" opacity="0.5" filter="url(#airGlowStrong)"/>
        <rect id="airGlow2" x="345" y="524" width="90" height="8" rx="4" fill="#00e5ff" opacity="0.5" filter="url(#airGlowStrong)"/>
        <rect id="airGlow3" x="495" y="524" width="90" height="8" rx="4" fill="#00e5ff" opacity="0.5" filter="url(#airGlowStrong)"/>
        <rect id="airGlow4" x="645" y="524" width="90" height="8" rx="4" fill="#00e5ff" opacity="0.5" filter="url(#airGlowStrong)"/>
        <!-- 气膜间隙亮线 -->
        <line x1="195" y1="528" x2="285" y2="528" stroke="#00e5ff" stroke-width="1.5" opacity="0.9"/>
        <line x1="345" y1="528" x2="435" y2="528" stroke="#00e5ff" stroke-width="1.5" opacity="0.9"/>
        <line x1="495" y1="528" x2="585" y2="528" stroke="#00e5ff" stroke-width="1.5" opacity="0.9"/>
        <line x1="645" y1="528" x2="735" y2="528" stroke="#00e5ff" stroke-width="1.5" opacity="0.9"/>
        <!-- 气膜厚度标注 -->
        <g id="airGapAnnotation" opacity="0">
          <line x1="780" y1="524" x2="780" y2="532" stroke="#00e5ff" stroke-width="0.8" stroke-dasharray="2,2"/>
          <line x1="775" y1="524" x2="785" y2="524" stroke="#00e5ff" stroke-width="0.8"/>
          <line x1="775" y1="532" x2="785" y2="532" stroke="#00e5ff" stroke-width="0.8"/>
          <text x="795" y="530" fill="#00e5ff" font-family="'Share Tech Mono', monospace" font-size="10" id="airGapLabel">0.03mm</text>
        </g>
      </g>

      <!-- 气膜粒子容器 -->
      <g id="particleContainer"></g>

      <!-- ========== 车体主组 ========== -->
      <g id="cartGroup">
        <!-- 底盘基板 -->
        <rect x="180" y="470" width="560" height="40" rx="3" fill="url(#brassGrad)" stroke="#a07e30" stroke-width="1"/>
        <!-- 底盘加强筋 -->
        <rect x="200" y="478" width="520" height="4" rx="1" fill="rgba(160,126,48,0.4)"/>
        <rect x="200" y="496" width="520" height="4" rx="1" fill="rgba(160,126,48,0.3)"/>

        <!-- 气浮垫 ×4 -->
        <g>
          <rect x="200" y="510" width="80" height="18" rx="3" fill="#3a5068" stroke="#4a6078" stroke-width="1"/>
          <rect x="205" y="512" width="70" height="5" rx="2" fill="#2a3e54" opacity="0.6"/>
          <text x="240" y="528" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="7">PAD</text>
        </g>
        <g>
          <rect x="350" y="510" width="80" height="18" rx="3" fill="#3a5068" stroke="#4a6078" stroke-width="1"/>
          <rect x="355" y="512" width="70" height="5" rx="2" fill="#2a3e54" opacity="0.6"/>
          <text x="390" y="528" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="7">PAD</text>
        </g>
        <g>
          <rect x="500" y="510" width="80" height="18" rx="3" fill="#3a5068" stroke="#4a6078" stroke-width="1"/>
          <rect x="505" y="512" width="70" height="5" rx="2" fill="#2a3e54" opacity="0.6"/>
          <text x="540" y="528" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="7">PAD</text>
        </g>
        <g>
          <rect x="650" y="510" width="80" height="18" rx="3" fill="#3a5068" stroke="#4a6078" stroke-width="1"/>
          <rect x="655" y="512" width="70" height="5" rx="2" fill="#2a3e54" opacity="0.6"/>
          <text x="690" y="528" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="7">PAD</text>
        </g>

        <!-- 风机 ×3 -->
        <g id="fan1" transform="translate(260,455)">
          <circle cx="0" cy="0" r="17" fill="#1a2a3e" stroke="#3a5068" stroke-width="1"/>
          <g id="fanBlade1" clip-path="url(#fanClip)">
            <rect x="-2" y="-16" width="4" height="32" rx="1" fill="#5a7a98"/>
            <rect x="-16" y="-2" width="32" height="4" rx="1" fill="#5a7a98"/>
          </g>
          <circle cx="0" cy="0" r="4" fill="#2a3e54" stroke="#4a6078" stroke-width="0.5"/>
        </g>
        <g id="fan2" transform="translate(460,455)">
          <circle cx="0" cy="0" r="17" fill="#1a2a3e" stroke="#3a5068" stroke-width="1"/>
          <g id="fanBlade2" clip-path="url(#fanClip)">
            <rect x="-2" y="-16" width="4" height="32" rx="1" fill="#5a7a98"/>
            <rect x="-16" y="-2" width="32" height="4" rx="1" fill="#5a7a98"/>
          </g>
          <circle cx="0" cy="0" r="4" fill="#2a3e54" stroke="#4a6078" stroke-width="0.5"/>
        </g>
        <g id="fan3" transform="translate(660,455)">
          <circle cx="0" cy="0" r="17" fill="#1a2a3e" stroke="#3a5068" stroke-width="1"/>
          <g id="fanBlade3" clip-path="url(#fanClip)">
            <rect x="-2" y="-16" width="4" height="32" rx="1" fill="#5a7a98"/>
            <rect x="-16" y="-2" width="32" height="4" rx="1" fill="#5a7a98"/>
          </g>
          <circle cx="0" cy="0" r="4" fill="#2a3e54" stroke="#4a6078" stroke-width="0.5"/>
        </g>

        <!-- 风机气流指示箭头 -->
        <g id="fanArrows" opacity="0">
          <path d="M260,438 L260,418 M254,424 L260,414 L266,424" stroke="#00e5ff" stroke-width="1.5" fill="none" opacity="0.6"/>
          <path d="M460,438 L460,418 M454,424 L460,414 L466,424" stroke="#00e5ff" stroke-width="1.5" fill="none" opacity="0.6"/>
          <path d="M660,438 L660,418 M654,424 L660,414 L666,424" stroke="#00e5ff" stroke-width="1.5" fill="none" opacity="0.6"/>
        </g>

        <!-- 差动螺旋千斤顶外壳 -->
        <g id="jackHousing">
          <rect x="430" y="260" width="60" height="210" rx="3" fill="url(#steelGrad)" stroke="#5a7088" stroke-width="1"/>
          <!-- 外壳细节线 -->
          <line x1="435" y1="270" x2="485" y2="270" stroke="#8fa4b8" stroke-width="0.5" opacity="0.3"/>
          <line x1="435" y1="460" x2="485" y2="460" stroke="#8fa4b8" stroke-width="0.5" opacity="0.3"/>
          <!-- 螺纹可见区域 -->
          <rect id="threadArea" x="440" y="265" width="40" height="195" fill="url(#threadPattern)" opacity="0.4" clip-path="url(#jackClip)"/>
          <!-- 差动螺旋标注 -->
          <text x="460" y="375" text-anchor="middle" fill="#8fa4b8" font-family="'Share Tech Mono', monospace" font-size="8" opacity="0.6" transform="rotate(-90,460,375)">DIFFERENTIAL SCREW</text>
        </g>

        <!-- 螺旋顶杆 -->
        <g id="screwRod">
          <rect x="448" y="240" width="24" height="30" rx="2" fill="#8fa4b8" stroke="#6b8aa0" stroke-width="0.8"/>
          <!-- 顶杆螺纹 -->
          <line x1="450" y1="245" x2="470" y2="245" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
          <line x1="450" y1="250" x2="470" y2="250" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
          <line x1="450" y1="255" x2="470" y2="255" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
          <line x1="450" y1="260" x2="470" y2="260" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
          <line x1="450" y1="265" x2="470" y2="265" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
        </g>

        <!-- 手电钻 -->
        <g id="handDrill" transform="translate(490, 340)">
          <rect x="0" y="-12" width="50" height="24" rx="4" fill="#e05050" stroke="#c03030" stroke-width="1"/>
          <rect x="48" y="-6" width="12" height="12" rx="2" fill="#c03030"/>
          <circle cx="14" cy="0" r="8" fill="#b02828" stroke="#901818" stroke-width="0.8"/>
          <circle cx="14" cy="0" r="3" fill="#801818"/>
          <!-- 钻头夹持器 -->
          <rect x="-8" y="-4" width="8" height="8" rx="1" fill="#888" stroke="#666" stroke-width="0.5"/>
          <text x="25" y="3" text-anchor="middle" fill="#ffaaaa" font-family="'Share Tech Mono', monospace" font-size="7">DRILL</text>
        </g>

        <!-- 升降平台 -->
        <g id="liftPlatform">
          <rect x="260" y="228" width="400" height="16" rx="2" fill="url(#brassGrad)" stroke="#a07e30" stroke-width="1"/>
          <!-- 平台加强筋 -->
          <line x1="280" y1="236" x2="640" y2="236" stroke="rgba(160,126,48,0.3)" stroke-width="1"/>
        </g>

        <!-- 夹具/模具 -->
        <g id="fixtureGroup">
          <rect x="290" y="120" width="340" height="108" rx="4" fill="url(#fixtureGrad)" stroke="#5a7088" stroke-width="1.5"/>
          <!-- 夹具细节 -->
          <rect x="300" y="130" width="320" height="88" rx="2" fill="none" stroke="#6b7d92" stroke-width="0.5" stroke-dasharray="4,4" opacity="0.4"/>
          <!-- T型槽 -->
          <rect x="340" y="140" width="60" height="8" rx="1" fill="#3a5068"/>
          <rect x="420" y="140" width="60" height="8" rx="1" fill="#3a5068"/>
          <rect x="500" y="140" width="60" height="8" rx="1" fill="#3a5068"/>
          <!-- 重量标注 -->
          <text x="460" y="190" text-anchor="middle" fill="#8fa4b8" font-family="'Chakra Petch', sans-serif" font-size="28" font-weight="700">500</text>
          <text x="460" y="210" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="12">KG</text>
          <!-- 吊装孔 -->
          <circle cx="320" cy="155" r="6" fill="#2a3e54" stroke="#4a6078" stroke-width="0.8"/>
          <circle cx="600" cy="155" r="6" fill="#2a3e54" stroke="#4a6078" stroke-width="0.8"/>
        </g>
      </g>

      <!-- ========== 手指推力组 ========== -->
      <g id="fingerGroup" opacity="0">
        <!-- 手指形状 -->
        <g transform="translate(148, 430)">
          <!-- 手掌 -->
          <rect x="-40" y="10" width="48" height="50" rx="10" fill="#e8b89a" stroke="#c4937a" stroke-width="1"/>
          <!-- 食指 -->
          <rect x="-8" y="-50" width="16" height="65" rx="8" fill="#e8b89a" stroke="#c4937a" stroke-width="1"/>
          <!-- 指甲 -->
          <ellipse cx="0" cy="-46" rx="6" ry="5" fill="#f5d0c0" stroke="#c4937a" stroke-width="0.5"/>
          <!-- 推力箭头 -->
          <g id="pushArrow">
            <line x1="10" y1="-30" x2="35" y2="-30" stroke="#ff5252" stroke-width="2.5" stroke-linecap="round"/>
            <polygon points="35,-36 47,-30 35,-24" fill="#ff5252"/>
          </g>
          <!-- 力值标注 -->
          <text id="pushForceLabel" x="30" y="-42" fill="#ff5252" font-family="'Share Tech Mono', monospace" font-size="11" font-weight="700">~0.5N</text>
        </g>
      </g>

      <!-- ========== 标注与说明 ========== -->
      <g id="annotations">
        <!-- 自锁状态标注 -->
        <g id="lockAnnotation" opacity="0">
          <rect x="545" y="285" width="120" height="36" rx="4" fill="rgba(0,230,118,0.1)" stroke="rgba(0,230,118,0.3)" stroke-width="1"/>
          <text x="605" y="300" text-anchor="middle" fill="#00e676" font-family="'Chakra Petch', sans-serif" font-size="11" font-weight="700">SELF-LOCKED</text>
          <text x="605" y="314" text-anchor="middle" fill="rgba(0,230,118,0.6)" font-family="'Share Tech Mono', monospace" font-size="8">MECHANICAL SAFETY</text>
          <!-- 连线 -->
          <line x1="545" y1="303" x2="492" y2="303" stroke="rgba(0,230,118,0.3)" stroke-width="0.8" stroke-dasharray="3,3"/>
        </g>

        <!-- 气浮原理标注 -->
        <g id="airFloatAnnotation" opacity="0">
          <rect x="20" y="395" width="140" height="50" rx="4" fill="rgba(0,229,255,0.08)" stroke="rgba(0,229,255,0.2)" stroke-width="1"/>
          <text x="90" y="414" text-anchor="middle" fill="#00e5ff" font-family="'Chakra Petch', sans-serif" font-size="11" font-weight="600">AIR-FILM ACTIVE</text>
          <text x="90" y="430" text-anchor="middle" fill="rgba(0,229,255,0.5)" font-family="'Share Tech Mono', monospace" font-size="9">μ &lt; 0.001</text>
          <!-- 连线 -->
          <line x1="160" y1="420" x2="195" y2="524" stroke="rgba(0,229,255,0.2)" stroke-width="0.8" stroke-dasharray="3,3"/>
        </g>

        <!-- 风机功率标注 -->
        <g id="fanPowerAnnotation" opacity="0">
          <text x="460" y="405" text-anchor="middle" fill="rgba(0,229,255,0.5)" font-family="'Share Tech Mono', monospace" font-size="9">500W BLOWER</text>
        </g>
      </g>

      <!-- 目标位置标记 -->
      <g id="targetMarker" opacity="0">
        <line x1="830" y1="480" x2="830" y2="530" stroke="#00e676" stroke-width="1.5" stroke-dasharray="4,3"/>
        <polygon points="824,480 836,480 830,470" fill="#00e676" opacity="0.7"/>
        <text x="830" y="555" text-anchor="middle" fill="#00e676" font-family="'Share Tech Mono', monospace" font-size="10" opacity="0.7">TARGET</text>
      </g>

      <!-- 阶段大标题 -->
      <g id="phaseTitle" opacity="0">
        <text id="phaseTitleText" x="550" y="60" text-anchor="middle" fill="rgba(0,229,255,0.15)" font-family="'Chakra Petch', sans-serif" font-size="42" font-weight="700" letter-spacing="6"></text>
      </g>

      <!-- 交互提示 -->
      <g id="dragHint" opacity="0">
        <text x="460" y="580" text-anchor="middle" fill="rgba(0,229,255,0.4)" font-family="'Share Tech Mono', monospace" font-size="10">← DRAG CART TO PUSH →</text>
      </g>
    </svg>
  </div>

  <!-- 右侧数据面板 -->
  <div class="data-panel">
    <h2>参数监控</h2>

    <div class="data-item">
      <span class="label">总载荷</span>
      <span class="value">500<span class="unit">kg</span></span>
    </div>

    <div class="data-item">
      <span class="label">摩擦系数 μ</span>
      <span class="value" id="muValue">0.150</span>
      <div class="force-bar-wrap">
        <div class="force-bar" id="muBar" style="width:100%;background:var(--danger);"></div>
      </div>
    </div>

    <div class="data-item">
      <span class="label">所需推力</span>
      <span class="value" id="forceValue">735<span class="unit">N</span></span>
      <div class="force-bar-wrap">
        <div class="force-bar" id="forceBar" style="width:100%;background:var(--danger);"></div>
      </div>
    </div>

    <div class="data-item">
      <span class="label">气膜厚度</span>
      <span class="value accent" id="gapValue">0<span class="unit">mm</span></span>
    </div>

    <div class="data-item">
      <span class="label">风机功率</span>
      <span class="value" id="fanPowerValue">0<span class="unit">W</span></span>
    </div>

    <div class="data-item">
      <span class="label">螺旋自锁</span>
      <div>
        <span class="lock-badge inactive" id="lockBadge">INACTIVE</span>
      </div>
    </div>

    <div class="phase-indicator">
      <div class="phase-name" id="phaseNameDisplay">待命</div>
      <div class="phase-desc" id="phaseDescDisplay">点击播放按钮启动动画序列</div>
    </div>
  </div>
</div>

<!-- 底部控制栏 -->
<div class="control-bar">
  <button class="ctrl-btn primary" id="btnPlay">播放序列</button>
  <button class="ctrl-btn" id="btnPause" disabled>暂停</button>
  <button class="ctrl-btn" id="btnReset">重置</button>
  <div style="width:1px;height:28px;background:var(--border);margin:0 4px;"></div>
  <button class="ctrl-btn" id="btnPrev" disabled>上一步</button>
  <button class="ctrl-btn" id="btnNext">下一步</button>

  <div class="slider-group">
    <label>气膜厚度</label>
    <input type="range" id="gapSlider" min="10" max="50" value="30" step="1"/>
    <span class="slider-val" id="gapSliderVal">0.030mm</span>
  </div>

  <div class="timeline" id="timeline">
    <!-- 由 JS 动态生成 -->
  </div>

  <span class="hint-text" id="hintText"></span>
</div>

<script>
(function() {
  'use strict';

  // ========== 阶段定义 ==========
  const PHASES = [
    { id: 'idle',        name: '待命',     desc: '夹具放置于换模车上,等待操作', duration: 1200 },
    { id: 'lift',        name: '升起夹具', desc: '手电钻驱动差动螺旋千斤顶,一人轻松摇起500kg夹具,螺纹自锁确保安全', duration: 3500 },
    { id: 'approach',    name: '推至目标', desc: '将换模车推行至目标位置附近', duration: 2500 },
    { id: 'airfilm',     name: '气膜启动', desc: '底盘风机启动,高压气膜形成,摩擦系数骤降至0.001以下', duration: 3000 },
    { id: 'push',        name: '一指推送', desc: '单根手指即可推动500kg换模车精确对位——IFR最终理想解', duration: 3500 },
    { id: 'land',        name: '刚性着地', desc: '风机关闭,气膜消散,底盘刚性接触地面,稳固定位', duration: 2500 },
    { id: 'lower',       name: '降下夹具', desc: '反向驱动差动螺旋,夹具缓慢降落至安装位,完成换模', duration: 3000 },
    { id: 'complete',    name: '换模完成', desc: '全部操作完成,安全可靠', duration: 2000 }
  ];

  // ========== 状态变量 ==========
  let currentPhase = 0;
  let phaseProgress = 0;
  let isPlaying = false;
  let isPaused = false;
  let lastTimestamp = 0;
  let animFrameId = null;

  // 动画插值状态
  let fixtureLift = 0;       // 0~1 夹具升起程度
  let cartOffsetX = 0;       // 车体水平偏移
  let airFilmOpacity = 0;    // 气膜可见度 0~1
  let fanSpeed = 0;          // 风机转速 0~1
  let fingerOpacity = 0;     // 手指可见度 0~1
  let lockActive = false;    // 自锁状态
  let mu = 0.15;             // 摩擦系数
  let pushForce = 735;       // 推力 N
  let fanPower = 0;          // 风机功率
  let airGapMM = 0;          // 气膜厚度 mm
  let targetOpacity = 0;     // 目标标记可见度

  // 风机旋转角度
  let fanAngle = 0;

  // 粒子系统
  let particles = [];
  const MAX_PARTICLES = 60;

  // 拖拽交互
  let isDragging = false;
  let dragStartX = 0;
  let dragCartStartX = 0;

  // DOM 引用
  const svg = document.getElementById('scene');
  const cartGroup = document.getElementById('cartGroup');
  const fixtureGroup = document.getElementById('fixtureGroup');
  const liftPlatform = document.getElementById('liftPlatform');
  const screwRod = document.getElementById('screwRod');
  const airFilmGroup = document.getElementById('airFilmGroup');
  const fingerGroup = document.getElementById('fingerGroup');
  const lockAnnotation = document.getElementById('lockAnnotation');
  const airFloatAnnotation = document.getElementById('airFloatAnnotation');
  const fanPowerAnnotation = document.getElementById('fanPowerAnnotation');
  const targetMarker = document.getElementById('targetMarker');
  const phaseTitle = document.getElementById('phaseTitle');
  const phaseTitleText = document.getElementById('phaseTitleText');
  const dragHint = document.getElementById('dragHint');
  const fanArrows = document.getElementById('fanArrows');
  const airGapAnnotation = document.getElementById('airGapAnnotation');
  const particleContainer = document.getElementById('particleContainer');

  const fanBlade1 = document.getElementById('fanBlade1');
  const fanBlade2 = document.getElementById('fanBlade2');
  const fanBlade3 = document.getElementById('fanBlade3');

  // 数据面板
  const muValueEl = document.getElementById('muValue');
  const muBarEl = document.getElementById('muBar');
  const forceValueEl = document.getElementById('forceValue');
  const forceBarEl = document.getElementById('forceBar');
  const gapValueEl = document.getElementById('gapValue');
  const fanPowerValueEl = document.getElementById('fanPowerValue');
  const lockBadgeEl = document.getElementById('lockBadge');
  const phaseNameDisplay = document.getElementById('phaseNameDisplay');
  const phaseDescDisplay = document.getElementById('phaseDescDisplay');

  // 控件
  const btnPlay = document.getElementById('btnPlay');
  const btnPause = document.getElementById('btnPause');
  const btnReset = document.getElementById('btnReset');
  const btnPrev = document.getElementById('btnPrev');
  const btnNext = document.getElementById('btnNext');
  const gapSlider = document.getElementById('gapSlider');
  const gapSliderVal = document.getElementById('gapSliderVal');
  const hintText = document.getElementById('hintText');

  // ========== 初始化时间线 ==========
  function initTimeline() {
    const tl = document.getElementById('timeline');
    tl.innerHTML = '';
    PHASES.forEach((p, i) => {
      if (i > 0) {
        const line = document.createElement('div');
        line.className = 'line';
        line.id = 'tl-line-' + i;
        tl.appendChild(line);
      }
      const dot = document.createElement('div');
      dot.className = 'dot';
      dot.id = 'tl-dot-' + i;
      dot.title = p.name;
      dot.addEventListener('click', () => jumpToPhase(i));
      tl.appendChild(dot);
    });
    updateTimeline();
  }

  function updateTimeline() {
    PHASES.forEach((_, i) => {
      const dot = document.getElementById('tl-dot-' + i);
      const line = document.getElementById('tl-line-' + i);
      dot.className = 'dot' + (i === currentPhase ? ' active' : (i < currentPhase ? ' done' : ''));
      if (line) line.className = 'line' + (i <= currentPhase ? ' done' : '');
    });
  }

  // ========== 缓动函数 ==========
  function easeInOut(t) {
    return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
  }
  function easeOut(t) {
    return 1 - Math.pow(1 - t, 3);
  }
  function easeIn(t) {
    return t * t * t;
  }

  // ========== 插值 ==========
  function lerp(a, b, t) {
    return a + (b - a) * t;
  }

  // ========== 阶段动画逻辑 ==========
  // 每个阶段定义起始和目标状态,根据 progress 插值
  function getPhaseTargets(phaseId) {
    switch(phaseId) {
      case 'idle':
        return { fixtureLift: 0, cartOffsetX: 0, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: false, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
      case 'lift':
        return { fixtureLift: 1, cartOffsetX: 0, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: true, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
      case 'approach':
        return { fixtureLift: 1, cartOffsetX: 120, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: true, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 1 };
      case 'airfilm':
        return { fixtureLift: 1, cartOffsetX: 120, airFilmOpacity: 1, fanSpeed: 1, fingerOpacity: 0, lockActive: true, mu: 0.001, pushForce: 0.5, fanPower: 500, airGapMM: parseFloat(gapSlider.value) / 1000, targetOpacity: 1 };
      case 'push':
        return { fixtureLift: 1, cartOffsetX: 250, airFilmOpacity: 1, fanSpeed: 1, fingerOpacity: 1, lockActive: true, mu: 0.001, pushForce: 0.5, fanPower: 500, airGapMM: parseFloat(gapSlider.value) / 1000, targetOpacity: 1 };
      case 'land':
        return { fixtureLift: 1, cartOffsetX: 250, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: true, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
      case 'lower':
        return { fixtureLift: 0, cartOffsetX: 250, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: false, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
      case 'complete':
        return { fixtureLift: 0, cartOffsetX: 250, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: false, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
      default:
        return getPhaseTargets('idle');
    }
  }

  // 获取上一阶段的终态
  function getPrevTargets() {
    if (currentPhase === 0) return getPhaseTargets('idle');
    return getPhaseTargets(PHASES[currentPhase - 1].id);
  }

  function getCurrentTargets() {
    return getPhaseTargets(PHASES[currentPhase].id);
  }

  // ========== 渲染 ==========
  function render() {
    const t = easeInOut(phaseProgress);
    const prev = getPrevTargets();
    const curr = getCurrentTargets();

    // 插值当前状态
    fixtureLift = lerp(prev.fixtureLift, curr.fixtureLift, t);
    cartOffsetX = lerp(prev.cartOffsetX, curr.cartOffsetX, t);
    airFilmOpacity = lerp(prev.airFilmOpacity, curr.airFilmOpacity, t);
    fanSpeed = lerp(prev.fanSpeed, curr.fanSpeed, t);
    fingerOpacity = lerp(prev.fingerOpacity, curr.fingerOpacity, t);
    mu = lerp(prev.mu, curr.mu, t);
    pushForce = lerp(prev.pushForce, curr.pushForce, t);
    fanPower = lerp(prev.fanPower, curr.fanPower, t);
    airGapMM = lerp(prev.airGapMM, curr.airGapMM, t);
    targetOpacity = lerp(prev.targetOpacity, curr.targetOpacity, t);

    // 自锁状态(立即切换)
    lockActive = curr.lockActive;

    // 车体水平移动
    cartGroup.setAttribute('transform', `translate(${cartOffsetX}, 0)`);

    // 夹具升起(向上移动)
    const liftAmount = fixtureLift * 80;
    fixtureGroup.setAttribute('transform', `translate(${cartOffsetX}, ${-liftAmount})`);
    liftPlatform.setAttribute('transform', `translate(${cartOffsetX}, ${-liftAmount})`);
    screwRod.setAttribute('transform', `translate(${cartOffsetX}, ${-liftAmount * 0.5})`);

    // 气膜效果
    airFilmGroup.setAttribute('opacity', airFilmOpacity);
    // 气膜辉光脉冲
    if (airFilmOpacity > 0.1) {
      const pulse = 0.5 + 0.3 * Math.sin(Date.now() * 0.005);
      const glowEls = airFilmGroup.querySelectorAll('rect[id^="airGlow"]');
      glowEls.forEach(el => el.setAttribute('opacity', pulse * airFilmOpacity));
    }
    // 气膜厚度标注
    airGapAnnotation.setAttribute('opacity', airFilmOpacity > 0.5 ? (airFilmOpacity - 0.5) * 2 : 0);

    // 风机旋转
    if (fanSpeed > 0.01) {
      fanAngle += fanSpeed * 15;
    }
    fanBlade1.setAttribute('transform', `rotate(${fanAngle})`);
    fanBlade2.setAttribute('transform', `rotate(${fanAngle * 1.1})`);
    fanBlade3.setAttribute('transform', `rotate(${fanAngle * 0.95})`);
    fanArrows.setAttribute('opacity', fanSpeed * 0.8);

    // 手指
    fingerGroup.setAttribute('opacity', fingerOpacity);
    // 手指位置跟随车体
    const fingerX = 148 + cartOffsetX;
    fingerGroup.querySelector('g').setAttribute('transform', `translate(${fingerX}, 430)`);

    // 标注
    lockAnnotation.setAttribute('opacity', lockActive ? 1 : 0);
    lockAnnotation.querySelector('g, rect')?.setAttribute('transform', `translate(${cartOffsetX}, 0)`);
    // 重新定位锁定标注
    const lockRect = lockAnnotation.querySelector('rect');
    const lockText1 = lockAnnotation.querySelectorAll('text')[0];
    const lockText2 = lockAnnotation.querySelectorAll('text')[1];
    const lockLine = lockAnnotation.querySelector('line');
    if (lockRect) {
      lockRect.setAttribute('x', 545 + cartOffsetX);
      lockRect.setAttribute('y', 285 - liftAmount * 0.3);
    }
    if (lockText1) { lockText1.setAttribute('x', 605 + cartOffsetX); lockText1.setAttribute('y', 300 - liftAmount * 0.3); }
    if (lockText2) { lockText2.setAttribute('x', 605 + cartOffsetX); lockText2.setAttribute('y', 314 - liftAmount * 0.3); }
    if (lockLine) { lockLine.setAttribute('x1', 545 + cartOffsetX); lockLine.setAttribute('y1', 303 - liftAmount * 0.3); lockLine.setAttribute('x2', 492 + cartOffsetX); lockLine.setAttribute('y2', 303 - liftAmount * 0.3); }

    airFloatAnnotation.setAttribute('opacity', airFilmOpacity > 0.3 ? Math.min(1, (airFilmOpacity - 0.3) * 2) : 0);
    fanPowerAnnotation.setAttribute('opacity', fanSpeed > 0.3 ? Math.min(1, (fanSpeed - 0.3) * 2) : 0);
    if (fanPowerAnnotation.querySelector('text')) {
      fanPowerAnnotation.querySelector('text').setAttribute('x', 460 + cartOffsetX);
    }

    // 目标标记
    targetMarker.setAttribute('opacity', targetOpacity);

    // 阶段标题
    const phaseObj = PHASES[currentPhase];
    if (phaseProgress < 0.3 && isPlaying) {
      phaseTitle.setAttribute('opacity', 1 - phaseProgress / 0.3);
      phaseTitleText.textContent = phaseObj.name;
    } else {
      phaseTitle.setAttribute('opacity', 0);
    }

    // 拖拽提示
    const showDrag = (PHASES[currentPhase].id === 'push' && isPlaying);
    dragHint.setAttribute('opacity', showDrag ? 0.6 : 0);
    if (showDrag) {
      dragHint.querySelector('text').setAttribute('x', 460 + cartOffsetX);
    }

    // 粒子系统
    updateParticles();

    // 数据面板更新
    updateDataPanel();
  }

  // ========== 粒子系统 ==========
  function updateParticles() {
    if (airFilmOpacity > 0.2 && fanSpeed > 0.3) {
      // 生成新粒子
      const spawnRate = airFilmOpacity * fanSpeed * 3;
      if (Math.random() < spawnRate && particles.length < MAX_PARTICLES) {
        const padIndex = Math.floor(Math.random() * 4);
        const padCenters = [240, 390, 540, 690];
        const px = padCenters[padIndex] + cartOffsetX + (Math.random() - 0.5) * 40;
        const py = 528;
        const dir = Math.random() > 0.5 ? 1 : -1;
        particles.push({
          x: px,
          y: py,
          vx: dir * (1 + Math.random() * 2) * fanSpeed,
          vy: (Math.random() - 0.6) * 0.5,
          life: 1,
          decay: 0.01 + Math.random() * 0.02,
          size: 2 + Math.random() * 3
        });
      }
    }

    // 更新粒子
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.x += p.vx;
      p.y += p.vy;
      p.life -= p.decay;
      if (p.life <= 0) {
        particles.splice(i, 1);
      }
    }

    // 渲染粒子
    while (particleContainer.children.length > particles.length) {
      particleContainer.removeChild(particleContainer.lastChild);
    }
    while (particleContainer.children.length < particles.length) {
      const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
      c.setAttribute('fill', '#00e5ff');
      particleContainer.appendChild(c);
    }
    particles.forEach((p, i) => {
      const el = particleContainer.children[i];
      el.setAttribute('cx', p.x);
      el.setAttribute('cy', p.y);
      el.setAttribute('r', p.size * p.life * airFilmOpacity);
      el.setAttribute('opacity', p.life * 0.6 * airFilmOpacity);
    });
  }

  // ========== 数据面板 ==========
  function updateDataPanel() {
    muValueEl.textContent = mu < 0.01 ? mu.toFixed(3) : mu.toFixed(3);
    muValueEl.className = 'value' + (mu < 0.01 ? ' accent' : ' danger');

    const muPct = Math.min(100, (mu / 0.15) * 100);
    muBarEl.style.width = muPct + '%';
    muBarEl.style.background = mu < 0.01 ? 'var(--accent)' : 'var(--danger)';

    forceValueEl.innerHTML = pushForce < 1 ? pushForce.toFixed(1) + '<span class="unit">N</span>' : Math.round(pushForce) + '<span class="unit">N</span>';
    forceValueEl.className = 'value' + (pushForce < 1 ? ' accent' : ' danger');

    const forcePct = Math.min(100, (pushForce / 735) * 100);
    forceBarEl.style.width = Math.max(forcePct, pushForce < 1 ? 1 : 0) + '%';
    forceBarEl.style.background = pushForce < 1 ? 'var(--accent)' : 'var(--danger)';

    gapValueEl.innerHTML = airGapMM > 0 ? airGapMM.toFixed(3) + '<span class="unit">mm</span>' : '0<span class="unit">mm</span>';
    fanPowerValueEl.innerHTML = Math.round(fanPower) + '<span class="unit">W</span>';

    lockBadgeEl.textContent = lockActive ? 'ACTIVE' : 'INACTIVE';
    lockBadgeEl.className = 'lock-badge ' + (lockActive ? 'active' : 'inactive');

    // 气膜厚度标注文字更新
    const gapLabel = document.getElementById('airGapLabel');
    if (gapLabel) gapLabel.textContent = airGapMM > 0 ? airGapMM.toFixed(3) + 'mm' : '';
  }

  // ========== 动画主循环 ==========
  function animate(timestamp) {
    if (!isPlaying || isPaused) {
      animFrameId = requestAnimationFrame(animate);
      render();
      return;
    }

    if (lastTimestamp === 0) lastTimestamp = timestamp;
    const dt = timestamp - lastTimestamp;
    lastTimestamp = timestamp;

    const phase = PHASES[currentPhase];
    phaseProgress += dt / phase.duration;

    if (phaseProgress >= 1) {
      phaseProgress = 1;
      render();

      // 进入下一阶段
      if (currentPhase < PHASES.length - 1) {
        currentPhase++;
        phaseProgress = 0;
        updateTimeline();
        updatePhaseDisplay();
      } else {
        // 动画完成
        isPlaying = false;
        isPaused = false;
        btnPlay.textContent = '重新播放';
        btnPlay.disabled = false;
        btnPause.disabled = true;
        hintText.textContent = '动画序列已完成';
      }
    }

    render();
    animFrameId = requestAnimationFrame(animate);
  }

  // ========== 控制函数 ==========
  function play() {
    if (currentPhase >= PHASES.length - 1 && phaseProgress >= 1) {
      reset();
    }
    isPlaying = true;
    isPaused = false;
    lastTimestamp = 0;
    btnPlay.disabled = true;
    btnPause.disabled = false;
    hintText.textContent = '正在播放动画序列...';
    updatePhaseDisplay();
  }

  function pause() {
    isPaused = !isPaused;
    btnPause.textContent = isPaused ? '继续' : '暂停';
    if (!isPaused) lastTimestamp = 0;
    hintText.textContent = isPaused ? '已暂停' : '继续播放...';
  }

  function reset() {
    currentPhase = 0;
    phaseProgress = 0;
    isPlaying = false;
    isPaused = false;
    lastTimestamp = 0;
    fixtureLift = 0;
    cartOffsetX = 0;
    airFilmOpacity = 0;
    fanSpeed = 0;
    fingerOpacity = 0;
    lockActive = false;
    mu = 0.15;
    pushForce = 735;
    fanPower = 0;
    airGapMM = 0;
    targetOpacity = 0;
    fanAngle = 0;
    particles = [];
    btnPlay.textContent = '播放序列';
    btnPlay.disabled = false;
    btnPause.textContent = '暂停';
    btnPause.disabled = true;
    hintText.textContent = '';
    updateTimeline();
    updatePhaseDisplay();
    render();
  }

  function nextPhase() {
    if (currentPhase < PHASES.length - 1) {
      // 完成当前阶段
      phaseProgress = 1;
      render();
      // 进入下一阶段
      currentPhase++;
      phaseProgress = 0;
      lastTimestamp = 0;
      updateTimeline();
      updatePhaseDisplay();
      if (isPlaying) {
        // 继续播放
      } else {
        // 单步模式:直接展示该阶段起始状态
        render();
      }
    }
  }

  function prevPhase() {
    if (currentPhase > 0) {
      currentPhase--;
      phaseProgress = 1;
      lastTimestamp = 0;
      updateTimeline();
      updatePhaseDisplay();
      render();
    }
  }

  function jumpToPhase(index) {
    if (index >= 0 && index < PHASES.length) {
      // 先完成之前的所有阶段
      currentPhase = index;
      phaseProgress = 0;
      lastTimestamp = 0;
      if (!isPlaying) {
        // 在单步模式下,直接显示该阶段完成状态
        phaseProgress = 1;
      }
      updateTimeline();
      updatePhaseDisplay();
      render();
    }
  }

  function updatePhaseDisplay() {
    const phase = PHASES[currentPhase];
    phaseNameDisplay.textContent = phase.name;
    phaseDescDisplay.textContent = phase.desc;
  }

  // ========== 拖拽交互 ==========
  function getSVGPoint(evt) {
    const pt = svg.createSVGPoint();
    const clientX = evt.touches ? evt.touches[0].clientX : evt.clientX;
    const clientY = evt.touches ? evt.touches[0].clientY : evt.clientY;
    pt.x = clientX;
    pt.y = clientY;
    const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
    return svgP;
  }

  svg.addEventListener('mousedown', startDrag);
  svg.addEventListener('touchstart', startDrag, { passive: false });

  function startDrag(evt) {
    // 仅在 push 阶段允许拖拽
    if (PHASES[currentPhase].id !== 'push' || !isPlaying) return;
    const pt = getSVGPoint(evt);
    // 检查是否点击在车体区域
    if (pt.y > 100 && pt.y < 540 && pt.x > 150 + cartOffsetX && pt.x < 800 + cartOffsetX) {
      isDragging = true;
      dragStartX = pt.x;
      dragCartStartX = cartOffsetX;
      evt.preventDefault();
    }
  }

  window.addEventListener('mousemove', onDrag);
  window.addEventListener('touchmove', onDrag, { passive: false });

  function onDrag(evt) {
    if (!isDragging) return;
    evt.preventDefault();
    const pt = getSVGPoint(evt);
    const dx = pt.x - dragStartX;
    // 限制拖拽范围
    const newOffset = Math.max(120, Math.min(350, dragCartStartX + dx));
    cartOffsetX = newOffset;
    // 覆盖插值,直接设置
    const curr = getCurrentTargets();
    curr.cartOffsetX = newOffset;
  }

  window.addEventListener('mouseup', endDrag);
  window.addEventListener('touchend', endDrag);

  function endDrag() {
    isDragging = false;
  }

  // ========== 气膜厚度滑块 ==========
  gapSlider.addEventListener('input', function() {
    const val = parseFloat(this.value) / 1000;
    gapSliderVal.textContent = val.toFixed(3) + 'mm';
    // 如果当前在气膜阶段,实时更新
    if (['airfilm', 'push'].includes(PHASES[currentPhase].id)) {
      const curr = getCurrentTargets();
      curr.airGapMM = val;
    }
  });

  // ========== 绑定控件 ==========
  btnPlay.addEventListener('click', play);
  btnPause.addEventListener('click', pause);
  btnReset.addEventListener('click', reset);
  btnNext.addEventListener('click', nextPhase);
  btnPrev.addEventListener('click', prevPhase);

  // ========== 键盘控制 ==========
  document.addEventListener('keydown', (e) => {
    if (e.key === ' ') { e.preventDefault(); isPlaying ? pause() : play(); }
    if (e.key === 'ArrowRight') nextPhase();
    if (e.key === 'ArrowLeft') prevPhase();
    if (e.key === 'r' || e.key === 'R') reset();
  });

  // ========== 启动 ==========
  initTimeline();
  updatePhaseDisplay();
  render();
  animFrameId = requestAnimationFrame(animate);

})();
</script>
</body>
</html>

这是一段完整的高保真 SVG 原理动画代码,实现细节如下:

设计理念与视觉方向

采用"工业蓝图"风格——深色背景搭配精密网格,黄铜色结构件、钢灰色夹具、青色气膜辉光形成三重色调层次。核心创新点(气膜悬浮与差动自锁)通过高对比度的色彩与辉光脉冲强制吸引视线。

IFR 理想解视觉表达

  • 消除矛盾:摩擦系数从 0.15 骤降至 0.001 以下,右侧面板实时显示力值从 735N 崩塌至 0.5N,配合红→青的颜色跳变,直观呈现"重物变轻"的理想态
  • 资源利用:气膜标注「AIR-FILM ACTIVE · μ < 0.001」与自锁标注「SELF-LOCKED · MECHANICAL SAFETY」分别突出空气(免费资源)和纯机械几何(零能耗)的巧妙利用
  • 一指推场景:真实手指图形 + 红色力箭头 + "~0.5N" 标注,将"500kg 一指推"的 IFR 终态视觉化

8 阶段动画时序

待命 → 升起夹具(差动螺旋+自锁亮起)→ 推至目标 → 气膜启动(风机旋转+粒子喷射+辉光扩散)→ 一指推送 → 刚性着地 → 降下夹具 → 完成

交互设计

  • 播放/暂停/重置/步进控制 + 底部时间线可点击跳转
  • 气膜厚度滑块(0.010–0.050mm)实时调节悬浮间隙
  • 在"一指推送"阶段,可直接拖拽车体体验近零摩擦滑移
  • 键盘快捷键:空格播放/暂停,←→步进,R 重置
积分规则:第一轮对话扣减6分,后续每轮扣4分