分享图
动画工坊
引擎就绪
<!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-color: #0b0f19;
      --panel-bg: rgba(15, 23, 42, 0.75);
      --border-color: rgba(56, 189, 248, 0.18);
      --accent-cyan: #38bdf8;
      --accent-orange: #fb923c;
      --accent-pink: #f43f5e;
      --accent-green: #34d399;
      --text-main: #f1f5f9;
      --text-sub: #94a3b8;
      --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
    }

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

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

    /* 背景工程网格与极光微晕 */
    .bg-grid {
      position: absolute;
      inset: 0;
      background-image: 
        radial-gradient(circle at 50% 20%, rgba(56, 189, 248, 0.08) 0%, transparent 60%),
        linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
      background-size: 100% 100%, 40px 40px, 40px 40px;
      pointer-events: none;
      z-index: 0;
    }

    /* 顶部精简状态头(极小字号,靠边缘,防遮挡) */
    header {
      position: absolute;
      top: 14px;
      left: 18px;
      z-index: 10;
      pointer-events: none;
      display: flex;
      flex-direction: column;
      gap: 4px;
    }

    .badge {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      padding: 3px 8px;
      border-radius: 4px;
      background: rgba(56, 189, 248, 0.12);
      border: 1px solid var(--border-color);
      width: fit-content;
    }

    .badge-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: var(--accent-cyan);
      box-shadow: 0 0 8px var(--accent-cyan);
      animation: pulse 2s infinite;
    }

    .badge-text {
      font-size: 11px;
      letter-spacing: 0.5px;
      color: var(--accent-cyan);
      font-weight: 600;
    }

    .main-title {
      font-size: 14px;
      font-weight: 600;
      color: #fff;
      letter-spacing: 0.3px;
    }

    .spec-tags {
      display: flex;
      gap: 8px;
      margin-top: 2px;
    }

    .tag {
      font-size: 10px;
      color: var(--text-sub);
      background: rgba(255, 255, 255, 0.04);
      padding: 2px 6px;
      border-radius: 3px;
      border: 1px solid rgba(255, 255, 255, 0.06);
    }

    .tag b {
      color: var(--text-main);
      font-weight: 500;
    }

    /* 右上角局部微观特写 HUD 框 */
    .micro-hud {
      position: absolute;
      top: 14px;
      right: 18px;
      z-index: 10;
      background: var(--panel-bg);
      border: 1px solid var(--border-color);
      border-radius: 8px;
      padding: 8px 10px;
      backdrop-filter: blur(8px);
      width: 190px;
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
      pointer-events: none;
    }

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

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

    .hud-status {
      font-size: 10px;
      font-family: monospace;
      color: var(--accent-green);
    }

    .hud-svg-box {
      width: 100%;
      height: 78px;
      background: rgba(0, 0, 0, 0.25);
      border-radius: 4px;
    }

    .hud-desc {
      font-size: 9.5px;
      color: var(--text-sub);
      margin-top: 5px;
      line-height: 1.3;
    }

    /* 主视觉 SVG 画布容器:全屏自适应,核心位于居中区域 */
    .viewport {
      flex: 1;
      width: 100%;
      height: 100%;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 1;
    }

    svg#mechanism-stage {
      width: 100%;
      height: 100%;
      max-width: 1100px;
      max-height: 820px;
      overflow: visible;
    }

    /* 图例(右侧靠边纵向紧凑排布) */
    .legend-panel {
      position: absolute;
      right: 18px;
      bottom: 74px;
      z-index: 10;
      background: var(--panel-bg);
      border: 1px solid rgba(255, 255, 255, 0.08);
      border-radius: 6px;
      padding: 7px 10px;
      backdrop-filter: blur(6px);
      display: flex;
      flex-direction: column;
      gap: 5px;
      pointer-events: none;
    }

    .legend-item {
      display: flex;
      align-items: center;
      gap: 7px;
      font-size: 10px;
      color: var(--text-sub);
    }

    .legend-bar {
      width: 14px;
      height: 3px;
      border-radius: 2px;
    }

    /* 底部极简浮动控制栏 */
    footer.control-dock {
      position: absolute;
      bottom: 14px;
      left: 50%;
      transform: translateX(-50%);
      z-index: 20;
      display: flex;
      align-items: center;
      gap: 12px;
      background: var(--panel-bg);
      border: 1px solid var(--border-color);
      border-radius: 30px;
      padding: 6px 16px;
      backdrop-filter: blur(12px);
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
    }

    .btn-toggle {
      background: rgba(56, 189, 248, 0.15);
      border: 1px solid var(--accent-cyan);
      color: #fff;
      width: 28px;
      height: 28px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      outline: none;
      transition: all 0.2s ease;
    }

    .btn-toggle:hover {
      background: var(--accent-cyan);
      color: #0b0f19;
      transform: scale(1.05);
    }

    .slider-box {
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .slider-box span {
      font-size: 11px;
      color: var(--text-sub);
      font-family: monospace;
      min-width: 52px;
    }

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

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

    .status-text-pill {
      font-size: 11px;
      color: var(--text-main);
      padding: 2px 8px;
      background: rgba(255, 255, 255, 0.06);
      border-radius: 12px;
      white-space: nowrap;
    }

    @keyframes pulse {
      0%, 100% { opacity: 0.4; }
      50% { opacity: 1; }
    }
  </style>
</head>
<body>
  <div class="bg-grid"></div>

  <!-- 顶部轻量标头 -->
  <header>
    <div class="badge">
      <div class="badge-dot"></div>
      <span class="badge-text">TRIZ IFR 最终理想解工程模型</span>
    </div>
    <div class="main-title">三段自律防夹骨架:外段先折 × 6mm 延时槽 × 柔性索自收拢</div>
    <div class="spec-tags">
      <div class="tag">主骨配比 <b>220 / 180 / 140 mm</b></div>
      <div class="tag">折角限位 <b>外 165° | 中 150°</b></div>
      <div class="tag">牵引预紧 <b>2.5 N</b></div>
      <div class="tag">机械延时 <b>6 mm 长槽</b></div>
    </div>
  </header>

  <!-- 右上角特写观察视窗(6mm延时机构与折角限位) -->
  <div class="micro-hud">
    <div class="hud-header">
      <span class="hud-title">节点微动显微</span>
      <span class="hud-status" id="slot-travel-text">槽位移: 0.0mm</span>
    </div>
    <svg class="hud-svg-box" viewBox="0 0 160 65" id="hud-svg">
      <defs>
        <pattern id="hud-subgrid" width="10" height="10" patternUnits="userSpaceOnUse">
          <path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="0.5"/>
        </pattern>
      </defs>
      <rect width="100%" height="100%" fill="url(#hud-subgrid)"/>
      <!-- 中段长槽导轨 -->
      <rect x="25" y="24" width="100" height="12" rx="6" fill="#1e293b" stroke="#475569" stroke-width="1.5"/>
      <!-- 行程刻度线 -->
      <line x1="45" y1="20" x2="45" y2="40" stroke="#f43f5e" stroke-dasharray="2,2" stroke-width="1"/>
      <line x1="85" y1="20" x2="85" y2="40" stroke="#34d399" stroke-dasharray="2,2" stroke-width="1"/>
      <text x="35" y="16" fill="#94a3b8" font-size="7" font-family="monospace">0mm</text>
      <text x="80" y="16" fill="#34d399" font-size="7" font-family="monospace">6mm限位</text>
      <!-- 滑动销轴(动态) -->
      <circle id="hud-pin" cx="45" cy="30" r="5" fill="#38bdf8" stroke="#fff" stroke-width="1.5"/>
      <path id="hud-arrow" d="M 52 30 L 68 30 M 64 27 L 68 30 L 64 33" stroke="#38bdf8" stroke-width="1.2" fill="none"/>
    </svg>
    <div class="hud-desc" id="hud-note">跑套下滑初期:撑杆销轴空行程滑移,锁住中段延迟动作。</div>
  </div>

  <!-- 中央主机构舞台 -->
  <div class="viewport">
    <svg id="mechanism-stage" viewBox="0 0 960 660">
      <defs>
        <!-- 伞面半透明膜梯度 -->
        <linearGradient id="canopyGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#38bdf8" stop-opacity="0.25"/>
          <stop offset="60%" stop-color="#0284c7" stop-opacity="0.12"/>
          <stop offset="100%" stop-color="#0369a1" stop-opacity="0.04"/>
        </linearGradient>

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

        <!-- 发光滤镜 -->
        <filter id="cyanGlow" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="3" result="blur" />
          <feComposite in="SourceGraphic" in2="blur" operator="over" />
        </filter>
        <filter id="pinkGlow" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="3.5" result="blur" />
          <feComposite in="SourceGraphic" in2="blur" operator="over" />
        </filter>
      </defs>

      <!-- 导向中棒与刻度 -->
      <g id="center-shaft-assembly">
        <!-- 垂直中轴线 -->
        <line x1="480" y1="60" x2="480" y2="600" stroke="rgba(255,255,255,0.08)" stroke-width="1" stroke-dasharray="4,4"/>
        <!-- 中棒实体 -->
        <rect x="474" y="90" width="12" height="490" rx="4" fill="url(#shaftGrad)"/>
        <!-- 顶冠/上巢(固定铰接盘) -->
        <path d="M 464 100 L 496 100 L 492 118 L 468 118 Z" fill="#94a3b8" stroke="#334155" stroke-width="1.5"/>
        <circle cx="480" cy="106" r="3" fill="#0f172a"/>
        <text x="498" y="110" fill="#64748b" font-size="9" font-family="monospace">TOP-HUB (上巢)</text>
      </g>

      <!-- 伞布曲面网(背景层) -->
      <path id="canopy-mesh-left" fill="url(#canopyGrad)" stroke="rgba(56,189,248,0.4)" stroke-width="1.2"/>
      <path id="canopy-mesh-right" fill="url(#canopyGrad)" stroke="rgba(56,189,248,0.4)" stroke-width="1.2"/>

      <!-- 动态骨架系统容器 (双侧对称,重点透视右侧) -->
      <!-- 柔性牵引索(粉红发光虚线:表现主动施加 2.5N 张力引导布面向轴心收束) -->
      <g id="tension-cables">
        <path id="cable-left" stroke="#f43f5e" stroke-width="1.5" stroke-dasharray="4,3" fill="none" opacity="0.85" filter="url(#pinkGlow)"/>
        <path id="cable-right" stroke="#f43f5e" stroke-width="1.5" stroke-dasharray="4,3" fill="none" opacity="0.85" filter="url(#pinkGlow)"/>
        <!-- 牵引向量箭头标识 -->
        <g id="tension-vectors" opacity="0"></g>
      </g>

      <!-- 伞骨连杆图元 -->
      <g id="ribs-group">
        <!-- 左侧副连杆组 (镜像,微弱透明度以突显右侧核心分析区) -->
        <g id="left-ribs" opacity="0.45"></g>

        <!-- 右侧主分析连杆组 -->
        <g id="right-ribs">
          <!-- 内段主骨 L1 (220mm) -->
          <line id="inner-rib" stroke="#94a3b8" stroke-width="5" stroke-linecap="round"/>
          <line id="inner-rib-core" stroke="#e2e8f0" stroke-width="1.5" stroke-linecap="round"/>

          <!-- 撑杆 Stretcher (连接跑套与中段延时槽) -->
          <line id="stretcher" stroke="#38bdf8" stroke-width="3.5" stroke-linecap="round"/>

          <!-- 中段主骨 L2 (180mm) -->
          <line id="mid-rib" stroke="#38bdf8" stroke-width="4.5" stroke-linecap="round"/>
          <!-- 中段延时滑动槽槽位标识 -->
          <line id="slot-line" stroke="#10b981" stroke-width="6" stroke-linecap="round" opacity="0.75"/>
          <circle id="slot-pin" r="3.2" fill="#fff" stroke="#0f172a" stroke-width="1.5"/>

          <!-- 外段主骨 L3 (140mm) - 橙黄色高亮优先折转 -->
          <line id="outer-rib" stroke="#fb923c" stroke-width="3.5" stroke-linecap="round" filter="url(#cyanGlow)"/>
          <circle id="outer-tip" r="3" fill="#fdba74"/>

          <!-- 铰链节点 -->
          <circle id="hinge-0" cx="480" cy="112" r="3.5" fill="#f8fafc" stroke="#0f172a" stroke-width="1.5"/>
          <circle id="hinge-1" r="4" fill="#38bdf8" stroke="#fff" stroke-width="1.5"/>
          <circle id="hinge-2" r="4" fill="#fb923c" stroke="#fff" stroke-width="1.5"/>
        </g>
      </g>

      <!-- 下滑跑套 (Runner) -->
      <g id="runner-assembly">
        <rect id="runner" x="468" y="240" width="24" height="28" rx="4" fill="#38bdf8" stroke="#0284c7" stroke-width="2"/>
        <line x1="472" y1="254" x2="488" y2="254" stroke="#fff" stroke-width="1.5"/>
        <circle id="runner-hinge" cx="480" cy="254" r="3" fill="#fff"/>
        <text id="runner-label" x="424" y="258" fill="#38bdf8" font-size="9" font-family="monospace">RUNNER ▼</text>
      </g>

      <!-- 动态角度与运动轨迹弧线提示 -->
      <g id="motion-annotations" pointer-events="none">
        <!-- 外段优先折叠角指示 -->
        <path id="angle-arc-outer" fill="none" stroke="#fb923c" stroke-width="1.2" stroke-dasharray="2,2"/>
        <text id="angle-text-outer" fill="#fb923c" font-size="9.5" font-weight="600" font-family="monospace">165° 先折</text>

        <!-- 中段后折角指示 -->
        <path id="angle-arc-mid" fill="none" stroke="#38bdf8" stroke-width="1.2" stroke-dasharray="2,2"/>
        <text id="angle-text-mid" fill="#38bdf8" font-size="9.5" font-weight="600" font-family="monospace">150° 延迟</text>
      </g>

      <!-- 收拢导引力场波纹(在近收束态时展现布面平顺内压) -->
      <g id="inward-field-lines" opacity="0"></g>
    </svg>
  </div>

  <!-- 图例面板 -->
  <div class="legend-panel">
    <div class="legend-item">
      <div class="legend-bar" style="background:#fb923c"></div>
      <span>外段骨 (140mm / 165°先折)</span>
    </div>
    <div class="legend-item">
      <div class="legend-bar" style="background:#10b981"></div>
      <span>撑杆滑移 (6mm延迟槽)</span>
    </div>
    <div class="legend-item">
      <div class="legend-bar" style="background:#38bdf8"></div>
      <span>中段骨 (180mm / 150°后折)</span>
    </div>
    <div class="legend-item">
      <div class="legend-bar" style="background:#f43f5e"></div>
      <span>柔性索 (2.5N自收拢防夹)</span>
    </div>
  </div>

  <!-- 底部微型播放控制台 -->
  <footer class="control-dock">
    <button class="btn-toggle" id="btn-play-pause" title="播放 / 暂停">
      <svg id="play-icon" width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
        <path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>
      </svg>
    </button>
    <div class="slider-box">
      <input type="range" id="progress-slider" min="0" max="1" step="0.001" value="0"/>
      <span id="cycle-percent">0%</span>
    </div>
    <div class="status-text-pill" id="stage-desc">开伞稳定态:膜面张紧</div>
  </footer>

  <script>
    /**
     * 三段式自律防夹伞骨收合数学运动模型
     * 遵循 TRIZ IFR 哲学:利用几何行程差与构件内在时序解耦,消除随机夹布矛盾。
     */
    const CONFIG = {
      hub: { x: 480, y: 112 },
      runnerOpenY: 235,
      runnerCloseY: 485,
      L1: 130, // 内段 (220mm 标度映射)
      L2: 108, // 中段 (180mm 标度映射)
      L3: 84,  // 外段 (140mm 标度映射)
      stretcherLen: 125,
      slotDistOnMid: 36, // 长槽位于中段骨近端基准位置
      slotStroke: 18,    // 对应 6mm 机械延时槽行程
      cycleDuration: 6200 // 完整单周期动画时间 ms
    };

    // DOM 元素引用
    const stage = document.getElementById('mechanism-stage');
    const runnerEl = document.getElementById('runner');
    const runnerHingeEl = document.getElementById('runner-hinge');
    const runnerLabel = document.getElementById('runner-label');
    const innerRib = document.getElementById('inner-rib');
    const innerRibCore = document.getElementById('inner-rib-core');
    const midRib = document.getElementById('mid-rib');
    const outerRib = document.getElementById('outer-rib');
    const outerTip = document.getElementById('outer-tip');
    const stretcherEl = document.getElementById('stretcher');
    const slotLine = document.getElementById('slot-line');
    const slotPin = document.getElementById('slot-pin');
    const hinge1 = document.getElementById('hinge-1');
    const hinge2 = document.getElementById('hinge-2');
    const leftRibsGroup = document.getElementById('left-ribs');
    const canopyLeft = document.getElementById('canopy-mesh-left');
    const canopyRight = document.getElementById('canopy-mesh-right');
    const cableLeft = document.getElementById('cable-left');
    const cableRight = document.getElementById('cable-right');
    const angleArcOuter = document.getElementById('angle-arc-outer');
    const angleTextOuter = document.getElementById('angle-text-outer');
    const angleArcMid = document.getElementById('angle-arc-mid');
    const angleTextMid = document.getElementById('angle-text-mid');

    // HUD & 控件元素
    const hudPin = document.getElementById('hud-pin');
    const slotTravelText = document.getElementById('slot-travel-text');
    const hudNote = document.getElementById('hud-note');
    const btnPlayPause = document.getElementById('btn-play-pause');
    const playIcon = document.getElementById('play-icon');
    const slider = document.getElementById('progress-slider');
    const cyclePercent = document.getElementById('cycle-percent');
    const stageDesc = document.getElementById('stage-desc');

    let isPlaying = true;
    let progress = 0; // 0.0 (全开) -> 1.0 (完全紧密收拢)
    let lastTimestamp = null;
    let animDirection = 1; // 1: 收伞折叠, -1: 复位展开

    // 缓动函数
    function easeInOutCubic(x) {
      return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
    }

    /**
     * 根据折叠进程 t (0 ~ 1) 计算多杆铰链精确坐标
     */
    function computeKinematics(t) {
      // 1. 跑套位置:沿垂直中棒线性匀速下拉
      const ry = CONFIG.runnerOpenY + (CONFIG.runnerCloseY - CONFIG.runnerOpenY) * t;

      // 2. 内段主骨旋转角度 theta1
      // 开伞时与中轴夹角约 72°,收拢时贴近中棒约 7°
      const theta1_open = (72 * Math.PI) / 180;
      const theta1_closed = (7 * Math.PI) / 180;
      // 引入非线性延迟响应(前段受延迟槽影响倾角变化较慢)
      const t_mid = Math.max(0, (t - 0.22) / 0.78);
      const theta1 = theta1_open - (theta1_open - theta1_closed) * easeInOutCubic(t_mid);

      const h0 = { x: CONFIG.hub.x, y: CONFIG.hub.y };
      const h1 = {
        x: h0.x + CONFIG.L1 * Math.sin(theta1),
        y: h0.y + CONFIG.L1 * Math.cos(theta1)
      };

      // 3. 6mm 延时槽内的销轴位移 (0.0 到 1.0 归一化)
      // 在收伞前半段 (t: 0.05 ~ 0.38),撑杆销轴在槽内由顶端滑向底端硬限位
      let slotProgress = 0;
      if (t < 0.05) {
        slotProgress = 0;
      } else if (t <= 0.38) {
        slotProgress = (t - 0.05) / 0.33;
      } else {
        slotProgress = 1.0;
      }
      const slotTravelMm = (slotProgress * 6).toFixed(1);

      // 4. 中段骨主折角 theta2
      // 当延时槽走完限位后,撑杆带动中段向内弯折至 150°
      const midFoldAngleMax = (150 * Math.PI) / 180;
      const midFold = midFoldAngleMax * easeInOutCubic(Math.max(0, (t - 0.28) / 0.72));
      const theta2 = theta1 - midFold;

      const h2 = {
        x: h1.x + CONFIG.L2 * Math.sin(theta2),
        y: h1.y + CONFIG.L2 * Math.cos(theta2)
      };

      // 撑杆在中段长槽上的作用点
      const slotBaseDist = CONFIG.slotDistOnMid + CONFIG.slotStroke * slotProgress;
      const slotPinPos = {
        x: h1.x + slotBaseDist * Math.sin(theta2),
        y: h1.y + slotBaseDist * Math.cos(theta2)
      };

      // 5. 外段骨先折角 theta3:核心创新时序(先于中段在 t: 0.10 ~ 0.52 迅速回折 165°)
      const outerFoldMax = (165 * Math.PI) / 180;
      let outerT = 0;
      if (t > 0.06 && t < 0.58) {
        outerT = (t - 0.06) / 0.52;
      } else if (t >= 0.58) {
        outerT = 1.0;
      }
      const outerFold = outerFoldMax * easeInOutCubic(outerT);
      const theta3 = theta2 - outerFold;

      const tip = {
        x: h2.x + CONFIG.L3 * Math.sin(theta3),
        y: h2.y + CONFIG.L3 * Math.cos(theta3)
      };

      // 6. 柔性牵引索锚点与受拉内收轨迹
      // 牵引索连接内段中点、中段折痕和外伞缘,将伞布拉向中轴
      const cableMidAnchor = {
        x: (h1.x + h2.x) * 0.5 - 12 * (1 - t),
        y: (h1.y + h2.y) * 0.5
      };

      return {
        ry,
        h0,
        h1,
        h2,
        tip,
        slotPinPos,
        slotProgress,
        slotTravelMm,
        cableMidAnchor,
        theta1,
        theta2,
        theta3,
        outerFold
      };
    }

    /**
     * 渲染完整骨架系统
     */
    function renderFrame(p) {
      const state = computeKinematics(p);
      const cx = CONFIG.hub.x;

      // 跑套位置
      runnerEl.setAttribute('y', state.ry - 14);
      runnerHingeEl.setAttribute('cy', state.ry);
      runnerLabel.setAttribute('y', state.ry + 4);

      // 内段主骨
      innerRib.setAttribute('x1', state.h0.x);
      innerRib.setAttribute('y1', state.h0.y);
      innerRib.setAttribute('x2', state.h1.x);
      innerRib.setAttribute('y2', state.h1.y);

      innerRibCore.setAttribute('x1', state.h0.x);
      innerRibCore.setAttribute('y1', state.h0.y);
      innerRibCore.setAttribute('x2', state.h1.x);
      innerRibCore.setAttribute('y2', state.h1.y);

      // 撑杆
      stretcherEl.setAttribute('x1', cx);
      stretcherEl.setAttribute('y1', state.ry);
      stretcherEl.setAttribute('x2', state.slotPinPos.x);
      stretcherEl.setAttribute('y2', state.slotPinPos.y);

      // 中段主骨
      midRib.setAttribute('x1', state.h1.x);
      midRib.setAttribute('y1', state.h1.y);
      midRib.setAttribute('x2', state.h2.x);
      midRib.setAttribute('y2', state.h2.y);

      // 6mm 延时槽实体与滑动销轴
      const slotStart = {
        x: state.h1.x + CONFIG.slotDistOnMid * Math.sin(state.theta2),
        y: state.h1.y + CONFIG.slotDistOnMid * Math.cos(state.theta2)
      };
      const slotEnd = {
        x: state.h1.x + (CONFIG.slotDistOnMid + CONFIG.slotStroke) * Math.sin(state.theta2),
        y: state.h1.y + (CONFIG.slotDistOnMid + CONFIG.slotStroke) * Math.cos(state.theta2)
      };
      slotLine.setAttribute('x1', slotStart.x);
      slotLine.setAttribute('y1', slotStart.y);
      slotLine.setAttribute('x2', slotEnd.x);
      slotLine.setAttribute('y2', slotEnd.y);

      slotPin.setAttribute('cx', state.slotPinPos.x);
      slotPin.setAttribute('cy', state.slotPinPos.y);

      // 外段骨(橙色先折段)
      outerRib.setAttribute('x1', state.h2.x);
      outerRib.setAttribute('y1', state.h2.y);
      outerRib.setAttribute('x2', state.tip.x);
      outerRib.setAttribute('y2', state.tip.y);

      outerTip.setAttribute('cx', state.tip.x);
      outerTip.setAttribute('cy', state.tip.y);

      // 铰链节点更新
      hinge1.setAttribute('cx', state.h1.x);
      hinge1.setAttribute('cy', state.h1.y);
      hinge2.setAttribute('cx', state.h2.x);
      hinge2.setAttribute('cy', state.h2.y);

      // 镜像左侧骨架渲染
      const leftH1 = { x: 2 * cx - state.h1.x, y: state.h1.y };
      const leftH2 = { x: 2 * cx - state.h2.x, y: state.h2.y };
      const leftTip = { x: 2 * cx - state.tip.x, y: state.tip.y };
      const leftSlotPin = { x: 2 * cx - state.slotPinPos.x, y: state.slotPinPos.y };

      leftRibsGroup.innerHTML = `
        <line x1="${cx}" y1="${state.h0.y}" x2="${leftH1.x}" y2="${leftH1.y}" stroke="#94a3b8" stroke-width="4.5" stroke-linecap="round"/>
        <line x1="${cx}" y1="${state.ry}" x2="${leftSlotPin.x}" y2="${leftSlotPin.y}" stroke="#38bdf8" stroke-width="3" stroke-linecap="round"/>
        <line x1="${leftH1.x}" y1="${leftH1.y}" x2="${leftH2.x}" y2="${leftH2.y}" stroke="#38bdf8" stroke-width="4" stroke-linecap="round"/>
        <line x1="${leftH2.x}" y1="${leftH2.y}" x2="${leftTip.x}" y2="${leftTip.y}" stroke="#fb923c" stroke-width="3" stroke-linecap="round"/>
        <circle cx="${leftH1.x}" cy="${leftH1.y}" r="3.5" fill="#38bdf8"/>
        <circle cx="${leftH2.x}" cy="${leftH2.y}" r="3.5" fill="#fb923c"/>
        <circle cx="${leftTip.x}" cy="${leftTip.y}" r="2.5" fill="#fdba74"/>
      `;

      // 伞布半透明曲面:显示根据折痕紧致内收
      const clothCurveRight = `M ${cx} ${state.h0.y} Q ${(cx + state.h1.x) * 0.5 + 8 * (1 - p)} ${(state.h0.y + state.h1.y) * 0.5 - 6 * (1 - p)}, ${state.h1.x} ${state.h1.y} Q ${(state.h1.x + state.h2.x) * 0.5} ${(state.h1.y + state.h2.y) * 0.5 - 4 * (1 - p)}, ${state.h2.x} ${state.h2.y} L ${state.tip.x} ${state.tip.y} L ${cx + 10 * (1 - p)} ${state.ry + 20} Z`;
      canopyRight.setAttribute('d', clothCurveRight);

      const clothCurveLeft = `M ${cx} ${state.h0.y} Q ${(cx + leftH1.x) * 0.5 - 8 * (1 - p)} ${(state.h0.y + leftH1.y) * 0.5 - 6 * (1 - p)}, ${leftH1.x} ${leftH1.y} Q ${(leftH1.x + leftH2.x) * 0.5} ${(leftH1.y + leftH2.y) * 0.5 - 4 * (1 - p)}, ${leftH2.x} ${leftH2.y} L ${leftTip.x} ${leftTip.y} L ${cx - 10 * (1 - p)} ${state.ry + 20} Z`;
      canopyLeft.setAttribute('d', clothCurveLeft);

      // 弹性收束索(内侧牵引拉力流):体现把伞布拽向中棒,彻底杜绝夹布
      const cableRightPath = `M ${cx} ${state.ry - 40} Q ${state.cableMidAnchor.x} ${state.cableMidAnchor.y}, ${state.h2.x} ${state.h2.y}`;
      cableRight.setAttribute('d', cableRightPath);

      const cableLeftPath = `M ${cx} ${state.ry - 40} Q ${2 * cx - state.cableMidAnchor.x} ${state.cableMidAnchor.y}, ${leftH2.x} ${leftH2.y}`;
      cableLeft.setAttribute('d', cableLeftPath);

      // 局部标注与角度弧线
      if (p > 0.08 && p < 0.88) {
        // 外段先折弧线
        angleArcOuter.setAttribute('opacity', '1');
        angleTextOuter.setAttribute('opacity', '1');
        const arcR = 24;
        const arcX = state.h2.x + arcR * Math.sin(state.theta2 - 0.4);
        const arcY = state.h2.y + arcR * Math.cos(state.theta2 - 0.4);
        angleArcOuter.setAttribute('d', `M ${state.h2.x} ${state.h2.y - arcR} A ${arcR} ${arcR} 0 0 1 ${arcX} ${arcY}`);
        angleTextOuter.setAttribute('x', state.h2.x + 22);
        angleTextOuter.setAttribute('y', state.h2.y - 12);

        // 中段后折指示
        angleArcMid.setAttribute('opacity', '1');
        angleTextMid.setAttribute('opacity', '1');
        angleTextMid.setAttribute('x', state.h1.x + 18);
        angleTextMid.setAttribute('y', state.h1.y + 14);
      } else {
        angleArcOuter.setAttribute('opacity', '0');
        angleTextOuter.setAttribute('opacity', '0');
        angleArcMid.setAttribute('opacity', '0');
        angleTextMid.setAttribute('opacity', '0');
      }

      // 同步右上角 HUD 微观视窗
      const hudPinX = 45 + state.slotProgress * 40;
      hudPin.setAttribute('cx', hudPinX);
      slotTravelText.textContent = `槽位移: ${state.slotTravelMm}mm`;

      // 状态解说与文字精简更新
      updateStateFeedback(p, state);
    }

    /**
     * 阶段语义描述(精准对应 TRIZ 时序解决动作)
     */
    function updateStateFeedback(p, state) {
      slider.value = p;
      cyclePercent.textContent = `${Math.round(p * 100)}%`;

      if (p < 0.15) {
        stageDesc.textContent = '【状态 1】全开受力态:双巢顶紧,伞面弧度丰盈';
        hudNote.textContent = '跑套初始下滑:撑杆销轴于 6mm 长槽内自由滑移';
      } else if (p < 0.42) {
        stageDesc.textContent = '【状态 2】外段骨率先折入 165°:避让外缘干涉';
        hudNote.textContent = `6mm 延迟槽滑移中 (${state.slotTravelMm}mm),中段保持硬限位未折`;
      } else if (p < 0.72) {
        stageDesc.textContent = '【状态 3】中段后折 150° + 弹性索 2.5N 张力主动内收';
        hudNote.textContent = '槽行程到达 6mm 机械止挡,力矩传导带动中段折拢';
      } else if (p < 0.94) {
        stageDesc.textContent = '【状态 4】平顺聚拢:骨架紧贴中棒,零随机夹布';
        hudNote.textContent = '伞布沿折痕规整内缩,收束环导入紧凑束身';
      } else {
        stageDesc.textContent = '【状态 5】极限理想解:最小收合包络完成';
        hudNote.textContent = '完成一阶循环,即将进行反向开伞动作演示';
      }
    }

    /**
     * 主动画渲染循环 (基于 requestAnimationFrame)
     */
    function animateLoop(timestamp) {
      if (!lastTimestamp) lastTimestamp = timestamp;
      const deltaTime = timestamp - lastTimestamp;
      lastTimestamp = timestamp;

      if (isPlaying) {
        // 增量计算
        const deltaProgress = (deltaTime / CONFIG.cycleDuration) * animDirection;
        progress += deltaProgress;

        // 到达双向极值后稍作停顿并往复
        if (progress >= 1.0) {
          progress = 1.0;
          animDirection = -1;
        } else if (progress <= 0.0) {
          progress = 0.0;
          animDirection = 1;
        }

        renderFrame(progress);
      }

      requestAnimationFrame(animateLoop);
    }

    // 交互绑定:播放/暂停
    btnPlayPause.addEventListener('click', () => {
      isPlaying = !isPlaying;
      playIcon.innerHTML = isPlaying
        ? '<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>' // 暂停图标
        : '<path d="M8 5v14l11-7z"/>';                 // 播放图标
    });

    // 交互绑定:进度滑块拖拽
    slider.addEventListener('input', (e) => {
      isPlaying = false;
      playIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
      progress = parseFloat(e.target.value);
      renderFrame(progress);
    });

    // 页面加载完成后自动启动播放
    window.addEventListener('DOMContentLoaded', () => {
      renderFrame(0);
      requestAnimationFrame(animateLoop);
    });
  </script>
</body>
</html>
积分规则:每轮对话扣10分