分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>预紧扭簧铰链与拉索自规整折伞机构 - 原理可视化</title>
  <style>
    :root {
      --bg-dark: #090d16;
      --panel-bg: rgba(15, 23, 42, 0.75);
      --panel-border: rgba(56, 189, 248, 0.2);
      --cyan-accent: #38bdf8;
      --orange-accent: #f59e0b;
      --green-accent: #10b981;
      --text-main: #f1f5f9;
      --text-muted: #94a3b8;
      --font-mono: "SF Mono", "Consolas", "Roboto Mono", monospace;
      --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
    }

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

    body {
      background-color: var(--bg-dark);
      color: var(--text-main);
      font-family: var(--font-sans);
      width: 100vw;
      height: 100vh;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      position: relative;
    }

    /* 顶部微型信息条 - 紧凑精炼,不遮挡主体 */
    .top-bar {
      position: absolute;
      top: 12px;
      left: 16px;
      right: 16px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      z-index: 20;
      pointer-events: none;
    }

    .brand-tag {
      display: flex;
      align-items: center;
      gap: 8px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      padding: 5px 12px;
      border-radius: 20px;
      backdrop-filter: blur(8px);
      box-shadow: 0 4px 20px rgba(0,0,0,0.4);
    }

    .status-dot {
      width: 7px;
      height: 7px;
      border-radius: 50%;
      background-color: var(--cyan-accent);
      box-shadow: 0 0 8px var(--cyan-accent);
      animation: pulse-dot 2s infinite ease-in-out;
    }

    @keyframes pulse-dot {
      0%, 100% { opacity: 0.5; transform: scale(0.9); }
      50% { opacity: 1; transform: scale(1.2); }
    }

    .brand-title {
      font-size: 13px;
      font-weight: 600;
      letter-spacing: 0.5px;
      color: #e2e8f0;
    }

    .brand-sub {
      font-size: 11px;
      color: var(--text-muted);
      font-family: var(--font-mono);
      margin-left: 4px;
    }

    /* 状态遥测浮动条 - 极小字号放置在右上角 */
    .hud-metrics {
      display: flex;
      gap: 10px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      padding: 5px 12px;
      border-radius: 8px;
      backdrop-filter: blur(8px);
    }

    .metric-item {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
    }

    .metric-label {
      font-size: 9px;
      color: var(--text-muted);
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .metric-value {
      font-size: 12px;
      font-family: var(--font-mono);
      font-weight: 600;
      color: var(--cyan-accent);
    }

    /* 中央全屏核心画布 */
    .stage-container {
      flex: 1;
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      position: relative;
      background: radial-gradient(circle at 50% 45%, #131d31 0%, #090d16 80%);
    }

    /* 背景工程栅格 */
    .grid-bg {
      position: absolute;
      inset: 0;
      background-image: 
        linear-gradient(rgba(56, 189, 248, 0.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(56, 189, 248, 0.04) 1px, transparent 1px);
      background-size: 32px 32px;
      pointer-events: none;
    }

    /* 主 SVG 动画视图 */
    svg#mechanism-stage {
      width: 100%;
      height: 100%;
      max-width: 1200px;
      max-height: 820px;
      z-index: 10;
    }

    /* 铰链微观特写画中画 (PIP) - 右下角紧凑浮窗 */
    .detail-pip {
      position: absolute;
      right: 18px;
      bottom: 84px;
      width: 200px;
      height: 160px;
      background: rgba(13, 20, 36, 0.85);
      border: 1px solid rgba(245, 158, 11, 0.35);
      border-radius: 10px;
      padding: 8px;
      backdrop-filter: blur(10px);
      z-index: 20;
      box-shadow: 0 8px 30px rgba(0,0,0,0.6);
      display: flex;
      flex-direction: column;
    }

    .pip-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 4px;
      padding-bottom: 4px;
      border-bottom: 1px solid rgba(255,255,255,0.08);
    }

    .pip-title {
      font-size: 10px;
      font-weight: 600;
      color: var(--orange-accent);
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .pip-tag {
      font-size: 9px;
      font-family: var(--font-mono);
      background: rgba(245, 158, 11, 0.15);
      color: #fbbf24;
      padding: 1px 4px;
      border-radius: 3px;
    }

    .pip-svg {
      flex: 1;
      width: 100%;
      height: 100%;
    }

    /* 底部核心精简控制栏 */
    .bottom-control-bar {
      position: absolute;
      bottom: 14px;
      left: 50%;
      transform: translateX(-50%);
      width: min(680px, 92%);
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      border-radius: 28px;
      padding: 8px 18px;
      display: flex;
      align-items: center;
      gap: 14px;
      backdrop-filter: blur(12px);
      z-index: 30;
      box-shadow: 0 10px 30px rgba(0,0,0,0.5);
    }

    .ctrl-btn {
      background: rgba(56, 189, 248, 0.12);
      border: 1px solid rgba(56, 189, 248, 0.3);
      color: var(--cyan-accent);
      border-radius: 50%;
      width: 32px;
      height: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: all 0.2s;
      flex-shrink: 0;
    }

    .ctrl-btn:hover {
      background: var(--cyan-accent);
      color: #04101e;
      box-shadow: 0 0 12px rgba(56, 189, 248, 0.6);
    }

    .ctrl-slider-group {
      flex: 1;
      display: flex;
      flex-direction: column;
      gap: 3px;
    }

    .slider-labels {
      display: flex;
      justify-content: space-between;
      font-size: 10px;
      color: var(--text-muted);
      font-family: var(--font-mono);
    }

    .slider-container {
      position: relative;
      width: 100%;
      display: flex;
      align-items: center;
    }

    input[type="range"] {
      -webkit-appearance: none;
      width: 100%;
      height: 4px;
      border-radius: 2px;
      background: rgba(255,255,255,0.15);
      outline: none;
      cursor: pointer;
    }

    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: 14px;
      height: 14px;
      border-radius: 50%;
      background: var(--cyan-accent);
      box-shadow: 0 0 8px var(--cyan-accent);
      cursor: pointer;
      transition: transform 0.1s;
    }

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

    .state-badge {
      font-size: 11px;
      font-weight: 600;
      color: #38bdf8;
      min-width: 80px;
      text-align: right;
      font-family: var(--font-mono);
    }

    /* 图例指示器 - 左下角精炼放置 */
    .legend-panel {
      position: absolute;
      left: 18px;
      bottom: 84px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      border-radius: 8px;
      padding: 8px 12px;
      backdrop-filter: blur(8px);
      z-index: 20;
      display: flex;
      flex-direction: column;
      gap: 6px;
      pointer-events: none;
    }

    .legend-item {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 10px;
      color: #cbd5e1;
    }

    .legend-color {
      width: 12px;
      height: 3px;
      border-radius: 2px;
    }

    .legend-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
    }
  </style>
</head>
<body>

  <!-- 顶部轻量 HUD -->
  <header class="top-bar">
    <div class="brand-tag">
      <div class="status-dot"></div>
      <div class="brand-title">自规整折伞机构 <span class="brand-sub">IFR 理想解动态原理</span></div>
    </div>

    <div class="hud-metrics">
      <div class="metric-item">
        <span class="metric-label">拉索工作张力</span>
        <span class="metric-value" id="cable-tension-val">300.0 N</span>
      </div>
      <div class="metric-item">
        <span class="metric-label">扭簧预紧储能</span>
        <span class="metric-value" id="spring-torque-val">0.02 N·m</span>
      </div>
      <div class="metric-item">
        <span class="metric-label">骨段贴合度</span>
        <span class="metric-value" id="alignment-val">100 %</span>
      </div>
    </div>
  </header>

  <!-- 左下角图例说明 -->
  <div class="legend-panel">
    <div class="legend-item">
      <div class="legend-color" style="background: #38bdf8; box-shadow: 0 0 6px #38bdf8;"></div>
      <span>中央张紧拉索 (1.2mm 编织缆)</span>
    </div>
    <div class="legend-item">
      <div class="legend-dot" style="background: #f59e0b; box-shadow: 0 0 6px #f59e0b;"></div>
      <span>内折预紧扭簧铰链 (限位165°)</span>
    </div>
    <div class="legend-item">
      <div class="legend-color" style="background: #94a3b8;"></div>
      <span>玻纤扁骨 (180mm + 160mm)</span>
    </div>
    <div class="legend-item">
      <div class="legend-dot" style="background: #10b981;"></div>
      <span>尾段防划软质导向球头</span>
    </div>
  </div>

  <!-- 右下角局部放大铰链特写 (画中画) -->
  <div class="detail-pip">
    <div class="pip-header">
      <span class="pip-title">铰链内部扭簧剖面</span>
      <span class="pip-tag" id="hinge-pip-mode">锁定受拉</span>
    </div>
    <svg class="pip-svg" viewBox="0 0 160 120" id="pip-hinge-svg">
      <!-- 铰链放大图动态元素 -->
      <g id="pip-dynamic-hinge" transform="translate(80, 60)">
        <!-- 铰链座 -->
        <circle cx="0" cy="0" r="28" fill="#1e293b" stroke="#475569" stroke-width="1.5"/>
        <!-- 限位咬合台阶 -->
        <path d="M -20 -10 L 0 -10 L 15 15" stroke="rgba(245,158,11,0.5)" stroke-dasharray="2 2" fill="none"/>
        <!-- 骨1插芯 -->
        <rect x="-42" y="-5" width="42" height="10" rx="3" fill="#334155" stroke="#64748b"/>
        <!-- 骨2旋转臂 -->
        <g id="pip-rot-arm">
          <rect x="0" y="-5" width="42" height="10" rx="3" fill="#475569" stroke="#94a3b8"/>
          <!-- 旋转轴心 -->
          <circle cx="0" cy="0" r="4" fill="#f59e0b"/>
          <!-- 扭簧螺旋线条 -->
          <path id="pip-spring-coil" d="M -7 -7 A 10 10 0 1 1 7 7" fill="none" stroke="#f59e0b" stroke-width="2.5" stroke-linecap="round"/>
        </g>
        <!-- 拉索穿行轨迹 -->
        <path id="pip-cable-line" d="M -45 -12 L 0 -12 L 45 -12" fill="none" stroke="#38bdf8" stroke-width="2" stroke-dasharray="4 2"/>
        <circle cx="0" cy="-12" r="3" fill="#38bdf8"/>
        <!-- 扭矩力矢量箭头 -->
        <path id="pip-torque-arrow" d="M -16 16 A 22 22 0 0 0 16 16" fill="none" stroke="#f59e0b" stroke-width="1.5" marker-end="url(#arrow)"/>
      </g>
      <defs>
        <marker id="arrow" viewBox="0 0 6 6" refX="4" refY="3" markerWidth="4" markerHeight="4" orient="auto-start-reverse">
          <polygon points="0 0, 6 3, 0 6" fill="#f59e0b"/>
        </marker>
      </defs>
    </svg>
  </div>

  <!-- 中央主舞台 -->
  <main class="stage-container">
    <div class="grid-bg"></div>

    <svg id="mechanism-stage" viewBox="0 0 1000 680" preserveAspectRatio="xMidYMid meet">
      <defs>
        <!-- 渐变定义 -->
        <linearGradient id="canopyGrad" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stop-color="#0284c7" stop-opacity="0.35"/>
          <stop offset="100%" stop-color="#0369a1" stop-opacity="0.1"/>
        </linearGradient>

        <linearGradient id="boneGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stop-color="#cbd5e1"/>
          <stop offset="100%" stop-color="#64748b"/>
        </linearGradient>

        <linearGradient id="shaftGrad" x1="0%" y1="0%" x2="100%" y2="0%">
          <stop offset="0%" stop-color="#334155"/>
          <stop offset="50%" stop-color="#94a3b8"/>
          <stop offset="100%" stop-color="#1e293b"/>
        </linearGradient>

        <!-- 发光滤镜 -->
        <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="3" result="blur"/>
          <feMerge>
            <feMergeNode in="blur"/>
            <feMergeNode in="SourceGraphic"/>
          </feMerge>
        </filter>

        <filter id="glow-orange" x="-30%" y="-30%" width="160%" height="160%">
          <feGaussianBlur stdDeviation="4" result="blur"/>
          <feMerge>
            <feMergeNode in="blur"/>
            <feMergeNode in="SourceGraphic"/>
          </feMerge>
        </filter>
      </defs>

      <!-- 机构几何层(由 JS 动态生成与驱动) -->
      <g id="scene-root">
        <!-- 伞面布面投影 -->
        <path id="canopy-surface-left" fill="url(#canopyGrad)" stroke="rgba(56, 189, 248, 0.4)" stroke-width="1.2"/>
        <path id="canopy-surface-right" fill="url(#canopyGrad)" stroke="rgba(56, 189, 248, 0.4)" stroke-width="1.2"/>

        <!-- 收伞聚拢导向约束外轮廓(淡线提示规整圆柱包络) -->
        <line id="envelope-left" x1="440" y1="120" x2="440" y2="580" stroke="rgba(56,189,248,0.15)" stroke-dasharray="3 3" opacity="0"/>
        <line id="envelope-right" x1="560" y1="120" x2="560" y2="580" stroke="rgba(56,189,248,0.15)" stroke-dasharray="3 3" opacity="0"/>

        <!-- 中棒主轴与伞巢 -->
        <g id="shaft-assembly">
          <!-- 伞顶导向轮与顶巢 -->
          <circle cx="500" cy="115" r="14" fill="#1e293b" stroke="#38bdf8" stroke-width="2"/>
          <circle cx="500" cy="115" r="5" fill="#38bdf8" filter="url(#glow-cyan)"/>
          
          <!-- 中棒主体钢管 -->
          <rect x="494" y="115" width="12" height="470" rx="3" fill="url(#shaftGrad)"/>
          <!-- 刻度线 -->
          <line x1="497" y1="180" x2="497" y2="480" stroke="rgba(255,255,255,0.15)" stroke-dasharray="2 6"/>
          
          <!-- 伞柄手把 -->
          <rect x="490" y="585" width="20" height="50" rx="6" fill="#0f172a" stroke="#334155" stroke-width="2"/>
          <circle cx="500" cy="625" r="4" fill="#38bdf8"/>
        </g>

        <!-- 滑块套筒 (Runner) -->
        <g id="runner-assembly" transform="translate(500, 260)">
          <rect x="-14" y="-12" width="28" height="24" rx="4" fill="#1e293b" stroke="#38bdf8" stroke-width="2"/>
          <circle cx="0" cy="0" r="3" fill="#38bdf8"/>
          <!-- 滑块侧边拉索锁固点 -->
          <circle cx="-9" cy="0" r="2" fill="#f59e0b"/>
          <circle cx="9" cy="0" r="2" fill="#f59e0b"/>
        </g>

        <!-- 左侧完整三节骨架机构组 -->
        <g id="left-arm">
          <line id="l-strut" stroke="#64748b" stroke-width="3" stroke-linecap="round"/>
          <path id="l-cable" fill="none" stroke="#38bdf8" stroke-width="2.2" filter="url(#glow-cyan)"/>
          <line id="l-bone1" stroke="url(#boneGrad)" stroke-width="4.5" stroke-linecap="round"/>
          <line id="l-bone2" stroke="url(#boneGrad)" stroke-width="3.5" stroke-linecap="round"/>
          <line id="l-bone3" stroke="url(#boneGrad)" stroke-width="2.5" stroke-linecap="round"/>

          <!-- 铰链节点 -->
          <g id="l-joint-h1">
            <circle cx="0" cy="0" r="5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="2" fill="#f59e0b" filter="url(#glow-orange)"/>
          </g>
          <g id="l-joint-h2">
            <circle cx="0" cy="0" r="4.5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="1.8" fill="#f59e0b" filter="url(#glow-orange)"/>
          </g>
          <!-- 末端软质防刮球头 -->
          <circle id="l-tip" cx="0" cy="0" r="4.5" fill="#10b981" stroke="#064e3b" stroke-width="1.2"/>
        </g>

        <!-- 右侧完整三节骨架机构组 (详细高亮对称展示) -->
        <g id="right-arm">
          <!-- 连杆支撑杆 -->
          <line id="r-strut" stroke="#64748b" stroke-width="3" stroke-linecap="round"/>
          
          <!-- 中央张紧编织拉索 -->
          <path id="r-cable" fill="none" stroke="#38bdf8" stroke-width="2.2" filter="url(#glow-cyan)"/>
          
          <!-- 主骨第1段 (180mm) -->
          <line id="r-bone1" stroke="url(#boneGrad)" stroke-width="4.5" stroke-linecap="round"/>
          <!-- 主骨第2段 (160mm) -->
          <line id="r-bone2" stroke="url(#boneGrad)" stroke-width="3.5" stroke-linecap="round"/>
          <!-- 尾骨段 -->
          <line id="r-bone3" stroke="url(#boneGrad)" stroke-width="2.5" stroke-linecap="round"/>

          <!-- 铰链1 (扭簧预紧节点 A) -->
          <g id="r-joint-h1">
            <circle cx="0" cy="0" r="5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="2" fill="#f59e0b" filter="url(#glow-orange)"/>
            <path id="r-torque-icon1" d="M -6 -6 A 8 8 0 1 1 6 6" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="2 1"/>
          </g>

          <!-- 铰链2 (扭簧预紧节点 B) -->
          <g id="r-joint-h2">
            <circle cx="0" cy="0" r="4.5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="1.8" fill="#f59e0b" filter="url(#glow-orange)"/>
            <path id="r-torque-icon2" d="M -5 -5 A 7 7 0 1 1 5 5" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="2 1"/>
          </g>

          <!-- 软质导向球头 -->
          <circle id="r-tip" cx="0" cy="0" r="4.5" fill="#10b981" stroke="#064e3b" stroke-width="1.2"/>
        </g>

        <!-- 动态应力矢量与折叠流动导向波纹 -->
        <g id="dynamics-layer" pointer-events="none"></g>
      </g>
    </svg>
  </main>

  <!-- 底部微交互控制栏 -->
  <footer class="bottom-control-bar">
    <button class="ctrl-btn" id="play-btn" title="播放 / 暂停 (空格)">
      <svg id="play-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
        <rect x="6" y="4" width="4" height="16" rx="1"/>
        <rect x="14" y="4" width="4" height="16" rx="1"/>
      </svg>
    </button>

    <div class="ctrl-slider-group">
      <div class="slider-labels">
        <span>收拢贴合 (拉索松弛·扭簧驱动)</span>
        <span id="slider-pct">开伞状态 (100%)</span>
        <span>完全撑开 (拉索锁定·刚性限位)</span>
      </div>
      <div class="slider-container">
        <input type="range" id="progress-slider" min="0" max="1" step="0.001" value="1"/>
      </div>
    </div>

    <div class="state-badge" id="mech-state-badge">刚性锁定</div>
  </footer>

  <script>
    /**
     * 连杆与扭簧拉索动力学模拟核心
     * 实现开伞刚性锁定与收伞自动顺序贴合的 IFR 机制
     */

    // 几何拓扑常量配置 (单位: SVG 坐标像素,精确对齐物理比例)
    const CFG = {
      cx: 500,           // 伞中棒 X 坐标
      topY: 120,         // 伞顶固定铰接座 Y
      handleY: 585,      // 伞柄位置
      // 骨节长度
      l1: 175,           // 主骨第1段 (180mm 标定)
      l2: 155,           // 主骨第2段 (160mm 标定)
      l3: 110,           // 尾骨段
      lStrut: 130,       // 支撑撑杆长度
      strutAttachRatio: 0.38, // 撑杆在主骨1上的铰接比例
      // 滑块行程
      runnerY_open: 245,  // 开伞时滑块位置 (高位,拉紧)
      runnerY_close: 520 // 收伞时滑块位置 (下移,卸载)
    };

    // 动画状态机
    let state = {
      progress: 1.0,     // 0 = 彻底收拢贴合, 1 = 彻底撑开锁定
      direction: -1,     // 默认加载后自动开始收伞演示
      isPlaying: true,
      speed: 0.0042,     // 平滑播放步进速度
      lastTimestamp: 0
    };

    // DOM 元素引用
    const els = {
      slider: document.getElementById('progress-slider'),
      playBtn: document.getElementById('play-btn'),
      playIcon: document.getElementById('play-icon'),
      badge: document.getElementById('mech-state-badge'),
      sliderPct: document.getElementById('slider-pct'),
      cableTensionVal: document.getElementById('cable-tension-val'),
      springTorqueVal: document.getElementById('spring-torque-val'),
      alignmentVal: document.getElementById('alignment-val'),
      // PIP 特写
      pipHingeMode: document.getElementById('hinge-pip-mode'),
      pipRotArm: document.getElementById('pip-rot-arm'),
      pipSpringCoil: document.getElementById('pip-spring-coil'),
      pipCableLine: document.getElementById('pip-cable-line'),
      // 机构图形元素
      runner: document.getElementById('runner-assembly'),
      rStrut: document.getElementById('r-strut'),
      rCable: document.getElementById('r-cable'),
      rBone1: document.getElementById('r-bone1'),
      rBone2: document.getElementById('r-bone2'),
      rBone3: document.getElementById('r-bone3'),
      rJointH1: document.getElementById('r-joint-h1'),
      rJointH2: document.getElementById('r-joint-h2'),
      rTip: document.getElementById('r-tip'),
      rTorqueIcon1: document.getElementById('r-torque-icon1'),
      rTorqueIcon2: document.getElementById('r-torque-icon2'),
      // 左侧镜像
      lStrut: document.getElementById('l-strut'),
      lCable: document.getElementById('l-cable'),
      lBone1: document.getElementById('l-bone1'),
      lBone2: document.getElementById('l-bone2'),
      lBone3: document.getElementById('l-bone3'),
      lJointH1: document.getElementById('l-joint-h1'),
      lJointH2: document.getElementById('l-joint-h2'),
      lTip: document.getElementById('l-tip'),
      // 伞面与轮廓
      canopyLeft: document.getElementById('canopy-surface-left'),
      canopyRight: document.getElementById('canopy-surface-right'),
      envL: document.getElementById('envelope-left'),
      envR: document.getElementById('envelope-right'),
    };

    // 缓动函数
    function easeInOutQuad(t) {
      return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
    }

    /**
     * 根据当前开合进度 t 计算全套骨节与拉索的空间几何姿态
     * @param {number} t [0 = 收伞, 1 = 开伞]
     * @param {number} side 1 = 右侧, -1 = 左侧
     */
    function calculateKinematics(t, side = 1) {
      // 1. 滑块位置插值
      const runnerY = CFG.runnerY_close + (CFG.runnerY_open - CFG.runnerY_close) * t;

      // 2. 主骨1仰角:收伞贴近中棒(约86度向下),开伞展开到约-18度(上扬形成伞穹)
      const bone1AngleDeg = 86 - t * 104; // 86° -> -18°
      const bone1Rad = (bone1AngleDeg * Math.PI) / 180;

      // 主骨1基点 (伞巢固定点)
      const p0 = { x: CFG.cx + side * 8, y: CFG.topY };

      // 主骨1末端 (铰链1位置)
      const p1 = {
        x: p0.x + side * CFG.l1 * Math.cos(bone1Rad),
        y: p0.y + CFG.l1 * Math.sin(bone1Rad)
      };

      // 支撑连杆铰接在主骨1上的点
      const pStrutAttach = {
        x: p0.x + (p1.x - p0.x) * CFG.strutAttachRatio,
        y: p0.y + (p1.y - p0.y) * CFG.strutAttachRatio
      };

      // 滑块支撑座铰接点
      const pRunnerJoint = {
        x: CFG.cx + side * 10,
        y: runnerY
      };

      // 3. 铰链1 (主骨2) 折角计算 - 创新核心:
      // 当 t > 0.8 时拉索完全拉直锁定 (相对夹角 0°)
      // 当 t < 0.8 时,扭簧释放,折角按时序急剧向内反折(达到 165° 限位)
      let fold1 = 0;
      if (t < 0.85) {
        const foldProgress1 = (0.85 - t) / 0.85; // 0 -> 1
        fold1 = Math.pow(foldProgress1, 1.2) * 165; // 最大内折 165 度
      }
      const bone2Rad = bone1Rad - side * (fold1 * Math.PI / 180) * 0.96;
      const p2 = {
        x: p1.x + side * CFG.l2 * Math.cos(bone2Rad),
        y: p1.y + CFG.l2 * Math.sin(bone2Rad)
      };

      // 4. 铰链2 (尾骨段) 折角计算 - 尾骨优先极速内折规整
      let fold2 = 0;
      if (t < 0.95) {
        const foldProgress2 = Math.min(1, (0.95 - t) / 0.7);
        fold2 = Math.pow(foldProgress2, 0.9) * 155; // 尾骨反折贴入
      }
      const bone3Rad = bone2Rad + side * (fold2 * Math.PI / 180) * 0.88;
      const p3 = {
        x: p2.x + side * CFG.l3 * Math.cos(bone3Rad),
        y: p2.y + CFG.l3 * Math.sin(bone3Rad)
      };

      // 5. 中央拉索穿行路径:从滑块 -> 伞顶导轮 -> 铰链1背侧 -> 铰链2背侧 -> 尾骨固定锚点
      // 在张紧状态下拉索紧贴铰链背侧槽,在卸载状态下拉索呈现自然柔性微弧
      const cableSlack = (1 - t) * 12;
      const cablePath = `M ${pRunnerJoint.x} ${pRunnerJoint.y} 
        Q ${CFG.cx + side * 16} ${(CFG.topY + runnerY) * 0.48}, ${CFG.cx + side * 14} ${CFG.topY - 4} 
        Q ${(p0.x + p1.x)*0.5 + side * cableSlack*0.5} ${(p0.y + p1.y)*0.5 - cableSlack*0.3}, ${p1.x} ${p1.y - 3} 
        Q ${(p1.x + p2.x)*0.5} ${(p1.y + p2.y)*0.5}, ${p2.x} ${p2.y - 2} 
        L ${p3.x} ${p3.y}`;

      return {
        p0, p1, p2, p3, pStrutAttach, pRunnerJoint, runnerY, fold1, fold2, cablePath
      };
    }

    /**
     * 将计算好的物理坐标渲染到 SVG 视图与 HUD
     */
    function renderFrame(t) {
      const right = calculateKinematics(t, 1);
      const left = calculateKinematics(t, -1);

      // 1. 更新滑块套筒位置
      els.runner.setAttribute('transform', `translate(${CFG.cx}, ${right.runnerY})`);

      // 2. 更新右侧骨架连杆
      els.rStrut.setAttribute('x1', right.pRunnerJoint.x);
      els.rStrut.setAttribute('y1', right.pRunnerJoint.y);
      els.rStrut.setAttribute('x2', right.pStrutAttach.x);
      els.rStrut.setAttribute('y2', right.pStrutAttach.y);

      els.rBone1.setAttribute('x1', right.p0.x);
      els.rBone1.setAttribute('y1', right.p0.y);
      els.rBone1.setAttribute('x2', right.p1.x);
      els.rBone1.setAttribute('y2', right.p1.y);

      els.rBone2.setAttribute('x1', right.p1.x);
      els.rBone2.setAttribute('y1', right.p1.y);
      els.rBone2.setAttribute('x2', right.p2.x);
      els.rBone2.setAttribute('y2', right.p2.y);

      els.rBone3.setAttribute('x1', right.p2.x);
      els.rBone3.setAttribute('y1', right.p2.y);
      els.rBone3.setAttribute('x2', right.p3.x);
      els.rBone3.setAttribute('y2', right.p3.y);

      els.rJointH1.setAttribute('transform', `translate(${right.p1.x}, ${right.p1.y})`);
      els.rJointH2.setAttribute('transform', `translate(${right.p2.x}, ${right.p2.y})`);
      els.rTip.setAttribute('cx', right.p3.x);
      els.rTip.setAttribute('cy', right.p3.y);

      els.rCable.setAttribute('d', right.cablePath);

      // 3. 更新左侧骨架连杆 (镜像)
      els.lStrut.setAttribute('x1', left.pRunnerJoint.x);
      els.lStrut.setAttribute('y1', left.pRunnerJoint.y);
      els.lStrut.setAttribute('x2', left.pStrutAttach.x);
      els.lStrut.setAttribute('y2', left.pStrutAttach.y);

      els.lBone1.setAttribute('x1', left.p0.x);
      els.lBone1.setAttribute('y1', left.p0.y);
      els.lBone1.setAttribute('x2', left.p1.x);
      els.lBone1.setAttribute('y2', left.p1.y);

      els.lBone2.setAttribute('x1', left.p1.x);
      els.lBone2.setAttribute('y1', left.p1.y);
      els.lBone2.setAttribute('x2', left.p2.x);
      els.lBone2.setAttribute('y2', left.p2.y);

      els.lBone3.setAttribute('x1', left.p2.x);
      els.lBone3.setAttribute('y1', left.p2.y);
      els.lBone3.setAttribute('x2', left.p3.x);
      els.lBone3.setAttribute('y2', left.p3.y);

      els.lJointH1.setAttribute('transform', `translate(${left.p1.x}, ${left.p1.y})`);
      els.lJointH2.setAttribute('transform', `translate(${left.p2.x}, ${left.p2.y})`);
      els.lTip.setAttribute('cx', left.p3.x);
      els.lTip.setAttribute('cy', left.p3.y);

      els.lCable.setAttribute('d', left.cablePath);

      // 4. 更新伞面布面曲线 (由点阵拟合真实织物张力形态)
      const rightCanopyD = `M ${CFG.cx} ${CFG.topY} 
        Q ${(right.p0.x + right.p1.x)*0.5} ${(right.p0.y + right.p1.y)*0.5 - 6*t}, ${right.p1.x} ${right.p1.y} 
        Q ${(right.p1.x + right.p2.x)*0.5} ${(right.p1.y + right.p2.y)*0.5 - 4*t}, ${right.p2.x} ${right.p2.y} 
        Q ${(right.p2.x + right.p3.x)*0.5} ${(right.p2.y + right.p3.y)*0.5 - 2*t}, ${right.p3.x} ${right.p3.y} 
        L ${right.p3.x - 4} ${right.p3.y + 4} 
        Z`;
      els.canopyRight.setAttribute('d', rightCanopyD);

      const leftCanopyD = `M ${CFG.cx} ${CFG.topY} 
        Q ${(left.p0.x + left.p1.x)*0.5} ${(left.p0.y + left.p1.y)*0.5 - 6*t}, ${left.p1.x} ${left.p1.y} 
        Q ${(left.p1.x + left.p2.x)*0.5} ${(left.p1.y + left.p2.y)*0.5 - 4*t}, ${left.p2.x} ${left.p2.y} 
        Q ${(left.p2.x + left.p3.x)*0.5} ${(left.p2.y + left.p3.y)*0.5 - 2*t}, ${left.p3.x} ${left.p3.y} 
        L ${left.p3.x + 4} ${left.p3.y + 4} 
        Z`;
      els.canopyLeft.setAttribute('d', leftCanopyD);

      // 5. 动态更新包络线透明度 (收伞时显现规整柱状约束)
      const envOpacity = Math.max(0, (0.3 - t) * 2.5);
      els.envL.style.opacity = envOpacity;
      els.envR.style.opacity = envOpacity;

      // 6. 更新画中画 (PIP) 铰链特写旋转角
      const pipAngle = right.fold1 * 0.9;
      els.pipRotArm.setAttribute('transform', `rotate(${-pipAngle})`);
      // 扭簧线圈形变
      const coilScale = 1 + (1 - t) * 0.25;
      els.pipSpringCoil.setAttribute('transform', `scale(${coilScale})`);
      
      // 拉索在特写中的松弛/张紧
      if (t > 0.7) {
        els.pipHingeMode.textContent = '锁定受拉 300N';
        els.pipHingeMode.style.color = '#38bdf8';
        els.pipCableLine.setAttribute('stroke', '#38bdf8');
        els.pipCableLine.setAttribute('stroke-width', '2.5');
      } else {
        els.pipHingeMode.textContent = '扭簧释能内折';
        els.pipHingeMode.style.color = '#f59e0b';
        els.pipCableLine.setAttribute('stroke', 'rgba(56,189,248,0.3)');
        els.pipCableLine.setAttribute('stroke-width', '1.2');
      }

      // 7. 更新 HUD 遥测参数读数与状态徽标
      const cableTension = (Math.max(0, (t - 0.2) / 0.8) * 300).toFixed(1);
      const springTorque = ((1 - t) * 0.02).toFixed(3);
      const alignQuality = (100 - Math.abs(t - 0.5) * 12).toFixed(0);

      els.cableTensionVal.textContent = `${cableTension} N`;
      els.springTorqueVal.textContent = `${(0.02 * (1 - t * 0.5)).toFixed(3)} N·m`;
      
      if (t < 0.15) {
        els.badge.textContent = '自动贴杆聚拢';
        els.badge.style.color = '#10b981';
        els.alignmentVal.textContent = '100 % (极佳)';
        els.rCable.setAttribute('stroke-opacity', '0.25');
        els.lCable.setAttribute('stroke-opacity', '0.25');
      } else if (t < 0.8) {
        els.badge.textContent = '扭簧顺序内折中';
        els.badge.style.color = '#f59e0b';
        els.alignmentVal.textContent = '自寻优对齐...';
        els.rCable.setAttribute('stroke-opacity', '0.6');
        els.lCable.setAttribute('stroke-opacity', '0.6');
      } else {
        els.badge.textContent = '刚性限位锁定';
        els.badge.style.color = '#38bdf8';
        els.alignmentVal.textContent = '刚性支撑';
        els.rCable.setAttribute('stroke-opacity', '1');
        els.lCable.setAttribute('stroke-opacity', '1');
      }

      // 更新滑块状态
      els.slider.value = t;
      els.sliderPct.textContent = `状态 (${Math.round(t * 100)}%)`;
    }

    /**
     * 主动画循环 - 自动往复展示
     */
    function tick(timestamp) {
      if (!state.lastTimestamp) state.lastTimestamp = timestamp;
      const dt = timestamp - state.lastTimestamp;
      state.lastTimestamp = timestamp;

      if (state.isPlaying) {
        state.progress += state.direction * state.speed;

        // 边界平滑停留换向
        if (state.progress <= 0) {
          state.progress = 0;
          state.direction = 0; // 停顿片刻
          setTimeout(() => {
            if (state.isPlaying && state.progress <= 0.05) state.direction = 1;
          }, 1100);
        } else if (state.progress >= 1) {
          state.progress = 1;
          state.direction = 0;
          setTimeout(() => {
            if (state.isPlaying && state.progress >= 0.95) state.direction = -1;
          }, 1400);
        }

        renderFrame(state.progress);
      }

      requestAnimationFrame(tick);
    }

    // 交互事件绑定
    function initControls() {
      // 拖动条实时手动探究
      els.slider.addEventListener('input', (e) => {
        state.isPlaying = false;
        updatePlayBtnIcon();
        state.progress = parseFloat(e.target.value);
        renderFrame(state.progress);
      });

      // 播放/暂停控制
      els.playBtn.addEventListener('click', () => {
        state.isPlaying = !state.isPlaying;
        if (state.isPlaying && state.direction === 0) {
          state.direction = state.progress > 0.5 ? -1 : 1;
        }
        updatePlayBtnIcon();
      });

      // 键盘快捷键 (空格播放/暂停)
      window.addEventListener('keydown', (e) => {
        if (e.code === 'Space') {
          e.preventDefault();
          els.playBtn.click();
        }
      });
    }

    function updatePlayBtnIcon() {
      if (state.isPlaying) {
        els.playIcon.innerHTML = `
          <rect x="6" y="4" width="4" height="16" rx="1"/>
          <rect x="14" y="4" width="4" height="16" rx="1"/>
        `;
      } else {
        els.playIcon.innerHTML = `
          <polygon points="6,4 20,12 6,20"/>
        `;
      }
    }

    // 页面加载完成后自启动运行
    window.addEventListener('DOMContentLoaded', () => {
      initControls();
      renderFrame(1.0);
      // 触发自动开机动画
      requestAnimationFrame(tick);
    });
  </script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>预紧扭簧铰链与拉索自规整折伞机构 - 原理可视化</title>
  <style>
    :root {
      --bg-dark: #090d16;
      --panel-bg: rgba(15, 23, 42, 0.75);
      --panel-border: rgba(56, 189, 248, 0.2);
      --cyan-accent: #38bdf8;
      --orange-accent: #f59e0b;
      --green-accent: #10b981;
      --text-main: #f1f5f9;
      --text-muted: #94a3b8;
      --font-mono: "SF Mono", "Consolas", "Roboto Mono", monospace;
      --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
    }

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

    body {
      background-color: var(--bg-dark);
      color: var(--text-main);
      font-family: var(--font-sans);
      width: 100vw;
      height: 100vh;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      position: relative;
    }

    /* 顶部微型信息条 - 紧凑精炼,不遮挡主体 */
    .top-bar {
      position: absolute;
      top: 12px;
      left: 16px;
      right: 16px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      z-index: 20;
      pointer-events: none;
    }

    .brand-tag {
      display: flex;
      align-items: center;
      gap: 8px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      padding: 5px 12px;
      border-radius: 20px;
      backdrop-filter: blur(8px);
      box-shadow: 0 4px 20px rgba(0,0,0,0.4);
    }

    .status-dot {
      width: 7px;
      height: 7px;
      border-radius: 50%;
      background-color: var(--cyan-accent);
      box-shadow: 0 0 8px var(--cyan-accent);
      animation: pulse-dot 2s infinite ease-in-out;
    }

    @keyframes pulse-dot {
      0%, 100% { opacity: 0.5; transform: scale(0.9); }
      50% { opacity: 1; transform: scale(1.2); }
    }

    .brand-title {
      font-size: 13px;
      font-weight: 600;
      letter-spacing: 0.5px;
      color: #e2e8f0;
    }

    .brand-sub {
      font-size: 11px;
      color: var(--text-muted);
      font-family: var(--font-mono);
      margin-left: 4px;
    }

    /* 状态遥测浮动条 - 极小字号放置在右上角 */
    .hud-metrics {
      display: flex;
      gap: 10px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      padding: 5px 12px;
      border-radius: 8px;
      backdrop-filter: blur(8px);
    }

    .metric-item {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
    }

    .metric-label {
      font-size: 9px;
      color: var(--text-muted);
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .metric-value {
      font-size: 12px;
      font-family: var(--font-mono);
      font-weight: 600;
      color: var(--cyan-accent);
    }

    /* 中央全屏核心画布 */
    .stage-container {
      flex: 1;
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      position: relative;
      background: radial-gradient(circle at 50% 45%, #131d31 0%, #090d16 80%);
    }

    /* 背景工程栅格 */
    .grid-bg {
      position: absolute;
      inset: 0;
      background-image: 
        linear-gradient(rgba(56, 189, 248, 0.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(56, 189, 248, 0.04) 1px, transparent 1px);
      background-size: 32px 32px;
      pointer-events: none;
    }

    /* 主 SVG 动画视图 */
    svg#mechanism-stage {
      width: 100%;
      height: 100%;
      max-width: 1200px;
      max-height: 820px;
      z-index: 10;
    }

    /* 铰链微观特写画中画 (PIP) - 右下角紧凑浮窗 */
    .detail-pip {
      position: absolute;
      right: 18px;
      bottom: 84px;
      width: 200px;
      height: 160px;
      background: rgba(13, 20, 36, 0.85);
      border: 1px solid rgba(245, 158, 11, 0.35);
      border-radius: 10px;
      padding: 8px;
      backdrop-filter: blur(10px);
      z-index: 20;
      box-shadow: 0 8px 30px rgba(0,0,0,0.6);
      display: flex;
      flex-direction: column;
    }

    .pip-header {
      display: space-between;
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 4px;
      padding-bottom: 4px;
      border-bottom: 1px solid rgba(255,255,255,0.08);
    }

    .pip-title {
      font-size: 10px;
      font-weight: 600;
      color: var(--orange-accent);
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .pip-tag {
      font-size: 9px;
      font-family: var(--font-mono);
      background: rgba(245, 158, 11, 0.15);
      color: #fbbf24;
      padding: 1px 4px;
      border-radius: 3px;
    }

    .pip-svg {
      flex: 1;
      width: 100%;
      height: 100%;
    }

    /* 底部核心精简控制栏 */
    .bottom-control-bar {
      position: absolute;
      bottom: 14px;
      left: 50%;
      transform: translateX(-50%);
      width: min(680px, 92%);
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      border-radius: 28px;
      padding: 8px 18px;
      display: flex;
      align-items: center;
      gap: 14px;
      backdrop-filter: blur(12px);
      z-index: 30;
      box-shadow: 0 10px 30px rgba(0,0,0,0.5);
    }

    .ctrl-btn {
      background: rgba(56, 189, 248, 0.12);
      border: 1px solid rgba(56, 189, 248, 0.3);
      color: var(--cyan-accent);
      border-radius: 50%;
      width: 32px;
      height: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: all 0.2s;
      flex-shrink: 0;
    }

    .ctrl-btn:hover {
      background: var(--cyan-accent);
      color: #04101e;
      box-shadow: 0 0 12px rgba(56, 189, 248, 0.6);
    }

    .ctrl-slider-group {
      flex: 1;
      display: flex;
      flex-direction: column;
      gap: 3px;
    }

    .slider-labels {
      display: flex;
      justify-content: space-between;
      font-size: 10px;
      color: var(--text-muted);
      font-family: var(--font-mono);
    }

    .slider-container {
      position: relative;
      width: 100%;
      display: flex;
      align-items: center;
    }

    input[type="range"] {
      -webkit-appearance: none;
      width: 100%;
      height: 4px;
      border-radius: 2px;
      background: rgba(255,255,255,0.15);
      outline: none;
      cursor: pointer;
    }

    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: 14px;
      height: 14px;
      border-radius: 50%;
      background: var(--cyan-accent);
      box-shadow: 0 0 8px var(--cyan-accent);
      cursor: pointer;
      transition: transform 0.1s;
    }

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

    .state-badge {
      font-size: 11px;
      font-weight: 600;
      color: #38bdf8;
      min-width: 80px;
      text-align: right;
      font-family: var(--font-mono);
    }

    /* 图例指示器 - 左下角精炼放置 */
    .legend-panel {
      position: absolute;
      left: 18px;
      bottom: 84px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      border-radius: 8px;
      padding: 8px 12px;
      backdrop-filter: blur(8px);
      z-index: 20;
      display: flex;
      flex-direction: column;
      gap: 6px;
      pointer-events: none;
    }

    .legend-item {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 10px;
      color: #cbd5e1;
    }

    .legend-color {
      width: 12px;
      height: 3px;
      border-radius: 2px;
    }

    .legend-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
    }
  </style>
</head>
<body>

  <!-- 顶部轻量 HUD -->
  <header class="top-bar">
    <div class="brand-tag">
      <div class="status-dot"></div>
      <div class="brand-title">自规整折伞机构 <span class="brand-sub">IFR 理想解动态原理</span></div>
    </div>

    <div class="hud-metrics">
      <div class="metric-item">
        <span class="metric-label">拉索工作张力</span>
        <span class="metric-value" id="cable-tension-val">300.0 N</span>
      </div>
      <div class="metric-item">
        <span class="metric-label">扭簧预紧储能</span>
        <span class="metric-value" id="spring-torque-val">0.02 N·m</span>
      </div>
      <div class="metric-item">
        <span class="metric-label">骨段贴合度</span>
        <span class="metric-value" id="alignment-val">100 %</span>
      </div>
    </div>
  </header>

  <!-- 左下角图例说明 -->
  <div class="legend-panel">
    <div class="legend-item">
      <div class="legend-color" style="background: #38bdf8; box-shadow: 0 0 6px #38bdf8;"></div>
      <span>中央张紧拉索 (1.2mm 编织缆)</span>
    </div>
    <div class="legend-item">
      <div class="legend-dot" style="background: #f59e0b; box-shadow: 0 0 6px #f59e0b;"></div>
      <span>内折预紧扭簧铰链 (限位165°)</span>
    </div>
    <div class="legend-item">
      <div class="legend-color" style="background: #94a3b8;"></div>
      <span>玻纤扁骨 (180mm + 160mm)</span>
    </div>
    <div class="legend-item">
      <div class="legend-dot" style="background: #10b981;"></div>
      <span>尾段防划软质导向球头</span>
    </div>
  </div>

  <!-- 右下角局部放大铰链特写 (画中画) -->
  <div class="detail-pip">
    <div class="pip-header">
      <span class="pip-title">铰链内部扭簧剖面</span>
      <span class="pip-tag" id="hinge-pip-mode">锁定受拉</span>
    </div>
    <svg class="pip-svg" viewBox="0 0 160 120" id="pip-hinge-svg">
      <!-- 铰链放大图动态元素 -->
      <g id="pip-dynamic-hinge" transform="translate(80, 60)">
        <!-- 铰链座 -->
        <circle cx="0" cy="0" r="28" fill="#1e293b" stroke="#475569" stroke-width="1.5"/>
        <!-- 限位咬合台阶 -->
        <path d="M -20 -10 L 0 -10 L 15 15" stroke="rgba(245,158,11,0.5)" stroke-dasharray="2 2" fill="none"/>
        <!-- 骨1插芯 -->
        <rect x="-42" y="-5" width="42" height="10" rx="3" fill="#334155" stroke="#64748b"/>
        <!-- 骨2旋转臂 -->
        <g id="pip-rot-arm">
          <rect x="0" y="-5" width="42" height="10" rx="3" fill="#475569" stroke="#94a3b8"/>
          <!-- 旋转轴心 -->
          <circle cx="0" cy="0" r="4" fill="#f59e0b"/>
          <!-- 扭簧螺旋线条 -->
          <path id="pip-spring-coil" d="M -7 -7 A 10 10 0 1 1 7 7" fill="none" stroke="#f59e0b" stroke-width="2.5" stroke-linecap="round"/>
        </g>
        <!-- 拉索穿行轨迹 -->
        <path id="pip-cable-line" d="M -45 -12 L 0 -12 L 45 -12" fill="none" stroke="#38bdf8" stroke-width="2" stroke-dasharray="4 2"/>
        <circle cx="0" cy="-12" r="3" fill="#38bdf8"/>
        <!-- 扭矩力矢量箭头 -->
        <path id="pip-torque-arrow" d="M -16 16 A 22 22 0 0 0 16 16" fill="none" stroke="#f59e0b" stroke-width="1.5" marker-end="url(#arrow)"/>
      </g>
      <defs>
        <marker id="arrow" viewBox="0 0 6 6" refX="4" refY="3" markerWidth="4" markerHeight="4" orient="auto-start-reverse">
          <polygon points="0 0, 6 3, 0 6" fill="#f59e0b"/>
        </marker>
      </defs>
    </svg>
  </div>

  <!-- 中央主舞台 -->
  <main class="stage-container">
    <div class="grid-bg"></div>

    <svg id="mechanism-stage" viewBox="0 0 1000 680" preserveAspectRatio="xMidYMid meet">
      <defs>
        <!-- 渐变定义 -->
        <linearGradient id="canopyGrad" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stop-color="#0284c7" stop-opacity="0.35"/>
          <stop offset="100%" stop-color="#0369a1" stop-opacity="0.1"/>
        </linearGradient>

        <linearGradient id="boneGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stop-color="#cbd5e1"/>
          <stop offset="100%" stop-color="#64748b"/>
        </linearGradient>

        <linearGradient id="shaftGrad" x1="0%" y1="0%" x2="100%" y2="0%">
          <stop offset="0%" stop-color="#334155"/>
          <stop offset="50%" stop-color="#94a3b8"/>
          <stop offset="100%" stop-color="#1e293b"/>
        </linearGradient>

        <!-- 发光滤镜 -->
        <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="3" result="blur"/>
          <feMerge>
            <feMergeNode in="blur"/>
            <feMergeNode in="SourceGraphic"/>
          </feMerge>
        </filter>

        <filter id="glow-orange" x="-30%" y="-30%" width="160%" height="160%">
          <feGaussianBlur stdDeviation="4" result="blur"/>
          <feMerge>
            <feMergeNode in="blur"/>
            <feMergeNode in="SourceGraphic"/>
          </feMerge>
        </filter>
      </defs>

      <!-- 机构几何层(由 JS 动态生成与驱动) -->
      <g id="scene-root">
        <!-- 伞面布面投影 -->
        <path id="canopy-surface-left" fill="url(#canopyGrad)" stroke="rgba(56, 189, 248, 0.4)" stroke-width="1.2"/>
        <path id="canopy-surface-right" fill="url(#canopyGrad)" stroke="rgba(56, 189, 248, 0.4)" stroke-width="1.2"/>

        <!-- 收伞聚拢导向约束外轮廓(淡线提示规整圆柱包络) -->
        <line id="envelope-left" x1="440" y1="120" x2="440" y2="580" stroke="rgba(56,189,248,0.15)" stroke-dasharray="3 3" opacity="0"/>
        <line id="envelope-right" x1="560" y1="120" x2="560" y2="580" stroke="rgba(56,189,248,0.15)" stroke-dasharray="3 3" opacity="0"/>

        <!-- 中棒主轴与伞巢 -->
        <g id="shaft-assembly">
          <!-- 伞顶导向轮与顶巢 -->
          <circle cx="500" cy="115" r="14" fill="#1e293b" stroke="#38bdf8" stroke-width="2"/>
          <circle cx="500" cy="115" r="5" fill="#38bdf8" filter="url(#glow-cyan)"/>
          
          <!-- 中棒主体钢管 -->
          <rect x="494" y="115" width="12" height="470" rx="3" fill="url(#shaftGrad)"/>
          <!-- 刻度线 -->
          <line x1="497" y1="180" x2="497" y2="480" stroke="rgba(255,255,255,0.15)" stroke-dasharray="2 6"/>
          
          <!-- 伞柄手把 -->
          <rect x="490" y="585" width="20" height="50" rx="6" fill="#0f172a" stroke="#334155" stroke-width="2"/>
          <circle cx="500" cy="625" r="4" fill="#38bdf8"/>
        </g>

        <!-- 滑块套筒 (Runner) -->
        <g id="runner-assembly" transform="translate(500, 260)">
          <rect x="-14" y="-12" width="28" height="24" rx="4" fill="#1e293b" stroke="#38bdf8" stroke-width="2"/>
          <circle cx="0" cy="0" r="3" fill="#38bdf8"/>
          <!-- 滑块侧边拉索锁固点 -->
          <circle cx="-9" cy="0" r="2" fill="#f59e0b"/>
          <circle cx="9" cy="0" r="2" fill="#f59e0b"/>
        </g>

        <!-- 左侧完整三节骨架机构组 -->
        <g id="left-arm">
          <line id="l-strut" stroke="#64748b" stroke-width="3" stroke-linecap="round"/>
          <path id="l-cable" fill="none" stroke="#38bdf8" stroke-width="2.2" filter="url(#glow-cyan)"/>
          <line id="l-bone1" stroke="url(#boneGrad)" stroke-width="4.5" stroke-linecap="round"/>
          <line id="l-bone2" stroke="url(#boneGrad)" stroke-width="3.5" stroke-linecap="round"/>
          <line id="l-bone3" stroke="url(#boneGrad)" stroke-width="2.5" stroke-linecap="round"/>

          <!-- 铰链节点 -->
          <g id="l-joint-h1">
            <circle cx="0" cy="0" r="5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="2" fill="#f59e0b" filter="url(#glow-orange)"/>
          </g>
          <g id="l-joint-h2">
            <circle cx="0" cy="0" r="4.5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="1.8" fill="#f59e0b" filter="url(#glow-orange)"/>
          </g>
          <!-- 末端软质防刮球头 -->
          <circle id="l-tip" cx="0" cy="0" r="4.5" fill="#10b981" stroke="#064e3b" stroke-width="1.2"/>
        </g>

        <!-- 右侧完整三节骨架机构组 (详细高亮对称展示) -->
        <g id="right-arm">
          <!-- 连杆支撑杆 -->
          <line id="r-strut" stroke="#64748b" stroke-width="3" stroke-linecap="round"/>
          
          <!-- 中央张紧编织拉索 -->
          <path id="r-cable" fill="none" stroke="#38bdf8" stroke-width="2.2" filter="url(#glow-cyan)"/>
          
          <!-- 主骨第1段 (180mm) -->
          <line id="r-bone1" stroke="url(#boneGrad)" stroke-width="4.5" stroke-linecap="round"/>
          <!-- 主骨第2段 (160mm) -->
          <line id="r-bone2" stroke="url(#boneGrad)" stroke-width="3.5" stroke-linecap="round"/>
          <!-- 尾骨段 -->
          <line id="r-bone3" stroke="url(#boneGrad)" stroke-width="2.5" stroke-linecap="round"/>

          <!-- 铰链1 (扭簧预紧节点 A) -->
          <g id="r-joint-h1">
            <circle cx="0" cy="0" r="5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="2" fill="#f59e0b" filter="url(#glow-orange)"/>
            <path id="r-torque-icon1" d="M -6 -6 A 8 8 0 1 1 6 6" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="2 1"/>
          </g>

          <!-- 铰链2 (扭簧预紧节点 B) -->
          <g id="r-joint-h2">
            <circle cx="0" cy="0" r="4.5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="1.8" fill="#f59e0b" filter="url(#glow-orange)"/>
            <path id="r-torque-icon2" d="M -5 -5 A 7 7 0 1 1 5 5" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="2 1"/>
          </g>

          <!-- 软质导向球头 -->
          <circle id="r-tip" cx="0" cy="0" r="4.5" fill="#10b981" stroke="#064e3b" stroke-width="1.2"/>
        </g>

        <!-- 动态应力矢量与折叠流动导向波纹 -->
        <g id="dynamics-layer" pointer-events="none"></g>
      </g>
    </svg>
  </main>

  <!-- 底部微交互控制栏 -->
  <footer class="bottom-control-bar">
    <button class="ctrl-btn" id="play-btn" title="播放 / 暂停 (空格)">
      <svg id="play-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
        <rect x="6" y="4" width="4" height="16" rx="1"/>
        <rect x="14" y="4" width="4" height="16" rx="1"/>
      </svg>
    </button>

    <div class="ctrl-slider-group">
      <div class="slider-labels">
        <span>收拢贴合 (拉索松弛·扭簧驱动)</span>
        <span id="slider-pct">开伞状态 (100%)</span>
        <span>完全撑开 (拉索锁定·刚性限位)</span>
      </div>
      <div class="slider-container">
        <input type="range" id="progress-slider" min="0" max="1" step="0.001" value="1"/>
      </div>
    </div>

    <div class="state-badge" id="mech-state-badge">刚性锁定</div>
  </footer>

  <script>
    /**
     * 连杆与扭簧拉索动力学模拟核心
     * 实现开伞刚性锁定与收伞自动顺序贴合的 IFR 机制
     */

    // 几何拓扑常量配置 (单位: SVG 坐标像素,精确对齐物理比例)
    const CFG = {
      cx: 500,           // 伞中棒 X 坐标
      topY: 120,         // 伞顶固定铰接座 Y
      handleY: 585,      // 伞柄位置
      // 骨节长度
      l1: 175,           // 主骨第1段 (180mm 标定)
      l2: 155,           // 主骨第2段 (160mm 标定)
      l3: 110,           // 尾骨段
      lStrut: 130,       // 支撑撑杆长度
      strutAttachRatio: 0.38, // 撑杆在主骨1上的铰接比例
      // 滑块行程
      runnerY_open: 245,  // 开伞时滑块位置 (高位,拉紧)
      runnerY_close: 520 // 收伞时滑块位置 (下移,卸载)
    };

    // 动画状态机
    let state = {
      progress: 1.0,     // 0 = 彻底收拢贴合, 1 = 彻底撑开锁定
      direction: -1,     // 默认加载后自动开始收伞演示
      isPlaying: true,
      speed: 0.0042,     // 平滑播放步进速度
      lastTimestamp: 0
    };

    // DOM 元素引用
    const els = {
      slider: document.getElementById('progress-slider'),
      playBtn: document.getElementById('play-btn'),
      playIcon: document.getElementById('play-icon'),
      badge: document.getElementById('mech-state-badge'),
      sliderPct: document.getElementById('slider-pct'),
      cableTensionVal: document.getElementById('cable-tension-val'),
      springTorqueVal: document.getElementById('spring-torque-val'),
      alignmentVal: document.getElementById('alignment-val'),
      // PIP 特写
      pipHingeMode: document.getElementById('hinge-pip-mode'),
      pipRotArm: document.getElementById('pip-rot-arm'),
      pipSpringCoil: document.getElementById('pip-spring-coil'),
      pipCableLine: document.getElementById('pip-cable-line'),
      // 机构图形元素
      runner: document.getElementById('runner-assembly'),
      rStrut: document.getElementById('r-strut'),
      rCable: document.getElementById('r-cable'),
      rBone1: document.getElementById('r-bone1'),
      rBone2: document.getElementById('r-bone2'),
      rBone3: document.getElementById('r-bone3'),
      rJointH1: document.getElementById('r-joint-h1'),
      rJointH2: document.getElementById('r-joint-h2'),
      rTip: document.getElementById('r-tip'),
      rTorqueIcon1: document.getElementById('r-torque-icon1'),
      rTorqueIcon2: document.getElementById('r-torque-icon2'),
      // 左侧镜像
      lStrut: document.getElementById('l-strut'),
      lCable: document.getElementById('l-cable'),
      lBone1: document.getElementById('l-bone1'),
      lBone2: document.getElementById('l-bone2'),
      lBone3: document.getElementById('l-bone3'),
      lJointH1: document.getElementById('l-joint-h1'),
      lJointH2: document.getElementById('l-joint-h2'),
      lTip: document.getElementById('l-tip'),
      // 伞面与轮廓
      canopyLeft: document.getElementById('canopy-surface-left'),
      canopyRight: document.getElementById('canopy-surface-right'),
      envL: document.getElementById('envelope-left'),
      envR: document.getElementById('envelope-right'),
    };

    /**
     * 根据当前开合进度 t 计算全套骨节与拉索的空间几何姿态
     * @param {number} t [0 = 收伞, 1 = 开伞]
     * @param {number} side 1 = 右侧, -1 = 左侧
     */
    function calculateKinematics(t, side = 1) {
      // 1. 滑块位置插值
      const runnerY = CFG.runnerY_close + (CFG.runnerY_open - CFG.runnerY_close) * t;

      // 2. 主骨1仰角:收伞贴近中棒(约86度向下),开伞展开到约-18度(上扬形成伞穹)
      const bone1AngleDeg = 86 - t * 104; // 86° -> -18°
      const bone1Rad = (bone1AngleDeg * Math.PI) / 180;

      // 主骨1基点 (伞巢固定点)
      const p0 = { x: CFG.cx + side * 8, y: CFG.topY };

      // 主骨1末端 (铰链1位置)
      const p1 = {
        x: p0.x + side * CFG.l1 * Math.cos(bone1Rad),
        y: p0.y + CFG.l1 * Math.sin(bone1Rad)
      };

      // 支撑连杆铰接在主骨1上的点
      const pStrutAttach = {
        x: p0.x + (p1.x - p0.x) * CFG.strutAttachRatio,
        y: p0.y + (p1.y - p0.y) * CFG.strutAttachRatio
      };

      // 滑块支撑座铰接点
      const pRunnerJoint = {
        x: CFG.cx + side * 10,
        y: runnerY
      };

      // 3. 铰链1 (主骨2) 折角计算:
      // 当 t < 0.85 时,扭簧释放,骨段内折
      let fold1 = 0;
      if (t < 0.85) {
        const foldProgress1 = (0.85 - t) / 0.85; // 0 -> 1
        fold1 = Math.pow(foldProgress1, 1.2) * 165; // 最大内折 165 度
      }
      // 几何角始终保持内折
      const bone2Rad = bone1Rad - (fold1 * Math.PI / 180) * 0.96;
      const p2 = {
        x: p1.x + side * CFG.l2 * Math.cos(bone2Rad),
        y: p1.y + CFG.l2 * Math.sin(bone2Rad)
      };

      // 4. 铰链2 (尾骨段) 折角计算:尾骨内折贴入
      let fold2 = 0;
      if (t < 0.95) {
        const foldProgress2 = Math.min(1, (0.95 - t) / 0.7);
        fold2 = Math.pow(foldProgress2, 0.9) * 155; // 尾骨反折贴入
      }
      const bone3Rad = bone2Rad + (fold2 * Math.PI / 180) * 0.88;
      const p3 = {
        x: p2.x + side * CFG.l3 * Math.cos(bone3Rad),
        y: p2.y + CFG.l3 * Math.sin(bone3Rad)
      };

      // 5. 中央拉索穿行路径:从滑块 -> 伞顶导轮 -> 铰链1背侧 -> 铰链2背侧 -> 尾骨固定锚点
      const cableSlack = (1 - t) * 12;
      const cablePath = `M ${pRunnerJoint.x} ${pRunnerJoint.y} 
        Q ${CFG.cx + side * 16} ${(CFG.topY + runnerY) * 0.48}, ${CFG.cx + side * 14} ${CFG.topY - 4} 
        Q ${(p0.x + p1.x)*0.5 + side * cableSlack*0.5} ${(p0.y + p1.y)*0.5 - cableSlack*0.3}, ${p1.x} ${p1.y - 3} 
        Q ${(p1.x + p2.x)*0.5} ${(p1.y + p2.y)*0.5}, ${p2.x} ${p2.y - 2} 
        L ${p3.x} ${p3.y}`;

      return {
        p0, p1, p2, p3, pStrutAttach, pRunnerJoint, runnerY, fold1, fold2, cablePath
      };
    }

    /**
     * 将计算好的物理坐标渲染到 SVG 视图与 HUD
     */
    function renderFrame(t) {
      const right = calculateKinematics(t, 1);
      const left = calculateKinematics(t, -1);

      // 1. 更新滑块套筒位置
      els.runner.setAttribute('transform', `translate(${CFG.cx}, ${right.runnerY})`);

      // 2. 更新右侧骨架连杆
      els.rStrut.setAttribute('x1', right.pRunnerJoint.x);
      els.rStrut.setAttribute('y1', right.pRunnerJoint.y);
      els.rStrut.setAttribute('x2', right.pStrutAttach.x);
      els.rStrut.setAttribute('y2', right.pStrutAttach.y);

      els.rBone1.setAttribute('x1', right.p0.x);
      els.rBone1.setAttribute('y1', right.p0.y);
      els.rBone1.setAttribute('x2', right.p1.x);
      els.rBone1.setAttribute('y2', right.p1.y);

      els.rBone2.setAttribute('x1', right.p1.x);
      els.rBone2.setAttribute('y1', right.p1.y);
      els.rBone2.setAttribute('x2', right.p2.x);
      els.rBone2.setAttribute('y2', right.p2.y);

      els.rBone3.setAttribute('x1', right.p2.x);
      els.rBone3.setAttribute('y1', right.p2.y);
      els.rBone3.setAttribute('x2', right.p3.x);
      els.rBone3.setAttribute('y2', right.p3.y);

      els.rJointH1.setAttribute('transform', `translate(${right.p1.x}, ${right.p1.y})`);
      els.rJointH2.setAttribute('transform', `translate(${right.p2.x}, ${right.p2.y})`);
      els.rTip.setAttribute('cx', right.p3.x);
      els.rTip.setAttribute('cy', right.p3.y);

      els.rCable.setAttribute('d', right.cablePath);

      // 3. 更新左侧骨架连杆 (与右侧精确镜像对称)
      els.lStrut.setAttribute('x1', left.pRunnerJoint.x);
      els.lStrut.setAttribute('y1', left.pRunnerJoint.y);
      els.lStrut.setAttribute('x2', left.pStrutAttach.x);
      els.lStrut.setAttribute('y2', left.pStrutAttach.y);

      els.lBone1.setAttribute('x1', left.p0.x);
      els.lBone1.setAttribute('y1', left.p0.y);
      els.lBone1.setAttribute('x2', left.p1.x);
      els.lBone1.setAttribute('y2', left.p1.y);

      els.lBone2.setAttribute('x1', left.p1.x);
      els.lBone2.setAttribute('y1', left.p1.y);
      els.lBone2.setAttribute('x2', left.p2.x);
      els.lBone2.setAttribute('y2', left.p2.y);

      els.lBone3.setAttribute('x1', left.p2.x);
      els.lBone3.setAttribute('y1', left.p2.y);
      els.lBone3.setAttribute('x2', left.p3.x);
      els.lBone3.setAttribute('y2', left.p3.y);

      els.lJointH1.setAttribute('transform', `translate(${left.p1.x}, ${left.p1.y})`);
      els.lJointH2.setAttribute('transform', `translate(${left.p2.x}, ${left.p2.y})`);
      els.lTip.setAttribute('cx', left.p3.x);
      els.lTip.setAttribute('cy', left.p3.y);

      els.lCable.setAttribute('d', left.cablePath);

      // 4. 更新伞面布面曲线
      const rightCanopyD = `M ${CFG.cx} ${CFG.topY} 
        Q ${(right.p0.x + right.p1.x)*0.5} ${(right.p0.y + right.p1.y)*0.5 - 6*t}, ${right.p1.x} ${right.p1.y} 
        Q ${(right.p1.x + right.p2.x)*0.5} ${(right.p1.y + right.p2.y)*0.5 - 4*t}, ${right.p2.x} ${right.p2.y} 
        Q ${(right.p2.x + right.p3.x)*0.5} ${(right.p2.y + right.p3.y)*0.5 - 2*t}, ${right.p3.x} ${right.p3.y} 
        L ${right.p3.x - 4} ${right.p3.y + 4} 
        Z`;
      els.canopyRight.setAttribute('d', rightCanopyD);

      const leftCanopyD = `M ${CFG.cx} ${CFG.topY} 
        Q ${(left.p0.x + left.p1.x)*0.5} ${(left.p0.y + left.p1.y)*0.5 - 6*t}, ${left.p1.x} ${left.p1.y} 
        Q ${(left.p1.x + left.p2.x)*0.5} ${(left.p1.y + left.p2.y)*0.5 - 4*t}, ${left.p2.x} ${left.p2.y} 
        Q ${(left.p2.x + left.p3.x)*0.5} ${(left.p2.y + left.p3.y)*0.5 - 2*t}, ${left.p3.x} ${left.p3.y} 
        L ${left.p3.x + 4} ${left.p3.y + 4} 
        Z`;
      els.canopyLeft.setAttribute('d', leftCanopyD);

      // 5. 动态更新包络线透明度 (收伞时显现规整柱状约束)
      const envOpacity = Math.max(0, (0.3 - t) * 2.5);
      els.envL.style.opacity = envOpacity;
      els.envR.style.opacity = envOpacity;

      // 6. 更新画中画 (PIP) 铰链特写旋转角
      const pipAngle = right.fold1 * 0.9;
      els.pipRotArm.setAttribute('transform', `rotate(${-pipAngle})`);
      // 扭簧线圈形变
      const coilScale = 1 + (1 - t) * 0.25;
      els.pipSpringCoil.setAttribute('transform', `scale(${coilScale})`);
      
      // 拉索在特写中的松弛/张紧
      if (t > 0.7) {
        els.pipHingeMode.textContent = '锁定受拉 300N';
        els.pipHingeMode.style.color = '#38bdf8';
        els.pipCableLine.setAttribute('stroke', '#38bdf8');
        els.pipCableLine.setAttribute('stroke-width', '2.5');
      } else {
        els.pipHingeMode.textContent = '扭簧释能内折';
        els.pipHingeMode.style.color = '#f59e0b';
        els.pipCableLine.setAttribute('stroke', 'rgba(56,189,248,0.3)');
        els.pipCableLine.setAttribute('stroke-width', '1.2');
      }

      // 7. 更新 HUD 遥测参数读数与状态徽标
      const cableTension = (Math.max(0, (t - 0.2) / 0.8) * 300).toFixed(1);
      const springTorque = ((1 - t) * 0.02).toFixed(3);
      const alignQuality = (100 - Math.abs(t - 0.5) * 12).toFixed(0);

      els.cableTensionVal.textContent = `${cableTension} N`;
      els.springTorqueVal.textContent = `${(0.02 * (1 - t * 0.5)).toFixed(3)} N·m`;
      
      if (t < 0.15) {
        els.badge.textContent = '自动贴杆聚拢';
        els.badge.style.color = '#10b981';
        els.alignmentVal.textContent = '100 % (极佳)';
        els.rCable.setAttribute('stroke-opacity', '0.25');
        els.lCable.setAttribute('stroke-opacity', '0.25');
      } else if (t < 0.8) {
        els.badge.textContent = '扭簧顺序内折中';
        els.badge.style.color = '#f59e0b';
        els.alignmentVal.textContent = '自寻优对齐...';
        els.rCable.setAttribute('stroke-opacity', '0.6');
        els.lCable.setAttribute('stroke-opacity', '0.6');
      } else {
        els.badge.textContent = '刚性限位锁定';
        els.badge.style.color = '#38bdf8';
        els.alignmentVal.textContent = '刚性支撑';
        els.rCable.setAttribute('stroke-opacity', '1');
        els.lCable.setAttribute('stroke-opacity', '1');
      }

      // 更新滑块状态
      els.slider.value = t;
      els.sliderPct.textContent = `状态 (${Math.round(t * 100)}%)`;
    }

    /**
     * 主动画循环 - 自动往复展示
     */
    function tick(timestamp) {
      if (!state.lastTimestamp) state.lastTimestamp = timestamp;
      const dt = timestamp - state.lastTimestamp;
      state.lastTimestamp = timestamp;

      if (state.isPlaying) {
        state.progress += state.direction * state.speed;

        // 边界平滑停留换向
        if (state.progress <= 0) {
          state.progress = 0;
          state.direction = 0; // 停顿片刻
          setTimeout(() => {
            if (state.isPlaying && state.progress <= 0.05) state.direction = 1;
          }, 1100);
        } else if (state.progress >= 1) {
          state.progress = 1;
          state.direction = 0;
          setTimeout(() => {
            if (state.isPlaying && state.progress >= 0.95) state.direction = -1;
          }, 1400);
        }

        renderFrame(state.progress);
      }

      requestAnimationFrame(tick);
    }

    // 交互事件绑定
    function initControls() {
      // 拖动条实时手动探究
      els.slider.addEventListener('input', (e) => {
        state.isPlaying = false;
        updatePlayBtnIcon();
        state.progress = parseFloat(e.target.value);
        renderFrame(state.progress);
      });

      // 播放/暂停控制
      els.playBtn.addEventListener('click', () => {
        state.isPlaying = !state.isPlaying;
        if (state.isPlaying && state.direction === 0) {
          state.direction = state.progress > 0.5 ? -1 : 1;
        }
        updatePlayBtnIcon();
      });

      // 键盘快捷键 (空格播放/暂停)
      window.addEventListener('keydown', (e) => {
        if (e.code === 'Space') {
          e.preventDefault();
          els.playBtn.click();
        }
      });
    }

    function updatePlayBtnIcon() {
      if (state.isPlaying) {
        els.playIcon.innerHTML = `
          <rect x="6" y="4" width="4" height="16" rx="1"/>
          <rect x="14" y="4" width="4" height="16" rx="1"/>
        `;
      } else {
        els.playIcon.innerHTML = `
          <polygon points="6,4 20,12 6,20"/>
        `;
      }
    }

    // 页面加载完成后自启动运行
    window.addEventListener('DOMContentLoaded', () => {
      initControls();
      renderFrame(1.0);
      // 触发自动开机动画
      requestAnimationFrame(tick);
    });
  </script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>预紧扭簧铰链与拉索自规整折伞机构 - 原理可视化</title>
  <style>
    :root {
      --bg-dark: #090d16;
      --panel-bg: rgba(15, 23, 42, 0.75);
      --panel-border: rgba(56, 189, 248, 0.2);
      --cyan-accent: #38bdf8;
      --orange-accent: #f59e0b;
      --green-accent: #10b981;
      --text-main: #f1f5f9;
      --text-muted: #94a3b8;
      --font-mono: "SF Mono", "Consolas", "Roboto Mono", monospace;
      --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
    }

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

    body {
      background-color: var(--bg-dark);
      color: var(--text-main);
      font-family: var(--font-sans);
      width: 100vw;
      height: 100vh;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      position: relative;
    }

    /* 顶部微型信息条 - 紧凑精炼,不遮挡主体 */
    .top-bar {
      position: absolute;
      top: 12px;
      left: 16px;
      right: 16px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      z-index: 20;
      pointer-events: none;
    }

    .brand-tag {
      display: flex;
      align-items: center;
      gap: 8px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      padding: 5px 12px;
      border-radius: 20px;
      backdrop-filter: blur(8px);
      box-shadow: 0 4px 20px rgba(0,0,0,0.4);
    }

    .status-dot {
      width: 7px;
      height: 7px;
      border-radius: 50%;
      background-color: var(--cyan-accent);
      box-shadow: 0 0 8px var(--cyan-accent);
      animation: pulse-dot 2s infinite ease-in-out;
    }

    @keyframes pulse-dot {
      0%, 100% { opacity: 0.5; transform: scale(0.9); }
      50% { opacity: 1; transform: scale(1.2); }
    }

    .brand-title {
      font-size: 13px;
      font-weight: 600;
      letter-spacing: 0.5px;
      color: #e2e8f0;
    }

    .brand-sub {
      font-size: 11px;
      color: var(--text-muted);
      font-family: var(--font-mono);
      margin-left: 4px;
    }

    /* 状态遥测浮动条 - 极小字号放置在右上角 */
    .hud-metrics {
      display: flex;
      gap: 10px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      padding: 5px 12px;
      border-radius: 8px;
      backdrop-filter: blur(8px);
    }

    .metric-item {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
    }

    .metric-label {
      font-size: 9px;
      color: var(--text-muted);
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .metric-value {
      font-size: 12px;
      font-family: var(--font-mono);
      font-weight: 600;
      color: var(--cyan-accent);
    }

    /* 中央全屏核心画布 */
    .stage-container {
      flex: 1;
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      position: relative;
      background: radial-gradient(circle at 50% 45%, #131d31 0%, #090d16 80%);
    }

    /* 背景工程栅格 */
    .grid-bg {
      position: absolute;
      inset: 0;
      background-image: 
        linear-gradient(rgba(56, 189, 248, 0.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(56, 189, 248, 0.04) 1px, transparent 1px);
      background-size: 32px 32px;
      pointer-events: none;
    }

    /* 主 SVG 动画视图 */
    svg#mechanism-stage {
      width: 100%;
      height: 100%;
      max-width: 1200px;
      max-height: 820px;
      z-index: 10;
    }

    /* 铰链微观特写画中画 (PIP) - 右下角紧凑浮窗 */
    .detail-pip {
      position: absolute;
      right: 18px;
      bottom: 84px;
      width: 200px;
      height: 160px;
      background: rgba(13, 20, 36, 0.85);
      border: 1px solid rgba(245, 158, 11, 0.35);
      border-radius: 10px;
      padding: 8px;
      backdrop-filter: blur(10px);
      z-index: 20;
      box-shadow: 0 8px 30px rgba(0,0,0,0.6);
      display: flex;
      flex-direction: column;
    }

    .pip-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 4px;
      padding-bottom: 4px;
      border-bottom: 1px solid rgba(255,255,255,0.08);
    }

    .pip-title {
      font-size: 10px;
      font-weight: 600;
      color: var(--orange-accent);
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .pip-tag {
      font-size: 9px;
      font-family: var(--font-mono);
      background: rgba(245, 158, 11, 0.15);
      color: #fbbf24;
      padding: 1px 4px;
      border-radius: 3px;
    }

    .pip-svg {
      flex: 1;
      width: 100%;
      height: 100%;
    }

    /* 底部核心精简控制栏 */
    .bottom-control-bar {
      position: absolute;
      bottom: 14px;
      left: 50%;
      transform: translateX(-50%);
      width: min(680px, 92%);
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      border-radius: 28px;
      padding: 8px 18px;
      display: flex;
      align-items: center;
      gap: 14px;
      backdrop-filter: blur(12px);
      z-index: 30;
      box-shadow: 0 10px 30px rgba(0,0,0,0.5);
    }

    .ctrl-btn {
      background: rgba(56, 189, 248, 0.12);
      border: 1px solid rgba(56, 189, 248, 0.3);
      color: var(--cyan-accent);
      border-radius: 50%;
      width: 32px;
      height: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: all 0.2s;
      flex-shrink: 0;
    }

    .ctrl-btn:hover {
      background: var(--cyan-accent);
      color: #04101e;
      box-shadow: 0 0 12px rgba(56, 189, 248, 0.6);
    }

    .ctrl-slider-group {
      flex: 1;
      display: flex;
      flex-direction: column;
      gap: 3px;
    }

    .slider-labels {
      display: flex;
      justify-content: space-between;
      font-size: 10px;
      color: var(--text-muted);
      font-family: var(--font-mono);
    }

    .slider-container {
      position: relative;
      width: 100%;
      display: flex;
      align-items: center;
    }

    input[type="range"] {
      -webkit-appearance: none;
      width: 100%;
      height: 4px;
      border-radius: 2px;
      background: rgba(255,255,255,0.15);
      outline: none;
      cursor: pointer;
    }

    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: 14px;
      height: 14px;
      border-radius: 50%;
      background: var(--cyan-accent);
      box-shadow: 0 0 8px var(--cyan-accent);
      cursor: pointer;
      transition: transform 0.1s;
    }

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

    .state-badge {
      font-size: 11px;
      font-weight: 600;
      color: #38bdf8;
      min-width: 80px;
      text-align: right;
      font-family: var(--font-mono);
    }

    /* 图例指示器 - 左下角精炼放置 */
    .legend-panel {
      position: absolute;
      left: 18px;
      bottom: 84px;
      background: var(--panel-bg);
      border: 1px solid var(--panel-border);
      border-radius: 8px;
      padding: 8px 12px;
      backdrop-filter: blur(8px);
      z-index: 20;
      display: flex;
      flex-direction: column;
      gap: 6px;
      pointer-events: none;
    }

    .legend-item {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 10px;
      color: #cbd5e1;
    }

    .legend-color {
      width: 12px;
      height: 3px;
      border-radius: 2px;
    }

    .legend-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
    }
  </style>
</head>
<body>

  <!-- 顶部轻量 HUD -->
  <header class="top-bar">
    <div class="brand-tag">
      <div class="status-dot"></div>
      <div class="brand-title">自规整折伞机构 <span class="brand-sub">IFR 理想解动态原理</span></div>
    </div>

    <div class="hud-metrics">
      <div class="metric-item">
        <span class="metric-label">拉索工作张力</span>
        <span class="metric-value" id="cable-tension-val">300.0 N</span>
      </div>
      <div class="metric-item">
        <span class="metric-label">扭簧预紧储能</span>
        <span class="metric-value" id="spring-torque-val">0.02 N·m</span>
      </div>
      <div class="metric-item">
        <span class="metric-label">骨段贴合度</span>
        <span class="metric-value" id="alignment-val">100 %</span>
      </div>
    </div>
  </header>

  <!-- 左下角图例说明 -->
  <div class="legend-panel">
    <div class="legend-item">
      <div class="legend-color" style="background: #38bdf8; box-shadow: 0 0 6px #38bdf8;"></div>
      <span>中央张紧拉索 (1.2mm 编织缆)</span>
    </div>
    <div class="legend-item">
      <div class="legend-dot" style="background: #f59e0b; box-shadow: 0 0 6px #f59e0b;"></div>
      <span>内折预紧扭簧铰链 (限位165°)</span>
    </div>
    <div class="legend-item">
      <div class="legend-color" style="background: #94a3b8;"></div>
      <span>玻纤扁骨 (180mm + 160mm)</span>
    </div>
    <div class="legend-item">
      <div class="legend-dot" style="background: #10b981;"></div>
      <span>尾段防划软质导向球头</span>
    </div>
  </div>

  <!-- 右下角局部放大铰链特写 (画中画) -->
  <div class="detail-pip">
    <div class="pip-header">
      <span class="pip-title">铰链内部扭簧剖面</span>
      <span class="pip-tag" id="hinge-pip-mode">锁定受拉</span>
    </div>
    <svg class="pip-svg" viewBox="0 0 160 120" id="pip-hinge-svg">
      <!-- 铰链放大图动态元素 -->
      <g id="pip-dynamic-hinge" transform="translate(80, 60)">
        <!-- 铰链座 -->
        <circle cx="0" cy="0" r="28" fill="#1e293b" stroke="#475569" stroke-width="1.5"/>
        <!-- 限位咬合台阶 -->
        <path d="M -20 -10 L 0 -10 L 15 15" stroke="rgba(245,158,11,0.5)" stroke-dasharray="2 2" fill="none"/>
        <!-- 骨1插芯 -->
        <rect x="-42" y="-5" width="42" height="10" rx="3" fill="#334155" stroke="#64748b"/>
        <!-- 骨2旋转臂 -->
        <g id="pip-rot-arm">
          <rect x="0" y="-5" width="42" height="10" rx="3" fill="#475569" stroke="#94a3b8"/>
          <!-- 旋转轴心 -->
          <circle cx="0" cy="0" r="4" fill="#f59e0b"/>
          <!-- 扭簧螺旋线条 -->
          <path id="pip-spring-coil" d="M -7 -7 A 10 10 0 1 1 7 7" fill="none" stroke="#f59e0b" stroke-width="2.5" stroke-linecap="round"/>
        </g>
        <!-- 拉索穿行轨迹 -->
        <path id="pip-cable-line" d="M -45 -12 L 0 -12 L 45 -12" fill="none" stroke="#38bdf8" stroke-width="2" stroke-dasharray="4 2"/>
        <circle cx="0" cy="-12" r="3" fill="#38bdf8"/>
        <!-- 扭矩力矢量箭头 -->
        <path id="pip-torque-arrow" d="M -16 16 A 22 22 0 0 0 16 16" fill="none" stroke="#f59e0b" stroke-width="1.5" marker-end="url(#arrow)"/>
      </g>
      <defs>
        <marker id="arrow" viewBox="0 0 6 6" refX="4" refY="3" markerWidth="4" markerHeight="4" orient="auto-start-reverse">
          <polygon points="0 0, 6 3, 0 6" fill="#f59e0b"/>
        </marker>
      </defs>
    </svg>
  </div>

  <!-- 中央主舞台 -->
  <main class="stage-container">
    <div class="grid-bg"></div>

    <svg id="mechanism-stage" viewBox="0 0 1000 680" preserveAspectRatio="xMidYMid meet">
      <defs>
        <!-- 渐变定义 -->
        <linearGradient id="canopyGrad" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stop-color="#0284c7" stop-opacity="0.35"/>
          <stop offset="100%" stop-color="#0369a1" stop-opacity="0.1"/>
        </linearGradient>

        <linearGradient id="boneGrad" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stop-color="#cbd5e1"/>
          <stop offset="100%" stop-color="#64748b"/>
        </linearGradient>

        <linearGradient id="shaftGrad" x1="0%" y1="0%" x2="100%" y2="0%">
          <stop offset="0%" stop-color="#334155"/>
          <stop offset="50%" stop-color="#94a3b8"/>
          <stop offset="100%" stop-color="#1e293b"/>
        </linearGradient>

        <!-- 发光滤镜 -->
        <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="3" result="blur"/>
          <feMerge>
            <feMergeNode in="blur"/>
            <feMergeNode in="SourceGraphic"/>
          </feMerge>
        </filter>

        <filter id="glow-orange" x="-30%" y="-30%" width="160%" height="160%">
          <feGaussianBlur stdDeviation="4" result="blur"/>
          <feMerge>
            <feMergeNode in="blur"/>
            <feMergeNode in="SourceGraphic"/>
          </feMerge>
        </filter>
      </defs>

      <!-- 机构几何层(由 JS 动态生成与驱动) -->
      <g id="scene-root">
        <!-- 伞面布面投影 -->
        <path id="canopy-surface-left" fill="url(#canopyGrad)" stroke="rgba(56, 189, 248, 0.4)" stroke-width="1.2"/>
        <path id="canopy-surface-right" fill="url(#canopyGrad)" stroke="rgba(56, 189, 248, 0.4)" stroke-width="1.2"/>

        <!-- 收伞聚拢导向约束外轮廓(淡线提示规整圆柱包络) -->
        <line id="envelope-left" x1="440" y1="120" x2="440" y2="580" stroke="rgba(56,189,248,0.15)" stroke-dasharray="3 3" opacity="0"/>
        <line id="envelope-right" x1="560" y1="120" x2="560" y2="580" stroke="rgba(56,189,248,0.15)" stroke-dasharray="3 3" opacity="0"/>

        <!-- 中棒主轴与伞巢 -->
        <g id="shaft-assembly">
          <!-- 伞顶导向轮与顶巢 -->
          <circle cx="500" cy="115" r="14" fill="#1e293b" stroke="#38bdf8" stroke-width="2"/>
          <circle cx="500" cy="115" r="5" fill="#38bdf8" filter="url(#glow-cyan)"/>
          
          <!-- 中棒主体钢管 -->
          <rect x="494" y="115" width="12" height="470" rx="3" fill="url(#shaftGrad)"/>
          <!-- 刻度线 -->
          <line x1="497" y1="180" x2="497" y2="480" stroke="rgba(255,255,255,0.15)" stroke-dasharray="2 6"/>
          
          <!-- 伞柄手把 -->
          <rect x="490" y="585" width="20" height="50" rx="6" fill="#0f172a" stroke="#334155" stroke-width="2"/>
          <circle cx="500" cy="625" r="4" fill="#38bdf8"/>
        </g>

        <!-- 滑块套筒 (Runner) -->
        <g id="runner-assembly" transform="translate(500, 260)">
          <rect x="-14" y="-12" width="28" height="24" rx="4" fill="#1e293b" stroke="#38bdf8" stroke-width="2"/>
          <circle cx="0" cy="0" r="3" fill="#38bdf8"/>
          <!-- 滑块侧边拉索锁固点 -->
          <circle cx="-9" cy="0" r="2" fill="#f59e0b"/>
          <circle cx="9" cy="0" r="2" fill="#f59e0b"/>
        </g>

        <!-- 左侧完整三节骨架机构组 -->
        <g id="left-arm">
          <line id="l-strut" stroke="#64748b" stroke-width="3" stroke-linecap="round"/>
          <path id="l-cable" fill="none" stroke="#38bdf8" stroke-width="2.2" filter="url(#glow-cyan)"/>
          <line id="l-bone1" stroke="url(#boneGrad)" stroke-width="4.5" stroke-linecap="round"/>
          <line id="l-bone2" stroke="url(#boneGrad)" stroke-width="3.5" stroke-linecap="round"/>
          <line id="l-bone3" stroke="url(#boneGrad)" stroke-width="2.5" stroke-linecap="round"/>

          <!-- 铰链节点 -->
          <g id="l-joint-h1">
            <circle cx="0" cy="0" r="5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="2" fill="#f59e0b" filter="url(#glow-orange)"/>
          </g>
          <g id="l-joint-h2">
            <circle cx="0" cy="0" r="4.5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="1.8" fill="#f59e0b" filter="url(#glow-orange)"/>
          </g>
          <!-- 末端软质防刮球头 -->
          <circle id="l-tip" cx="0" cy="0" r="4.5" fill="#10b981" stroke="#064e3b" stroke-width="1.2"/>
        </g>

        <!-- 右侧完整三节骨架机构组 (详细高亮对称展示) -->
        <g id="right-arm">
          <!-- 连杆支撑杆 -->
          <line id="r-strut" stroke="#64748b" stroke-width="3" stroke-linecap="round"/>
          
          <!-- 中央张紧编织拉索 -->
          <path id="r-cable" fill="none" stroke="#38bdf8" stroke-width="2.2" filter="url(#glow-cyan)"/>
          
          <!-- 主骨第1段 (180mm) -->
          <line id="r-bone1" stroke="url(#boneGrad)" stroke-width="4.5" stroke-linecap="round"/>
          <!-- 主骨第2段 (160mm) -->
          <line id="r-bone2" stroke="url(#boneGrad)" stroke-width="3.5" stroke-linecap="round"/>
          <!-- 尾骨段 -->
          <line id="r-bone3" stroke="url(#boneGrad)" stroke-width="2.5" stroke-linecap="round"/>

          <!-- 铰链1 (扭簧预紧节点 A) -->
          <g id="r-joint-h1">
            <circle cx="0" cy="0" r="5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="2" fill="#f59e0b" filter="url(#glow-orange)"/>
            <path id="r-torque-icon1" d="M -6 -6 A 8 8 0 1 1 6 6" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="2 1"/>
          </g>

          <!-- 铰链2 (扭簧预紧节点 B) -->
          <g id="r-joint-h2">
            <circle cx="0" cy="0" r="4.5" fill="#0f172a" stroke="#f59e0b" stroke-width="2"/>
            <circle cx="0" cy="0" r="1.8" fill="#f59e0b" filter="url(#glow-orange)"/>
            <path id="r-torque-icon2" d="M -5 -5 A 7 7 0 1 1 5 5" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="2 1"/>
          </g>

          <!-- 软质导向球头 -->
          <circle id="r-tip" cx="0" cy="0" r="4.5" fill="#10b981" stroke="#064e3b" stroke-width="1.2"/>
        </g>

        <!-- 动态应力矢量与折叠流动导向波纹 -->
        <g id="dynamics-layer" pointer-events="none"></g>
      </g>
    </svg>
  </main>

  <!-- 底部微交互控制栏 -->
  <footer class="bottom-control-bar">
    <button class="ctrl-btn" id="play-btn" title="播放 / 暂停 (空格)">
      <svg id="play-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
        <rect x="6" y="4" width="4" height="16" rx="1"/>
        <rect x="14" y="4" width="4" height="16" rx="1"/>
      </svg>
    </button>

    <div class="ctrl-slider-group">
      <div class="slider-labels">
        <span>收拢贴合 (拉索松弛·扭簧驱动)</span>
        <span id="slider-pct">开伞状态 (100%)</span>
        <span>完全撑开 (拉索锁定·刚性限位)</span>
      </div>
      <div class="slider-container">
        <input type="range" id="progress-slider" min="0" max="1" step="0.001" value="1"/>
      </div>
    </div>

    <div class="state-badge" id="mech-state-badge">刚性锁定</div>
  </footer>

  <script>
    /**
     * 连杆与扭簧拉索动力学模拟核心
     * 实现开伞刚性锁定与收伞自动顺序贴合的 IFR 机制
     */

    // 几何拓扑常量配置 (单位: SVG 坐标像素,精确对齐物理比例)
    const CFG = {
      cx: 500,           // 伞中棒 X 坐标
      topY: 120,         // 伞顶固定铰接座 Y
      handleY: 585,      // 伞柄位置
      // 骨节长度
      l1: 175,           // 主骨第1段 (180mm 标定)
      l2: 155,           // 主骨第2段 (160mm 标定)
      l3: 110,           // 尾骨段
      lStrut: 130,       // 支撑撑杆长度
      strutAttachRatio: 0.38, // 撑杆在主骨1上的铰接比例
      // 滑块行程
      runnerY_open: 285,  // 开伞时滑块位置 (撑开至倒V伞穹顶形态)
      runnerY_close: 520 // 收伞时滑块位置 (下移,卸载)
    };

    // 动画状态机
    let state = {
      progress: 1.0,     // 0 = 彻底收拢贴合, 1 = 彻底撑开锁定
      direction: -1,     // 默认加载后自动开始收伞演示
      isPlaying: true,
      speed: 0.0042,     // 平滑播放步进速度
      lastTimestamp: 0
    };

    // DOM 元素引用
    const els = {
      slider: document.getElementById('progress-slider'),
      playBtn: document.getElementById('play-btn'),
      playIcon: document.getElementById('play-icon'),
      badge: document.getElementById('mech-state-badge'),
      sliderPct: document.getElementById('slider-pct'),
      cableTensionVal: document.getElementById('cable-tension-val'),
      springTorqueVal: document.getElementById('spring-torque-val'),
      alignmentVal: document.getElementById('alignment-val'),
      // PIP 特写
      pipHingeMode: document.getElementById('hinge-pip-mode'),
      pipRotArm: document.getElementById('pip-rot-arm'),
      pipSpringCoil: document.getElementById('pip-spring-coil'),
      pipCableLine: document.getElementById('pip-cable-line'),
      // 机构图形元素
      runner: document.getElementById('runner-assembly'),
      rStrut: document.getElementById('r-strut'),
      rCable: document.getElementById('r-cable'),
      rBone1: document.getElementById('r-bone1'),
      rBone2: document.getElementById('r-bone2'),
      rBone3: document.getElementById('r-bone3'),
      rJointH1: document.getElementById('r-joint-h1'),
      rJointH2: document.getElementById('r-joint-h2'),
      rTip: document.getElementById('r-tip'),
      rTorqueIcon1: document.getElementById('r-torque-icon1'),
      rTorqueIcon2: document.getElementById('r-torque-icon2'),
      // 左侧镜像
      lStrut: document.getElementById('l-strut'),
      lCable: document.getElementById('l-cable'),
      lBone1: document.getElementById('l-bone1'),
      lBone2: document.getElementById('l-bone2'),
      lBone3: document.getElementById('l-bone3'),
      lJointH1: document.getElementById('l-joint-h1'),
      lJointH2: document.getElementById('l-joint-h2'),
      lTip: document.getElementById('l-tip'),
      // 伞面与轮廓
      canopyLeft: document.getElementById('canopy-surface-left'),
      canopyRight: document.getElementById('canopy-surface-right'),
      envL: document.getElementById('envelope-left'),
      envR: document.getElementById('envelope-right'),
    };

    /**
     * 根据当前开合进度 t 计算全套骨节与拉索的空间几何姿态
     * @param {number} t [0 = 收伞, 1 = 开伞]
     * @param {number} side 1 = 右侧, -1 = 左侧
     */
    function calculateKinematics(t, side = 1) {
      // 1. 滑块位置插值
      const runnerY = CFG.runnerY_close + (CFG.runnerY_open - CFG.runnerY_close) * t;

      // 2. 主骨1仰角:收伞贴近中棒(约86度向下),开伞展开到约18度(呈倒V伞顶向下倾斜自然支撑形态,杜绝向上仰翻)
      const bone1AngleDeg = 86 - t * 68; // 86° -> 18° (保持倾斜向下)
      const bone1Rad = (bone1AngleDeg * Math.PI) / 180;

      // 主骨1基点 (伞巢固定点)
      const p0 = { x: CFG.cx + side * 8, y: CFG.topY };

      // 主骨1末端 (铰链1位置)
      const p1 = {
        x: p0.x + side * CFG.l1 * Math.cos(bone1Rad),
        y: p0.y + CFG.l1 * Math.sin(bone1Rad)
      };

      // 支撑连杆铰接在主骨1上的点
      const pStrutAttach = {
        x: p0.x + (p1.x - p0.x) * CFG.strutAttachRatio,
        y: p0.y + (p1.y - p0.y) * CFG.strutAttachRatio
      };

      // 滑块支撑座铰接点
      const pRunnerJoint = {
        x: CFG.cx + side * 10,
        y: runnerY
      };

      // 3. 铰链1 (主骨2) 折角计算:
      // 当 t < 0.85 时,扭簧释放,骨段内折;开伞 t=1 时与主骨1微弧衔接(约22°保持整体倒V形态)
      let fold1 = 0;
      if (t < 0.85) {
        const foldProgress1 = (0.85 - t) / 0.85; // 0 -> 1
        fold1 = Math.pow(foldProgress1, 1.2) * 165; // 最大内折 165 度
      }
      const bone2AngleDeg = (bone1AngleDeg + 4 * t) - fold1 * 0.96;
      const bone2Rad = (bone2AngleDeg * Math.PI) / 180;
      const p2 = {
        x: p1.x + side * CFG.l2 * Math.cos(bone2Rad),
        y: p1.y + CFG.l2 * Math.sin(bone2Rad)
      };

      // 4. 铰链2 (尾骨段) 折角计算:开伞时呈自然下收微弧(约26°),收伞时内折贴入
      let fold2 = 0;
      if (t < 0.95) {
        const foldProgress2 = Math.min(1, (0.95 - t) / 0.7);
        fold2 = Math.pow(foldProgress2, 0.9) * 155; // 尾骨反折贴入
      }
      const bone3AngleDeg = (bone2AngleDeg + 4 * t) + fold2 * 0.88;
      const bone3Rad = (bone3AngleDeg * Math.PI) / 180;
      const p3 = {
        x: p2.x + side * CFG.l3 * Math.cos(bone3Rad),
        y: p2.y + CFG.l3 * Math.sin(bone3Rad)
      };

      // 5. 中央拉索穿行路径:从滑块 -> 伞顶导轮 -> 铰链1背侧 -> 铰链2背侧 -> 尾骨固定锚点
      const cableSlack = (1 - t) * 12;
      const cablePath = `M ${pRunnerJoint.x} ${pRunnerJoint.y} 
        Q ${CFG.cx + side * 16} ${(CFG.topY + runnerY) * 0.48}, ${CFG.cx + side * 14} ${CFG.topY - 4} 
        Q ${(p0.x + p1.x)*0.5 + side * cableSlack*0.5} ${(p0.y + p1.y)*0.5 - cableSlack*0.3}, ${p1.x} ${p1.y - 3} 
        Q ${(p1.x + p2.x)*0.5} ${(p1.y + p2.y)*0.5}, ${p2.x} ${p2.y - 2} 
        L ${p3.x} ${p3.y}`;

      return {
        p0, p1, p2, p3, pStrutAttach, pRunnerJoint, runnerY, fold1, fold2, cablePath
      };
    }

    /**
     * 将计算好的物理坐标渲染到 SVG 视图与 HUD
     */
    function renderFrame(t) {
      const right = calculateKinematics(t, 1);
      const left = calculateKinematics(t, -1);

      // 1. 更新滑块套筒位置
      els.runner.setAttribute('transform', `translate(${CFG.cx}, ${right.runnerY})`);

      // 2. 更新右侧骨架连杆
      els.rStrut.setAttribute('x1', right.pRunnerJoint.x);
      els.rStrut.setAttribute('y1', right.pRunnerJoint.y);
      els.rStrut.setAttribute('x2', right.pStrutAttach.x);
      els.rStrut.setAttribute('y2', right.pStrutAttach.y);

      els.rBone1.setAttribute('x1', right.p0.x);
      els.rBone1.setAttribute('y1', right.p0.y);
      els.rBone1.setAttribute('x2', right.p1.x);
      els.rBone1.setAttribute('y2', right.p1.y);

      els.rBone2.setAttribute('x1', right.p1.x);
      els.rBone2.setAttribute('y1', right.p1.y);
      els.rBone2.setAttribute('x2', right.p2.x);
      els.rBone2.setAttribute('y2', right.p2.y);

      els.rBone3.setAttribute('x1', right.p2.x);
      els.rBone3.setAttribute('y1', right.p2.y);
      els.rBone3.setAttribute('x2', right.p3.x);
      els.rBone3.setAttribute('y2', right.p3.y);

      els.rJointH1.setAttribute('transform', `translate(${right.p1.x}, ${right.p1.y})`);
      els.rJointH2.setAttribute('transform', `translate(${right.p2.x}, ${right.p2.y})`);
      els.rTip.setAttribute('cx', right.p3.x);
      els.rTip.setAttribute('cy', right.p3.y);

      els.rCable.setAttribute('d', right.cablePath);

      // 3. 更新左侧骨架连杆 (与右侧精确镜像对称)
      els.lStrut.setAttribute('x1', left.pRunnerJoint.x);
      els.lStrut.setAttribute('y1', left.pRunnerJoint.y);
      els.lStrut.setAttribute('x2', left.pStrutAttach.x);
      els.lStrut.setAttribute('y2', left.pStrutAttach.y);

      els.lBone1.setAttribute('x1', left.p0.x);
      els.lBone1.setAttribute('y1', left.p0.y);
      els.lBone1.setAttribute('x2', left.p1.x);
      els.lBone1.setAttribute('y2', left.p1.y);

      els.lBone2.setAttribute('x1', left.p1.x);
      els.lBone2.setAttribute('y1', left.p1.y);
      els.lBone2.setAttribute('x2', left.p2.x);
      els.lBone2.setAttribute('y2', left.p2.y);

      els.lBone3.setAttribute('x1', left.p2.x);
      els.lBone3.setAttribute('y1', left.p2.y);
      els.lBone3.setAttribute('x2', left.p3.x);
      els.lBone3.setAttribute('y2', left.p3.y);

      els.lJointH1.setAttribute('transform', `translate(${left.p1.x}, ${left.p1.y})`);
      els.lJointH2.setAttribute('transform', `translate(${left.p2.x}, ${left.p2.y})`);
      els.lTip.setAttribute('cx', left.p3.x);
      els.lTip.setAttribute('cy', left.p3.y);

      els.lCable.setAttribute('d', left.cablePath);

      // 4. 更新伞面布面曲线
      const rightCanopyD = `M ${CFG.cx} ${CFG.topY} 
        Q ${(right.p0.x + right.p1.x)*0.5} ${(right.p0.y + right.p1.y)*0.5 - 6*t}, ${right.p1.x} ${right.p1.y} 
        Q ${(right.p1.x + right.p2.x)*0.5} ${(right.p1.y + right.p2.y)*0.5 - 4*t}, ${right.p2.x} ${right.p2.y} 
        Q ${(right.p2.x + right.p3.x)*0.5} ${(right.p2.y + right.p3.y)*0.5 - 2*t}, ${right.p3.x} ${right.p3.y} 
        L ${right.p3.x - 4} ${right.p3.y + 4} 
        Z`;
      els.canopyRight.setAttribute('d', rightCanopyD);

      const leftCanopyD = `M ${CFG.cx} ${CFG.topY} 
        Q ${(left.p0.x + left.p1.x)*0.5} ${(left.p0.y + left.p1.y)*0.5 - 6*t}, ${left.p1.x} ${left.p1.y} 
        Q ${(left.p1.x + left.p2.x)*0.5} ${(left.p1.y + left.p2.y)*0.5 - 4*t}, ${left.p2.x} ${left.p2.y} 
        Q ${(left.p2.x + left.p3.x)*0.5} ${(left.p2.y + left.p3.y)*0.5 - 2*t}, ${left.p3.x} ${left.p3.y} 
        L ${left.p3.x + 4} ${left.p3.y + 4} 
        Z`;
      els.canopyLeft.setAttribute('d', leftCanopyD);

      // 5. 动态更新包络线透明度 (收伞时显现规整柱状约束)
      const envOpacity = Math.max(0, (0.3 - t) * 2.5);
      els.envL.style.opacity = envOpacity;
      els.envR.style.opacity = envOpacity;

      // 6. 更新画中画 (PIP) 铰链特写旋转角
      const pipAngle = right.fold1 * 0.9;
      els.pipRotArm.setAttribute('transform', `rotate(${-pipAngle})`);
      // 扭簧线圈形变
      const coilScale = 1 + (1 - t) * 0.25;
      els.pipSpringCoil.setAttribute('transform', `scale(${coilScale})`);
      
      // 拉索在特写中的松弛/张紧
      if (t > 0.7) {
        els.pipHingeMode.textContent = '锁定受拉 300N';
        els.pipHingeMode.style.color = '#38bdf8';
        els.pipCableLine.setAttribute('stroke', '#38bdf8');
        els.pipCableLine.setAttribute('stroke-width', '2.5');
      } else {
        els.pipHingeMode.textContent = '扭簧释能内折';
        els.pipHingeMode.style.color = '#f59e0b';
        els.pipCableLine.setAttribute('stroke', 'rgba(56,189,248,0.3)');
        els.pipCableLine.setAttribute('stroke-width', '1.2');
      }

      // 7. 更新 HUD 遥测参数读数与状态徽标
      const cableTension = (Math.max(0, (t - 0.2) / 0.8) * 300).toFixed(1);
      const springTorque = ((1 - t) * 0.02).toFixed(3);
      const alignQuality = (100 - Math.abs(t - 0.5) * 12).toFixed(0);

      els.cableTensionVal.textContent = `${cableTension} N`;
      els.springTorqueVal.textContent = `${(0.02 * (1 - t * 0.5)).toFixed(3)} N·m`;
      
      if (t < 0.15) {
        els.badge.textContent = '自动贴杆聚拢';
        els.badge.style.color = '#10b981';
        els.alignmentVal.textContent = '100 % (极佳)';
        els.rCable.setAttribute('stroke-opacity', '0.25');
        els.lCable.setAttribute('stroke-opacity', '0.25');
      } else if (t < 0.8) {
        els.badge.textContent = '扭簧顺序内折中';
        els.badge.style.color = '#f59e0b';
        els.alignmentVal.textContent = '自寻优对齐...';
        els.rCable.setAttribute('stroke-opacity', '0.6');
        els.lCable.setAttribute('stroke-opacity', '0.6');
      } else {
        els.badge.textContent = '刚性限位锁定';
        els.badge.style.color = '#38bdf8';
        els.alignmentVal.textContent = '刚性支撑';
        els.rCable.setAttribute('stroke-opacity', '1');
        els.lCable.setAttribute('stroke-opacity', '1');
      }

      // 更新滑块状态
      els.slider.value = t;
      els.sliderPct.textContent = `状态 (${Math.round(t * 100)}%)`;
    }

    /**
     * 主动画循环 - 自动往复展示
     */
    function tick(timestamp) {
      if (!state.lastTimestamp) state.lastTimestamp = timestamp;
      const dt = timestamp - state.lastTimestamp;
      state.lastTimestamp = timestamp;

      if (state.isPlaying) {
        state.progress += state.direction * state.speed;

        // 边界平滑停留换向
        if (state.progress <= 0) {
          state.progress = 0;
          state.direction = 0; // 停顿片刻
          setTimeout(() => {
            if (state.isPlaying && state.progress <= 0.05) state.direction = 1;
          }, 1100);
        } else if (state.progress >= 1) {
          state.progress = 1;
          state.direction = 0;
          setTimeout(() => {
            if (state.isPlaying && state.progress >= 0.95) state.direction = -1;
          }, 1400);
        }

        renderFrame(state.progress);
      }

      requestAnimationFrame(tick);
    }

    // 交互事件绑定
    function initControls() {
      // 拖动条实时手动探究
      els.slider.addEventListener('input', (e) => {
        state.isPlaying = false;
        updatePlayBtnIcon();
        state.progress = parseFloat(e.target.value);
        renderFrame(state.progress);
      });

      // 播放/暂停控制
      els.playBtn.addEventListener('click', () => {
        state.isPlaying = !state.isPlaying;
        if (state.isPlaying && state.direction === 0) {
          state.direction = state.progress > 0.5 ? -1 : 1;
        }
        updatePlayBtnIcon();
      });

      // 键盘快捷键 (空格播放/暂停)
      window.addEventListener('keydown', (e) => {
        if (e.code === 'Space') {
          e.preventDefault();
          els.playBtn.click();
        }
      });
    }

    function updatePlayBtnIcon() {
      if (state.isPlaying) {
        els.playIcon.innerHTML = `
          <rect x="6" y="4" width="4" height="16" rx="1"/>
          <rect x="14" y="4" width="4" height="16" rx="1"/>
        `;
      } else {
        els.playIcon.innerHTML = `
          <polygon points="6,4 20,12 6,20"/>
        `;
      }
    }

    // 页面加载完成后自启动运行
    window.addEventListener('DOMContentLoaded', () => {
      initControls();
      renderFrame(1.0);
      // 触发自动开机动画
      requestAnimationFrame(tick);
    });
  </script>
</body>
</html>
积分规则:每轮对话扣10分