分享图
动画工坊
引擎就绪

这是为您生成的一个展示“行星翻转轮组”越障原理的高保真 SVG 动画网页源码,您可以直接在浏览器中运行和观看。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>行星翻转轮组 - 越障原理动画 | TRIZ 最终理想解</title>
    <style>
        :root {
            --bg: #1a1b20;
            --surface: #23242a;
            --text: #d4d5db;
            --text-secondary: #9597a2;
            --accent: #f0a54a;
            --accent-glow: #f0a54a88;
            --highlight: #ffcc66;
            --step: #4e5060;
            --step-edge: #7a7d90;
            --ground: #3a3b44;
            --metal: #8892a0;
            --metal-dark: #5a5f6b;
            --rubber: #2c2d35;
            --cargo: #c9a96e;
            --cargo-dark: #8b6f3f;
            --danger-zone: #e0554a33;
            --success-zone: #4ecf8f22;
            --trajectory: #f0a54a66;
            --pivot-glow: #ffcc66;
            --font-display: 'Georgia', 'Noto Serif SC', 'STSong', 'SimSun', 'Songti SC', serif;
            --font-body: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', sans-serif;
        }

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

        body {
            background: var(--bg);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            font-family: var(--font-body);
            overflow-x: hidden;
            -webkit-font-smoothing: antialiased;
        }

        .animation-wrapper {
            position: relative;
            width: 95vw;
            max-width: 1300px;
            aspect-ratio: 16/7.5;
            min-height: 520px;
            background: radial-gradient(ellipse at 50% 40%, #24252d 0%, #18191e 70%);
            border-radius: 18px;
            overflow: hidden;
            box-shadow:
                0 2px 30px rgba(0, 0, 0, 0.5),
                0 1px 0 rgba(255, 255, 255, 0.03) inset,
                0 0 0 1px rgba(255, 255, 255, 0.05);
            cursor: default;
        }

        .animation-wrapper svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        .overlay-labels {
            position: absolute;
            pointer-events: none;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 10;
        }

        .label-tag {
            position: absolute;
            font-family: var(--font-body);
            font-size: 11px;
            font-weight: 500;
            letter-spacing: 0.03em;
            color: var(--text-secondary);
            background: rgba(26, 27, 32, 0.75);
            padding: 4px 10px;
            border-radius: 12px;
            border: 1px solid rgba(255, 255, 255, 0.08);
            white-space: nowrap;
            backdrop-filter: blur(4px);
            -webkit-backdrop-filter: blur(4px);
            transition: color 0.6s, border-color 0.6s, background 0.6s;
        }
        .label-tag.highlight-active {
            color: #f0cc7a;
            border-color: rgba(240, 165, 74, 0.5);
            background: rgba(30, 28, 22, 0.8);
        }

        .label-top-left {
            top: 14px;
            left: 16px;
        }
        .label-top-right {
            top: 14px;
            right: 16px;
        }
        .label-bottom-left {
            bottom: 14px;
            left: 16px;
        }
        .label-bottom-right {
            bottom: 14px;
            right: 16px;
        }
        .label-center-bottom {
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 10px;
            letter-spacing: 0.05em;
        }

        .phase-indicator {
            position: absolute;
            bottom: 40px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 6px;
            z-index: 11;
            pointer-events: none;
        }
        .phase-dot {
            width: 7px;
            height: 7px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.2);
            transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
        }
        .phase-dot.active {
            background: var(--accent);
            box-shadow: 0 0 10px var(--accent-glow);
            width: 9px;
            height: 9px;
            margin-top: -1px;
        }
        .phase-dot.done {
            background: rgba(240, 165, 74, 0.5);
        }

        .speed-control {
            position: absolute;
            bottom: 16px;
            right: 80px;
            z-index: 20;
            display: flex;
            align-items: center;
            gap: 8px;
            pointer-events: all;
            opacity: 0.7;
            transition: opacity 0.3s;
        }
        .speed-control:hover {
            opacity: 1;
        }
        .speed-control label {
            font-size: 10px;
            color: var(--text-secondary);
            font-family: var(--font-body);
            letter-spacing: 0.04em;
        }
        .speed-control input[type="range"] {
            -webkit-appearance: none;
            width: 90px;
            height: 4px;
            border-radius: 2px;
            background: rgba(255, 255, 255, 0.2);
            outline: none;
            cursor: pointer;
        }
        .speed-control input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 16px;
            height: 16px;
            border-radius: 50%;
            background: var(--accent);
            cursor: pointer;
            border: 2px solid var(--bg);
            box-shadow: 0 0 8px rgba(240, 165, 74, 0.4);
        }

        @keyframes subtlePulse {
            0%,
            100% {
                opacity: 0.6;
            }
            50% {
                opacity: 1;
            }
        }

        .pulse-active {
            animation: subtlePulse 1.6s ease-in-out infinite;
        }
    </style>
</head>
<body>

    <div class="animation-wrapper" id="animContainer">
        <svg id="mainSvg" viewBox="0 0 1400 560" xmlns="http://www.w3.org/2000/svg">
            <defs>
                <!-- 背景网格图案 -->
                <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                    <path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.03)" stroke-width="0.5"/>
                </pattern>
                <!-- 发光滤镜 -->
                <filter id="glowPivot" x="-80%" y="-80%" width="260%" height="260%">
                    <feGaussianBlur stdDeviation="5" result="blur"/>
                    <feMerge>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>
                <filter id="glowSoft" x="-40%" y="-40%" width="180%" height="180%">
                    <feGaussianBlur stdDeviation="3" result="blur"/>
                    <feMerge>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>
                <!-- 渐变 -->
                <linearGradient id="gradGround" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#4a4b58"/>
                    <stop offset="100%" stop-color="#2e2f38"/>
                </linearGradient>
                <linearGradient id="gradStepFace" x1="0" y1="0" x2="1" y2="0">
                    <stop offset="0%" stop-color="#5c5e6e"/>
                    <stop offset="100%" stop-color="#3e404d"/>
                </linearGradient>
                <linearGradient id="gradStepTop" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#6a6d7c"/>
                    <stop offset="100%" stop-color="#4e505e"/>
                </linearGradient>
                <linearGradient id="gradMetal" x1="0" y1="0" x2="1" y2="1">
                    <stop offset="0%" stop-color="#9aa3b0"/>
                    <stop offset="50%" stop-color="#6e7684"/>
                    <stop offset="100%" stop-color="#8890a0"/>
                </linearGradient>
                <linearGradient id="gradCargo" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#dbb878"/>
                    <stop offset="100%" stop-color="#9e7d44"/>
                </linearGradient>
                <radialGradient id="gradPivotGlow" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#ffd166" stop-opacity="0.9"/>
                    <stop offset="40%" stop-color="#f0a54a" stop-opacity="0.4"/>
                    <stop offset="100%" stop-color="#f0a54a" stop-opacity="0"/>
                </radialGradient>
                <radialGradient id="gradContactFlash" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#ffffff" stop-opacity="0.8"/>
                    <stop offset="30%" stop-color="#ffcc66" stop-opacity="0.5"/>
                    <stop offset="100%" stop-color="#ff8800" stop-opacity="0"/>
                </radialGradient>
            </defs>

            <!-- 背景层 -->
            <rect x="0" y="0" width="1400" height="560" fill="url(#grid)" opacity="0.6"/>

            <!-- 低处地面 -->
            <rect id="groundLow" x="0" y="445" width="740" height="115" fill="url(#gradGround)"/>
            <line x1="0" y1="445" x2="740" y2="445" stroke="#7a7d90" stroke-width="2"/>

            <!-- 台阶立面 -->
            <rect id="stepFace" x="735" y="340" width="20" height="105" fill="url(#gradStepFace)" rx="2"/>
            <!-- 台阶顶部边缘高亮 -->
            <line x1="733" y1="342" x2="757" y2="342" stroke="#b0b4c0" stroke-width="2.5" stroke-linecap="round"/>

            <!-- 台阶顶部地面 -->
            <rect id="groundHigh" x="750" y="340" width="650" height="220" fill="url(#gradStepTop)"/>
            <line x1="750" y1="340" x2="1400" y2="340" stroke="#8a8d9a" stroke-width="2"/>

            <!-- 台阶高度标注虚线 -->
            <line x1="770" y1="340" x2="770" y2="445" stroke="rgba(255,255,255,0.18)" stroke-width="1" stroke-dasharray="5,5"/>
            <line x1="765" y1="340" x2="775" y2="340" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
            <line x1="765" y1="445" x2="775" y2="445" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>

            <!-- 台阶高度标签 -->
            <text x="778" y="396" fill="rgba(255,255,255,0.35)" font-size="11" font-family="var(--font-body)">台阶</text>
            <text x="778" y="410" fill="rgba(255,255,255,0.35)" font-size="11" font-family="var(--font-body)">~100mm</text>

            <!-- 支点发光指示器(在翻转阶段显示) -->
            <circle id="pivotGlow" cx="0" cy="0" r="22" fill="url(#gradPivotGlow)" filter="url(#glowPivot)" opacity="0" pointer-events="none"/>
            <circle id="pivotGlowInner" cx="0" cy="0" r="8" fill="#ffd166" opacity="0" pointer-events="none" filter="url(#glowSoft)"/>

            <!-- 翻转轨迹弧线(虚线) -->
            <path id="flipTrajectory" d="" fill="none" stroke="#f0a54a" stroke-width="2" stroke-dasharray="8,4" opacity="0" pointer-events="none" filter="url(#glowSoft)"/>

            <!-- 接触闪光 -->
            <circle id="contactFlash" cx="0" cy="0" r="14" fill="url(#gradContactFlash)" opacity="0" pointer-events="none"/>

            <!-- === 动态元素组 === -->
            <!-- 后轮组 -->
            <g id="rearHubGroup">
                <!-- 旋转臂 -->
                <g id="rearArms">
                    <line x1="0" y1="0" x2="0" y2="-115" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
                    <line x1="0" y1="0" x2="99.6" y2="57.5" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
                    <line x1="0" y1="0" x2="-99.6" y2="57.5" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
                </g>
                <!-- 中心轴 -->
                <circle cx="0" cy="0" r="9" fill="#6e7684" stroke="#9aa3b0" stroke-width="2"/>
                <circle cx="0" cy="0" r="4" fill="#3a3d45"/>
                <!-- 三个小轮 -->
                <circle cx="0" cy="-115" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-rear"/>
                <circle cx="99.6" cy="57.5" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-rear"/>
                <circle cx="-99.6" cy="57.5" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-rear"/>
                <!-- 小轮轮毂标记 -->
                <circle cx="0" cy="-115" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
                <circle cx="99.6" cy="57.5" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
                <circle cx="-99.6" cy="57.5" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
            </g>

            <!-- 前轮组 -->
            <g id="frontHubGroup">
                <g id="frontArms">
                    <line x1="0" y1="0" x2="0" y2="-115" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
                    <line x1="0" y1="0" x2="99.6" y2="57.5" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
                    <line x1="0" y1="0" x2="-99.6" y2="57.5" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
                </g>
                <circle cx="0" cy="0" r="9" fill="#6e7684" stroke="#9aa3b0" stroke-width="2"/>
                <circle cx="0" cy="0" r="4" fill="#3a3d45"/>
                <circle cx="0" cy="-115" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-front"/>
                <circle cx="99.6" cy="57.5" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-front"/>
                <circle cx="-99.6" cy="57.5" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-front"/>
                <circle cx="0" cy="-115" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
                <circle cx="99.6" cy="57.5" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
                <circle cx="-99.6" cy="57.5" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
            </g>

            <!-- 底盘主梁 -->
            <g id="chassisGroup">
                <rect x="-175" y="-14" width="350" height="10" rx="5" fill="#5a5f6b" stroke="#8892a0" stroke-width="1.5"/>
                <!-- 阻尼铰链标记 -->
                <circle cx="0" cy="-9" r="6" fill="#3a3d45" stroke="#8892a0" stroke-width="1.5"/>
                <line x1="-3" y1="-9" x2="3" y2="-9" stroke="#f0a54a" stroke-width="2" stroke-linecap="round"/>
            </g>

            <!-- 货箱 -->
            <g id="cargoGroup">
                <rect x="-80" y="-75" width="160" height="58" rx="6" fill="url(#gradCargo)" stroke="#7a6238" stroke-width="2"/>
                <!-- 货箱内部纹理 -->
                <line x1="-55" y1="-55" x2="55" y2="-55" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
                <line x1="-55" y1="-38" x2="55" y2="-38" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
                <!-- 货箱标签 -->
                <text x="0" y="-20" text-anchor="middle" fill="rgba(255,255,255,0.5)" font-size="10" font-family="var(--font-body)" letter-spacing="0.06em">货箱</text>
                <!-- 阻尼铰链连接线 -->
                <line x1="0" y1="-17" x2="0" y2="0" stroke="#c9a96e" stroke-width="2" stroke-dasharray="3,3" opacity="0.6"/>
            </g>

            <!-- 翻转阶段高亮遮罩 -->
            <rect id="highlightOverlay" x="0" y="0" width="1400" height="560" fill="none" pointer-events="none" stroke="none"/>

            <!-- 地面接触指示点 -->
            <circle id="rearContactDot" cx="0" cy="0" r="5" fill="#4ecf8f" opacity="0.6" pointer-events="none" filter="url(#glowSoft)"/>
            <circle id="frontContactDot" cx="0" cy="0" r="5" fill="#4ecf8f" opacity="0.6" pointer-events="none" filter="url(#glowSoft)"/>
        </svg>

        <!-- 覆盖层标签 -->
        <div class="overlay-labels">
            <div class="label-tag label-top-left" id="tagTitle">🌟 TRIZ 最终理想解 · 行星翻转轮组</div>
            <div class="label-tag label-top-right" id="tagPhase">阶段:平路滚动</div>
            <div class="label-tag label-bottom-left" id="tagInnovation">💡 核心创新:台阶从"障碍"变为"支点"</div>
            <div class="label-tag label-bottom-right" id="tagDetail">回转半径 115mm · 小轮 Φ80mm</div>
        </div>

        <!-- 阶段指示点 -->
        <div class="phase-indicator" id="phaseDots">
            <div class="phase-dot active" title="平路滚动"></div>
            <div class="phase-dot" title="遇障"></div>
            <div class="phase-dot" title="翻转"></div>
            <div class="phase-dot" title="登台"></div>
            <div class="phase-dot" title="完成"></div>
        </div>

        <!-- 速度控制 -->
        <div class="speed-control">
            <label for="speedSlider">⏱ 速度</label>
            <input type="range" id="speedSlider" min="0.3" max="2.5" step="0.1" value="1.0" title="调节动画播放速度">
            <label id="speedLabel" style="min-width:28px;text-align:center;">1×</label>
        </div>
    </div>

    <script>
        (function() {
            // ============ DOM 引用 ============
            const svg = document.getElementById('mainSvg');
            const rearHubGroup = document.getElementById('rearHubGroup');
            const frontHubGroup = document.getElementById('frontHubGroup');
            const chassisGroup = document.getElementById('chassisGroup');
            const cargoGroup = document.getElementById('cargoGroup');
            const pivotGlow = document.getElementById('pivotGlow');
            const pivotGlowInner = document.getElementById('pivotGlowInner');
            const flipTrajectory = document.getElementById('flipTrajectory');
            const contactFlash = document.getElementById('contactFlash');
            const rearContactDot = document.getElementById('rearContactDot');
            const frontContactDot = document.getElementById('frontContactDot');
            const tagPhase = document.getElementById('tagPhase');
            const tagInnovation = document.getElementById('tagInnovation');
            const phaseDots = document.querySelectorAll('.phase-dot');
            const speedSlider = document.getElementById('speedSlider');
            const speedLabel = document.getElementById('speedLabel');

            // ============ 常量与参数 ============
            const GROUND_LOW_Y = 445; // 低地面Y坐标
            const GROUND_HIGH_Y = 340; // 台阶顶部Y坐标
            const STEP_X = 745; // 台阶立面X坐标
            const STEP_HEIGHT = GROUND_LOW_Y - GROUND_HIGH_Y; // 105
            const WHEEL_RADIUS = 40; // 小轮半径
            const ARM_RADIUS = 115; // 回转半径
            const HUB_GROUND_OFFSET = ARM_RADIUS - WHEEL_RADIUS + 5; // 轮组中心距地面高度(平路) ≈80
            const CHASSIS_LENGTH = 350; // 前后轮组间距
            const TOTAL_DURATION = 11.0; // 动画总时长(秒)

            // 小轮在轮组中的本地坐标(120°分布,0°在正上方)
            const wheelLocalAngles = [-90, 30, 150]; // 角度(度),-90=正上方
            const wheelLocalPositions = wheelLocalAngles.map(a => ({
                x: ARM_RADIUS * Math.cos(a * Math.PI / 180),
                y: ARM_RADIUS * Math.sin(a * Math.PI / 180)
            }));

            // ============ 动画状态 ============
            let animTime = 0;
            let speedMultiplier = 1.0;
            let lastFrameTime = null;
            let animationId = null;
            let isRunning = true;
            let currentPhaseIndex = 0;

            // ============ 阶段定义 ============
            const phaseNames = ['平路滚动', '前轮遇障', '翻转越障', '前轮登台', '后轮跟进', '整车登台', '平稳驶离'];
            const phaseTimeRanges = [
                { start: 0, end: 2.2, name: '平路滚动', dotIndex: 0 },
                { start: 2.2, end: 3.2, name: '前轮遇障', dotIndex: 1 },
                { start: 3.2, end: 5.2, name: '翻转越障', dotIndex: 2 },
                { start: 5.2, end: 6.8, name: '前轮登台', dotIndex: 3 },
                { start: 6.8, end: 8.2, name: '后轮翻转', dotIndex: 3 },
                { start: 8.2, end: 9.6, name: '整车登台', dotIndex: 4 },
                { start: 9.6, end: TOTAL_DURATION, name: '平稳驶离', dotIndex: 4 },
            ];

            function getPhaseInfo(t) {
                for (const p of phaseTimeRanges) {
                    if (t >= p.start && t < p.end) return p;
                }
                return phaseTimeRanges[0];
            }

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

            function easeOutBack(t) { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math
                    .pow(t - 1, 2); }

            function easeOutElastic(t) { if (t === 0 || t === 1) return t; return Math.pow(2, -10 * t) * Math.sin((t - 1) *
                    3.5 * Math.PI) + 1; }

            function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }

            function lerp(a, b, t) { return a + (b - a) * Math.max(0, Math.min(1, t)); }

            function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }

            // ============ 动画计算核心 ============
            function computeAnimationState(t) {
                // t: 0 到 TOTAL_DURATION
                const tNorm = t / TOTAL_DURATION;

                // 全局X位移:小车整体从左向右移动
                // 阶段0-1:平路接近 (0 → 2.2s),移动约280px
                // 阶段2:遇障减速 (2.2 → 3.2s),移动约30px
                // 阶段3:翻转 (3.2 → 5.2s),前轮组翻转,底盘前进约60px
                // 阶段4:前轮登台过渡 (5.2 → 6.8s),前进约80px
                // 阶段5:后轮翻转 (6.8 → 8.2s),前进约50px
                // 阶段6:整车登台 (8.2 → 9.6s),前进约100px
                // 阶段7:驶离 (9.6 → 11s),前进约120px

                let globalX = 160; // 初始X位置(底盘参考点)
                let chassisTilt = 0; // 底盘倾斜角度(度)
                let cargoTilt = 0; // 货箱倾斜角度(度,相对于水平面)
                let frontHubRotation = 0; // 前轮组自转角度(度)
                let rearHubRotation = 0; // 后轮组自转角度(度)
                let frontHubLocalX = 0; // 前轮组相对于底盘中心的X偏移
                let frontHubLocalY = 0; // 前轮组相对于底盘中心的Y偏移
                let rearHubLocalX = 0;
                let rearHubLocalY = 0;
                let pivotX = 0; // 翻转支点X(全局坐标)
                let pivotY = 0; // 翻转支点Y
                let showPivotGlow = false;
                let pivotGlowAlpha = 0;
                let showTrajectory = false;
                let trajectoryAlpha = 0;
                let contactFlashAlpha = 0;
                let contactFlashX = 0;
                let contactFlashY = 0;

                const halfChassis = CHASSIS_LENGTH / 2;

                if (t < 2.2) {
                    // === 阶段0:平路滚动 ===
                    const phaseT = t / 2.2;
                    const easedT = easeInOutQuad(phaseT);
                    globalX = 160 + easedT * 300;
                    chassisTilt = 0;
                    cargoTilt = 0;
                    frontHubRotation = easedT * 360 * 1.3;
                    rearHubRotation = easedT * 360 * 1.3;
                    frontHubLocalX = halfChassis;
                    frontHubLocalY = HUB_GROUND_OFFSET;
                    rearHubLocalX = -halfChassis;
                    rearHubLocalY = HUB_GROUND_OFFSET;

                } else if (t < 3.2) {
                    // === 阶段1:前轮遇障(减速,前轮接触台阶立面) ===
                    const phaseT = (t - 2.2) / 1.0;
                    const easedT = easeInOutCubic(phaseT);
                    globalX = 460 + easedT * 35;
                    chassisTilt = easedT * 3;
                    cargoTilt = -chassisTilt * 0.3;
                    frontHubRotation += (t - 2.2) / (3.2 - 2.2) * 80;
                    rearHubRotation += (t - 2.2) / (3.2 - 2.2) * 100;
                    frontHubLocalX = halfChassis;
                    frontHubLocalY = HUB_GROUND_OFFSET - easedT * 15;
                    rearHubLocalX = -halfChassis;
                    rearHubLocalY = HUB_GROUND_OFFSET;
                    // 接触点
                    contactFlashAlpha = easedT > 0.6 ? (easedT - 0.6) / 0.4 : 0;
                    contactFlashX = globalX + halfChassis;
                    contactFlashY = GROUND_LOW_Y - WHEEL_RADIUS;
                    showPivotGlow = easedT > 0.7;
                    pivotGlowAlpha = easedT > 0.7 ? (easedT - 0.7) / 0.3 * 0.6 : 0;
                    pivotX = contactFlashX;
                    pivotY = contactFlashY;

                } else if (t < 5.2) {
                    // === 阶段2:翻转越障(核心动作) ===
                    const phaseT = (t - 3.2) / 2.0;
                    const easedT = easeOutBack(clamp(phaseT, 0, 1));
                    globalX = 495 + easedT * 70;
                    chassisTilt = 3 + easedT * 14;
                    cargoTilt = -chassisTilt * 0.25;
                    const flipAngle = easedT * 120;
                    frontHubRotation += (t - 3.2) / (5.2 - 3.2) * 200;
                    rearHubRotation += (t - 3.2) / (5.2 - 3.2) * 130;
                    frontHubLocalX = halfChassis - easedT * 25;
                    frontHubLocalY = HUB_GROUND_OFFSET - 15 - easedT * (STEP_HEIGHT + 20);
                    rearHubLocalX = -halfChassis + easedT * 10;
                    rearHubLocalY = HUB_GROUND_OFFSET + easedT * 8;
                    pivotX = 495 + halfChassis;
                    pivotY = GROUND_LOW_Y - WHEEL_RADIUS;
                    showPivotGlow = true;
                    pivotGlowAlpha = 0.5 + easedT * 0.4;
                    showTrajectory = true;
                    trajectoryAlpha = 0.35 + easedT * 0.45;
                    contactFlashAlpha = 0.7 - easedT * 0.5;
                    contactFlashX = pivotX;
                    contactFlashY = pivotY;

                } else if (t < 6.8) {
                    // === 阶段3:前轮登台,过渡 ===
                    const phaseT = (t - 5.2) / 1.6;
                    const easedT = easeInOutCubic(phaseT);
                    globalX = 565 + easedT * 90;
                    chassisTilt = 17 - easedT * 8;
                    cargoTilt = -chassisTilt * 0.25;
                    frontHubRotation += (t - 5.2) / (6.8 - 5.2) * 150;
                    rearHubRotation += (t - 5.2) / (6.8 - 5.2) * 160;
                    frontHubLocalX = halfChassis - 25 + easedT * 25;
                    frontHubLocalY = HUB_GROUND_OFFSET - 15 - STEP_HEIGHT - 20 + easedT * (STEP_HEIGHT + 15);
                    rearHubLocalX = -halfChassis + 10 + easedT * 20;
                    rearHubLocalY = HUB_GROUND_OFFSET + 8 - easedT * 2;
                    pivotGlowAlpha = 0.9 - easedT * 0.6;
                    showPivotGlow = pivotGlowAlpha > 0.05;
                    trajectoryAlpha = 0.8 - easedT * 0.6;
                    showTrajectory = trajectoryAlpha > 0.05;
                    contactFlashAlpha = 0.2 - easedT * 0.2;

                } else if (t < 8.2) {
                    // === 阶段4:后轮翻转 ===
                    const phaseT = (t - 6.8) / 1.4;
                    const easedT = easeOutBack(clamp(phaseT, 0, 1));
                    globalX = 655 + easedT * 55;
                    chassisTilt = 9 - easedT * 7;
                    cargoTilt = -chassisTilt * 0.22;
                    frontHubRotation += (t - 6.8) / (8.2 - 6.8) * 100;
                    rearHubRotation += (t - 6.8) / (8.2 - 6.8) * 210;
                    frontHubLocalX = halfChassis;
                    frontHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT + 5;
                    rearHubLocalX = -halfChassis + 30 - easedT * 30;
                    rearHubLocalY = HUB_GROUND_OFFSET + 6 - easedT * (STEP_HEIGHT + 25);
                    pivotX = globalX - halfChassis + 30;
                    pivotY = GROUND_LOW_Y - WHEEL_RADIUS;
                    showPivotGlow = easedT > 0.25 && easedT < 0.9;
                    pivotGlowAlpha = (easedT > 0.25 && easedT < 0.9) ? 0.55 : 0;
                    showTrajectory = easedT > 0.2 && easedT < 0.85;
                    trajectoryAlpha = showTrajectory ? 0.4 : 0;
                    contactFlashAlpha = (easedT > 0.3 && easedT < 0.7) ? 0.5 : 0;
                    contactFlashX = pivotX;
                    contactFlashY = pivotY;

                } else if (t < 9.6) {
                    // === 阶段5:整车登台 ===
                    const phaseT = (t - 8.2) / 1.4;
                    const easedT = easeInOutCubic(phaseT);
                    globalX = 710 + easedT * 110;
                    chassisTilt = 2 - easedT * 1.5;
                    cargoTilt = -chassisTilt * 0.2;
                    frontHubRotation += (t - 8.2) / (9.6 - 8.2) * 80;
                    rearHubRotation += (t - 8.2) / (9.6 - 8.2) * 80;
                    frontHubLocalX = halfChassis;
                    frontHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT + 5;
                    rearHubLocalX = -halfChassis;
                    rearHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT + 3;
                    pivotGlowAlpha = Math.max(0, pivotGlowAlpha - 0.03);
                    showPivotGlow = pivotGlowAlpha > 0.02;
                    trajectoryAlpha = Math.max(0, trajectoryAlpha - 0.03);
                    showTrajectory = trajectoryAlpha > 0.02;
                    contactFlashAlpha = Math.max(0, contactFlashAlpha - 0.04);

                } else {
                    // === 阶段6:平稳驶离 ===
                    const phaseT = (t - 9.6) / (TOTAL_DURATION - 9.6);
                    const easedT = easeInOutQuad(phaseT);
                    globalX = 820 + easedT * 160;
                    chassisTilt = 0.5 - easedT * 0.5;
                    cargoTilt = 0;
                    frontHubRotation += (t - 9.6) / (TOTAL_DURATION - 9.6) * 60;
                    rearHubRotation += (t - 9.6) / (TOTAL_DURATION - 9.6) * 60;
                    frontHubLocalX = halfChassis;
                    frontHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT;
                    rearHubLocalX = -halfChassis;
                    rearHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT;
                    pivotGlowAlpha = 0;
                    showPivotGlow = false;
                    trajectoryAlpha = 0;
                    showTrajectory = false;
                    contactFlashAlpha = 0;
                }

                return {
                    globalX,
                    chassisTilt,
                    cargoTilt,
                    frontHubRotation,
                    rearHubRotation,
                    frontHubLocalX,
                    frontHubLocalY,
                    rearHubLocalX,
                    rearHubLocalY,
                    pivotX,
                    pivotY,
                    showPivotGlow,
                    pivotGlowAlpha,
                    showTrajectory,
                    trajectoryAlpha,
                    contactFlashAlpha,
                    contactFlashX,
                    contactFlashY,
                };
            }

            // ============ 更新SVG元素 ============
            function updateSVG(state) {
                const cx = state.globalX;
                const cy = GROUND_LOW_Y - HUB_GROUND_OFFSET;

                // 底盘组:位于前后轮组中间
                const chassisCX = cx;
                const chassisCY = cy - (state.frontHubLocalY + state.rearHubLocalY) / 2 + HUB_GROUND_OFFSET;
                chassisGroup.setAttribute('transform',
                    `translate(${chassisCX}, ${chassisCY}) rotate(${state.chassisTilt})`);

                // 前轮组
                const fhx = cx + state.frontHubLocalX;
                const fhy = cy - HUB_GROUND_OFFSET + state.frontHubLocalY;
                frontHubGroup.setAttribute('transform',
                    `translate(${fhx}, ${fhy}) rotate(${state.frontHubRotation})`);

                // 后轮组
                const rhx = cx + state.rearHubLocalX;
                const rhy = cy - HUB_GROUND_OFFSET + state.rearHubLocalY;
                rearHubGroup.setAttribute('transform',
                    `translate(${rhx}, ${rhy}) rotate(${state.rearHubRotation})`);

                // 货箱(相对于底盘,绕阻尼铰链旋转)
                cargoGroup.setAttribute('transform', `rotate(${state.cargoTilt})`);

                // 支点发光
                if (state.showPivotGlow && state.pivotGlowAlpha > 0.01) {
                    pivotGlow.setAttribute('cx', state.pivotX);
                    pivotGlow.setAttribute('cy', state.pivotY);
                    pivotGlow.setAttribute('opacity', state.pivotGlowAlpha);
                    pivotGlowInner.setAttribute('cx', state.pivotX);
                    pivotGlowInner.setAttribute('cy', state.pivotY);
                    pivotGlowInner.setAttribute('opacity', state.pivotGlowAlpha * 0.8);
                } else {
                    pivotGlow.setAttribute('opacity', '0');
                    pivotGlowInner.setAttribute('opacity', '0');
                }

                // 翻转轨迹
                if (state.showTrajectory && state.trajectoryAlpha > 0.01) {
                    flipTrajectory.setAttribute('opacity', state.trajectoryAlpha);
                    // 更新轨迹路径(从前轮组翻转支点画弧线到台阶顶部)
                    const arcStartX = state.pivotX;
                    const arcStartY = state.pivotY;
                    const arcEndX = state.pivotX + 30;
                    const arcEndY = GROUND_HIGH_Y - WHEEL_RADIUS;
                    const arcMidX = (arcStartX + arcEndX) / 2 + 40;
                    const arcMidY = Math.min(arcStartY, arcEndY) - 55;
                    flipTrajectory.setAttribute('d',
                        `M ${arcStartX} ${arcStartY} Q ${arcMidX} ${arcMidY} ${arcEndX} ${arcEndY}`);
                } else {
                    flipTrajectory.setAttribute('opacity', '0');
                }

                // 接触闪光
                if (state.contactFlashAlpha > 0.01) {
                    contactFlash.setAttribute('cx', state.contactFlashX);
                    contactFlash.setAttribute('cy', state.contactFlashY);
                    contactFlash.setAttribute('opacity', state.contactFlashAlpha);
                    contactFlash.setAttribute('r', 10 + state.contactFlashAlpha * 8);
                } else {
                    contactFlash.setAttribute('opacity', '0');
                }

                // 地面接触点
                const fContactX = fhx;
                const fContactY = GROUND_LOW_Y;
                const rContactX = rhx;
                const rContactY = GROUND_LOW_Y;
                const onHighGround = state.globalX > STEP_X + 80;
                if (onHighGround && state.chassisTilt < 2) {
                    frontContactDot.setAttribute('cx', fContactX);
                    frontContactDot.setAttribute('cy', GROUND_HIGH_Y);
                    rearContactDot.setAttribute('cx', rContactX);
                    rearContactDot.setAttribute('cy', GROUND_HIGH_Y);
                } else {
                    frontContactDot.setAttribute('cx', fContactX);
                    frontContactDot.setAttribute('cy', Math.min(fContactY, GROUND_HIGH_Y + 50));
                    rearContactDot.setAttribute('cx', rContactX);
                    rearContactDot.setAttribute('cy', Math.min(rContactY, GROUND_HIGH_Y + 50));
                }
            }

            // ============ 更新UI覆盖层 ============
            function updateUI(t) {
                const phaseInfo = getPhaseInfo(t);
                if (phaseInfo && phaseInfo.dotIndex !== currentPhaseIndex) {
                    currentPhaseIndex = phaseInfo.dotIndex;
                    tagPhase.textContent = '阶段:' + phaseInfo.name;
                    // 更新阶段指示点
                    phaseDots.forEach((dot, i) => {
                        dot.classList.remove('active', 'done');
                        if (i < currentPhaseIndex) dot.classList.add('done');
                        if (i === currentPhaseIndex) dot.classList.add('active');
                    });
                }
                // 动态更新创新标签
                if (t > 2.8 && t < 5.5) {
                    tagInnovation.textContent = '💡 台阶成为翻转支点 · 资源被巧妙利用';
                    tagInnovation.classList.add('highlight-active');
                } else if (t > 6.5 && t < 8.5) {
                    tagInnovation.textContent = '💡 后轮组同样利用台阶完成翻转';
                    tagInnovation.classList.add('highlight-active');
                } else {
                    tagInnovation.textContent = '💡 核心创新:台阶从"障碍"变为"支点"';
                    tagInnovation.classList.remove('highlight-active');
                }
            }

            // ============ 动画循环 ============
            function animate(timestamp) {
                if (!isRunning) return;

                if (lastFrameTime === null) {
                    lastFrameTime = timestamp;
                }

                let delta = (timestamp - lastFrameTime) / 1000; // 秒
                if (delta > 0.2) delta = 0.2; // 防止大帧跳跃
                if (delta <= 0) delta = 0.016;

                lastFrameTime = timestamp;

                animTime += delta * speedMultiplier;
                if (animTime >= TOTAL_DURATION) {
                    animTime -= TOTAL_DURATION;
                    currentPhaseIndex = -1; // 强制刷新阶段
                }
                if (animTime < 0) animTime = 0;

                const state = computeAnimationState(animTime);
                updateSVG(state);
                updateUI(animTime);

                animationId = requestAnimationFrame(animate);
            }

            // ============ 启动与重置 ============
            function startAnimation() {
                if (animationId) {
                    cancelAnimationFrame(animationId);
                }
                animTime = 0;
                lastFrameTime = null;
                currentPhaseIndex = -1;
                isRunning = true;
                // 重置阶段指示点
                phaseDots.forEach((dot, i) => {
                    dot.classList.remove('active', 'done');
                    if (i === 0) dot.classList.add('active');
                });
                currentPhaseIndex = 0;
                tagPhase.textContent = '阶段:平路滚动';
                tagInnovation.textContent = '💡 核心创新:台阶从"障碍"变为"支点"';
                tagInnovation.classList.remove('highlight-active');
                pivotGlow.setAttribute('opacity', '0');
                pivotGlowInner.setAttribute('opacity', '0');
                flipTrajectory.setAttribute('opacity', '0');
                contactFlash.setAttribute('opacity', '0');
                animationId = requestAnimationFrame(animate);
            }

            function stopAnimation() {
                isRunning = false;
                if (animationId) {
                    cancelAnimationFrame(animationId);
                    animationId = null;
                }
            }

            // ============ 事件监听 ============
            window.addEventListener('DOMContentLoaded', () => {
                startAnimation();
            });

            // 如果DOM已加载则立即启动
            if (document.readyState === 'interactive' || document.readyState === 'complete') {
                startAnimation();
            }

            // 页面可见性变化时处理
            document.addEventListener('visibilitychange', () => {
                if (document.hidden) {
                    stopAnimation();
                } else {
                    // 页面重新可见时重新启动
                    lastFrameTime = null;
                    isRunning = true;
                    if (!animationId) {
                        animationId = requestAnimationFrame(animate);
                    }
                }
            });

            // 速度滑块
            speedSlider.addEventListener('input', () => {
                speedMultiplier = parseFloat(speedSlider.value);
                speedLabel.textContent = speedMultiplier.toFixed(1) + '×';
            });

            // 窗口大小变化时不需要特殊处理,SVG viewBox自动适配

            console.log('🚀 行星翻转轮组动画已就绪 | TRIZ 最终理想解 (IFR) 可视化');
            console.log('   - 自动循环播放中');
            console.log('   - 使用速度滑块可调节播放速率');
            console.log('   - 观察金色发光标记:台阶从障碍变为支点');
        })();
    </script>
</body>
</html>

动画展示解析

这个动画结合清晰机械动作、阶段说明和轮组翻转轨迹,直观展示了“台阶障碍→阶越支点”这一创新点和基于转换原理的越障过程。

  • 清晰的动画叙事:页面将复杂的越障过程拆解为6个连续阶段,每个阶段都有对应的文字说明和进度指示点。动画默认自动循环播放,无需手动触发,让您能完整观察从平路滚动、前轮遇障、翻转登台到整车驶离的全过程。

  • 直观的交互控制:页面右下角提供了动画速度调节滑块。您可以放慢动画,仔细观察轮组翻转时各部件(小轮、旋转臂、货箱)的相对运动;或加快动画,快速浏览整个越障循环。

  • 聚焦核心的视觉设计:采用了深色工业风格背景,将视觉焦点集中在金色主色调的行星轮组机构上。动画中会动态高亮台阶立面的接触点,并绘制出轮组翻转的轨迹弧线,直接展示了“利用台阶作为支点完成翻转”这一核心物理原理。


优化建议: 您可以在代码开头的<style>标签中修改CSS变量(如--accent)来调整主题色;台阶高度和轮组尺寸等关键参数位于<script>开头的常量定义区域,您可按需微调。

这是为您重新生成的行星翻转轮组越障动画,重点优化了支架与轮组的整体联动表现。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>行星翻转轮组 · 越障原理动画 | TRIZ 最终理想解</title>
    <style>
        :root {
            --bg: #14151a;
            --surface: #1e1f26;
            --text: #c8cad4;
            --text-secondary: #8a8d9a;
            --accent: #f0a54a;
            --accent-bright: #ffd166;
            --accent-glow: rgba(240, 165, 74, 0.5);
            --step: #4a4c5a;
            --step-edge: #7a7d90;
            --ground-low: #2e303b;
            --ground-high: #353744;
            --metal: #8892a0;
            --metal-light: #a8b2c0;
            --rubber: #262830;
            --rubber-stroke: #3e404a;
            --cargo: #c9a96e;
            --cargo-dark: #7a5f36;
            --hub-glow: #ffcc66;
            --font-display: 'Georgia', 'Noto Serif SC', 'STSong', 'SimSun', 'Songti SC', serif;
            --font-body: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', sans-serif;
        }

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

        body {
            background: var(--bg);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            font-family: var(--font-body);
            overflow-x: hidden;
            -webkit-font-smoothing: antialiased;
            user-select: none;
            -webkit-user-select: none;
        }

        .animation-wrapper {
            position: relative;
            width: 96vw;
            max-width: 1350px;
            aspect-ratio: 16 / 7;
            min-height: 500px;
            background: radial-gradient(ellipse at 50% 45%, #1e2030 0%, #111218 75%);
            border-radius: 20px;
            overflow: hidden;
            box-shadow:
                0 4px 40px rgba(0, 0, 0, 0.55),
                0 1px 0 rgba(255, 255, 255, 0.025) inset,
                0 0 0 1px rgba(255, 255, 255, 0.06);
        }

        .animation-wrapper svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        /* 覆盖层标签 */
        .overlay-labels {
            position: absolute;
            pointer-events: none;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 10;
        }
        .label-tag {
            position: absolute;
            font-family: var(--font-body);
            font-size: 10.5px;
            font-weight: 500;
            letter-spacing: 0.03em;
            color: #9597a2;
            background: rgba(20, 21, 26, 0.78);
            padding: 4px 11px;
            border-radius: 13px;
            border: 1px solid rgba(255, 255, 255, 0.07);
            white-space: nowrap;
            backdrop-filter: blur(5px);
            -webkit-backdrop-filter: blur(5px);
            transition: color 0.5s, border-color 0.5s, background 0.5s;
            pointer-events: none;
        }
        .label-tag.highlight-active {
            color: #f0cc7a;
            border-color: rgba(240, 165, 74, 0.45);
            background: rgba(28, 25, 18, 0.82);
        }
        .label-tl {
            top: 12px;
            left: 14px;
        }
        .label-tr {
            top: 12px;
            right: 14px;
        }
        .label-bl {
            bottom: 12px;
            left: 14px;
        }
        .label-br {
            bottom: 12px;
            right: 14px;
        }
        .label-bc {
            bottom: 6px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 9.5px;
            letter-spacing: 0.04em;
        }

        /* 阶段指示点 */
        .phase-indicator {
            position: absolute;
            bottom: 38px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 5px;
            z-index: 11;
            pointer-events: none;
        }
        .phase-dot {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.18);
            transition: all 0.45s cubic-bezier(0.4, 0, 0.2, 1);
        }
        .phase-dot.active {
            background: var(--accent);
            box-shadow: 0 0 9px var(--accent-glow);
            width: 8px;
            height: 8px;
            margin-top: -1px;
        }
        .phase-dot.done {
            background: rgba(240, 165, 74, 0.45);
        }

        /* 速度控制 */
        .speed-control {
            position: absolute;
            bottom: 14px;
            right: 70px;
            z-index: 20;
            display: flex;
            align-items: center;
            gap: 7px;
            pointer-events: all;
            opacity: 0.65;
            transition: opacity 0.3s;
        }
        .speed-control:hover {
            opacity: 1;
        }
        .speed-control label {
            font-size: 10px;
            color: #9597a2;
            font-family: var(--font-body);
            letter-spacing: 0.04em;
            pointer-events: none;
        }
        .speed-control input[type="range"] {
            -webkit-appearance: none;
            width: 85px;
            height: 4px;
            border-radius: 2px;
            background: rgba(255, 255, 255, 0.18);
            outline: none;
            cursor: pointer;
        }
        .speed-control input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 15px;
            height: 15px;
            border-radius: 50%;
            background: var(--accent);
            cursor: pointer;
            border: 2px solid #14151a;
            box-shadow: 0 0 8px rgba(240, 165, 74, 0.35);
        }
        #speedLabel {
            min-width: 26px;
            text-align: center;
            color: #c8cad4;
        }

        @keyframes subtlePulse {
            0%,
            100% {
                opacity: 0.55;
            }
            50% {
                opacity: 1;
            }
        }
        .pulse-active {
            animation: subtlePulse 1.5s ease-in-out infinite;
        }
    </style>
</head>
<body>

    <div class="animation-wrapper" id="animContainer">
        <svg id="mainSvg" viewBox="0 0 1450 560" xmlns="http://www.w3.org/2000/svg">
            <defs>
                <!-- 背景网格 -->
                <pattern id="grid" width="50" height="50" patternUnits="userSpaceOnUse">
                    <path d="M 50 0 L 0 0 0 50" fill="none" stroke="rgba(255,255,255,0.025)" stroke-width="0.5"/>
                </pattern>
                <!-- 发光滤镜 -->
                <filter id="glowStrong" x="-100%" y="-100%" width="300%" height="300%">
                    <feGaussianBlur stdDeviation="6" result="blur"/>
                    <feMerge>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>
                <filter id="glowSoft" x="-60%" y="-60%" width="220%" height="220%">
                    <feGaussianBlur stdDeviation="3.5" result="blur"/>
                    <feMerge>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>
                <filter id="glowTiny" x="-40%" y="-40%" width="180%" height="180%">
                    <feGaussianBlur stdDeviation="2" result="blur"/>
                    <feMerge>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>
                <!-- 渐变 -->
                <linearGradient id="gradGroundLow" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#424454"/>
                    <stop offset="100%" stop-color="#272934"/>
                </linearGradient>
                <linearGradient id="gradStepFace" x1="0" y1="0" x2="1" y2="0">
                    <stop offset="0%" stop-color="#555868"/>
                    <stop offset="100%" stop-color="#383a47"/>
                </linearGradient>
                <linearGradient id="gradGroundHigh" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#5e6072"/>
                    <stop offset="100%" stop-color="#434554"/>
                </linearGradient>
                <linearGradient id="gradMetalArm" x1="0" y1="0" x2="1" y2="1">
                    <stop offset="0%" stop-color="#a8b2c0"/>
                    <stop offset="45%" stop-color="#788290"/>
                    <stop offset="100%" stop-color="#8a94a4"/>
                </linearGradient>
                <linearGradient id="gradCargo" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#ddbb78"/>
                    <stop offset="100%" stop-color="#9a7340"/>
                </linearGradient>
                <radialGradient id="gradPivotGlow" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#ffe08a" stop-opacity="0.95"/>
                    <stop offset="35%" stop-color="#f0a54a" stop-opacity="0.5"/>
                    <stop offset="100%" stop-color="#f0a54a" stop-opacity="0"/>
                </radialGradient>
                <radialGradient id="gradContactFlash" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#ffffff" stop-opacity="0.85"/>
                    <stop offset="25%" stop-color="#ffd166" stop-opacity="0.55"/>
                    <stop offset="100%" stop-color="#ff8800" stop-opacity="0"/>
                </radialGradient>
                <radialGradient id="gradHubHighlight" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#ffe8b0" stop-opacity="0.9"/>
                    <stop offset="50%" stop-color="#f0a54a" stop-opacity="0.35"/>
                    <stop offset="100%" stop-color="#f0a54a" stop-opacity="0"/>
                </radialGradient>
            </defs>

            <!-- 背景层 -->
            <rect x="0" y="0" width="1450" height="560" fill="url(#grid)" opacity="0.5"/>

            <!-- 低处地面 -->
            <rect id="groundLow" x="0" y="445" width="755" height="115" fill="url(#gradGroundLow)"/>
            <line x1="0" y1="445" x2="755" y2="445" stroke="#6e707e" stroke-width="2.5"/>

            <!-- 台阶立面 -->
            <rect id="stepFace" x="750" y="340" width="22" height="105" fill="url(#gradStepFace)" rx="3"/>
            <line x1="748" y1="342" x2="774" y2="342" stroke="#a0a3b2" stroke-width="2.5" stroke-linecap="round"/>
            <!-- 台阶边缘微小高光 -->
            <line x1="750" y1="343" x2="770" y2="343" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>

            <!-- 台阶顶部地面 -->
            <rect id="groundHigh" x="770" y="340" width="680" height="220" fill="url(#gradGroundHigh)"/>
            <line x1="770" y1="340" x2="1450" y2="340" stroke="#7e8090" stroke-width="2.5"/>

            <!-- 台阶高度标注 -->
            <line x1="790" y1="340" x2="790" y2="445" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="5,5"/>
            <line x1="785" y1="340" x2="795" y2="340" stroke="rgba(255,255,255,0.2)" stroke-width="1"/>
            <line x1="785" y1="445" x2="795" y2="445" stroke="rgba(255,255,255,0.2)" stroke-width="1"/>
            <text x="798" y="396" fill="rgba(255,255,255,0.3)" font-size="10" font-family="var(--font-body)">台阶</text>
            <text x="798" y="409" fill="rgba(255,255,255,0.3)" font-size="10" font-family="var(--font-body)">~100mm</text>

            <!-- 支点发光(翻转阶段显示) -->
            <circle id="pivotGlow" cx="0" cy="0" r="24" fill="url(#gradPivotGlow)" filter="url(#glowStrong)" opacity="0" pointer-events="none"/>
            <circle id="pivotGlowInner" cx="0" cy="0" r="7" fill="#ffe8b0" opacity="0" pointer-events="none" filter="url(#glowSoft)"/>

            <!-- 翻转轨迹弧线 -->
            <path id="flipTrajectory" d="" fill="none" stroke="#f0a54a" stroke-width="2.2" stroke-dasharray="7,5" opacity="0" pointer-events="none" filter="url(#glowTiny)"/>

            <!-- 接触闪光 -->
            <circle id="contactFlash" cx="0" cy="0" r="16" fill="url(#gradContactFlash)" opacity="0" pointer-events="none"/>

            <!-- 轮组中心高亮(翻转时标记支架位置) -->
            <circle id="frontHubHighlight" cx="0" cy="0" r="15" fill="url(#gradHubHighlight)" filter="url(#glowSoft)" opacity="0" pointer-events="none"/>
            <circle id="rearHubHighlight" cx="0" cy="0" r="15" fill="url(#gradHubHighlight)" filter="url(#glowSoft)" opacity="0" pointer-events="none"/>

            <!-- === 动态元素组 === -->

            <!-- 后轮组 -->
            <g id="rearHubGroup">
                <!-- 旋转臂(三条,120°分布) -->
                <g id="rearArms">
                    <line x1="0" y1="0" x2="0" y2="-115" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
                    <line x1="0" y1="0" x2="99.59" y2="57.5" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
                    <line x1="0" y1="0" x2="-99.59" y2="57.5" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
                </g>
                <!-- 中心支架 / 轮毂 -->
                <circle cx="0" cy="0" r="10" fill="#5a5f6b" stroke="#8892a0" stroke-width="2.5"/>
                <circle cx="0" cy="0" r="4.5" fill="#2e3038"/>
                <circle cx="0" cy="0" r="2.2" fill="#8892a0"/>
                <!-- 三个小轮 -->
                <circle cx="0" cy="-115" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
                <circle cx="99.59" cy="57.5" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
                <circle cx="-99.59" cy="57.5" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
                <!-- 小轮内圈标记 -->
                <circle cx="0" cy="-115" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
                <circle cx="99.59" cy="57.5" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
                <circle cx="-99.59" cy="57.5" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
                <!-- 小轮花纹暗示(细微横线) -->
                <line x1="-10" y1="-115" x2="10" y2="-115" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
                <line x1="89.59" y1="57.5" x2="109.59" y2="57.5" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
                <line x1="-109.59" y1="57.5" x2="-89.59" y2="57.5" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
            </g>

            <!-- 前轮组(结构与后轮组相同) -->
            <g id="frontHubGroup">
                <g id="frontArms">
                    <line x1="0" y1="0" x2="0" y2="-115" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
                    <line x1="0" y1="0" x2="99.59" y2="57.5" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
                    <line x1="0" y1="0" x2="-99.59" y2="57.5" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
                </g>
                <circle cx="0" cy="0" r="10" fill="#5a5f6b" stroke="#8892a0" stroke-width="2.5"/>
                <circle cx="0" cy="0" r="4.5" fill="#2e3038"/>
                <circle cx="0" cy="0" r="2.2" fill="#8892a0"/>
                <circle cx="0" cy="-115" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
                <circle cx="99.59" cy="57.5" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
                <circle cx="-99.59" cy="57.5" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
                <circle cx="0" cy="-115" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
                <circle cx="99.59" cy="57.5" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
                <circle cx="-99.59" cy="57.5" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
                <line x1="-10" y1="-115" x2="10" y2="-115" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
                <line x1="89.59" y1="57.5" x2="109.59" y2="57.5" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
                <line x1="-109.59" y1="57.5" x2="-89.59" y2="57.5" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
            </g>

            <!-- 底盘主梁(动态线段,连接前后轮组中心) -->
            <line id="chassisBeam" x1="0" y1="0" x2="0" y2="0" stroke="#6e7684" stroke-width="11" stroke-linecap="round" opacity="0.9"/>
            <line id="chassisBeamHighlight" x1="0" y1="0" x2="0" y2="0" stroke="rgba(180,190,200,0.5)" stroke-width="2" stroke-linecap="round"/>

            <!-- 货箱(挂在底盘中点) -->
            <g id="cargoGroup">
                <rect x="-78" y="-72" width="156" height="56" rx="7" fill="url(#gradCargo)" stroke="#7a5f36" stroke-width="2.2"/>
                <line x1="-52" y1="-52" x2="52" y2="-52" stroke="rgba(255,255,255,0.07)" stroke-width="1"/>
                <line x1="-52" y1="-35" x2="52" y2="-35" stroke="rgba(255,255,255,0.07)" stroke-width="1"/>
                <text x="0" y="-18" text-anchor="middle" fill="rgba(255,255,255,0.45)" font-size="10" font-family="var(--font-body)" letter-spacing="0.05em">货箱</text>
                <!-- 阻尼铰链示意 -->
                <circle cx="0" cy="-16" r="5" fill="#3a3d45" stroke="#8892a0" stroke-width="1.5"/>
                <line x1="-3" y1="-16" x2="3" y2="-16" stroke="#f0a54a" stroke-width="2" stroke-linecap="round"/>
            </g>

            <!-- 地面接触指示点 -->
            <circle id="rearContactDot" cx="0" cy="0" r="5" fill="#4ecf8f" opacity="0.5" pointer-events="none" filter="url(#glowTiny)"/>
            <circle id="frontContactDot" cx="0" cy="0" r="5" fill="#4ecf8f" opacity="0.5" pointer-events="none" filter="url(#glowTiny)"/>
        </svg>

        <!-- 覆盖层标签 -->
        <div class="overlay-labels">
            <div class="label-tag label-tl" id="tagTitle">🌟 TRIZ 最终理想解 · 行星翻转轮组</div>
            <div class="label-tag label-tr" id="tagPhase">阶段:平路滚动</div>
            <div class="label-tag label-bl" id="tagInnovation">💡 台阶从"障碍"变为翻转"支点"</div>
            <div class="label-tag label-br" id="tagDetail">回转半径 115mm · 小轮 Φ80mm · 阻尼铰链 ±20°</div>
        </div>

        <!-- 阶段指示点 -->
        <div class="phase-indicator" id="phaseDots">
            <div class="phase-dot active" title="平路滚动"></div>
            <div class="phase-dot" title="遇障"></div>
            <div class="phase-dot" title="翻转"></div>
            <div class="phase-dot" title="登台"></div>
            <div class="phase-dot" title="完成"></div>
        </div>

        <!-- 速度控制 -->
        <div class="speed-control">
            <label for="speedSlider">⏱ 速度</label>
            <input type="range" id="speedSlider" min="0.25" max="2.5" step="0.05" value="1.0" title="调节动画播放速度">
            <label id="speedLabel">1×</label>
        </div>
    </div>

    <script>
        (function() {
            // ========== DOM 引用 ==========
            const rearHubGroup = document.getElementById('rearHubGroup');
            const frontHubGroup = document.getElementById('frontHubGroup');
            const chassisBeam = document.getElementById('chassisBeam');
            const chassisBeamHighlight = document.getElementById('chassisBeamHighlight');
            const cargoGroup = document.getElementById('cargoGroup');
            const pivotGlow = document.getElementById('pivotGlow');
            const pivotGlowInner = document.getElementById('pivotGlowInner');
            const flipTrajectory = document.getElementById('flipTrajectory');
            const contactFlash = document.getElementById('contactFlash');
            const frontHubHighlight = document.getElementById('frontHubHighlight');
            const rearHubHighlight = document.getElementById('rearHubHighlight');
            const rearContactDot = document.getElementById('rearContactDot');
            const frontContactDot = document.getElementById('frontContactDot');
            const tagPhase = document.getElementById('tagPhase');
            const tagInnovation = document.getElementById('tagInnovation');
            const phaseDots = document.querySelectorAll('.phase-dot');
            const speedSlider = document.getElementById('speedSlider');
            const speedLabel = document.getElementById('speedLabel');

            // ========== 常量 ==========
            const GROUND_LOW_Y = 445;
            const GROUND_HIGH_Y = 340;
            const STEP_X = 750; // 台阶立面X
            const STEP_HEIGHT = GROUND_LOW_Y - GROUND_HIGH_Y; // 105
            const WHEEL_RADIUS = 40;
            const ARM_RADIUS = 115;
            const HUB_RIDE_HEIGHT = ARM_RADIUS - WHEEL_RADIUS + 6; // 轮组中心距地面高度 ≈81
            const CHASSIS_HALF = 175; // 前后轮组间距的一半
            const TOTAL_DURATION = 11.5; // 总时长(秒)

            // ========== 状态变量 ==========
            let animTime = 0;
            let speedMultiplier = 1.0;
            let lastFrameTime = null;
            let animationId = null;
            let isRunning = true;
            let currentPhaseIndex = -1;

            // ========== 阶段定义 ==========
            const phaseTimeRanges = [
                { start: 0, end: 2.3, name: '平路滚动', dotIndex: 0 },
                { start: 2.3, end: 3.3, name: '前轮遇障', dotIndex: 1 },
                { start: 3.3, end: 5.5, name: '翻转越障', dotIndex: 2 },
                { start: 5.5, end: 7.0, name: '前轮登台', dotIndex: 3 },
                { start: 7.0, end: 8.6, name: '后轮翻转', dotIndex: 3 },
                { start: 8.6, end: 10.0, name: '整车登台', dotIndex: 4 },
                { start: 10.0, end: TOTAL_DURATION, name: '平稳驶离', dotIndex: 4 },
            ];

            function getPhaseInfo(t) {
                for (const p of phaseTimeRanges) {
                    if (t >= p.start && t < p.end) return p;
                }
                return phaseTimeRanges[0];
            }

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

            function easeOutBack(t) { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math
                    .pow(t - 1, 2); }

            function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }

            function easeOutQuad(t) { return 1 - (1 - t) * (1 - t); }

            function easeInQuad(t) { return t * t; }

            function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }

            function lerp(a, b, t) { return a + (b - a) * clamp(t, 0, 1); }

            // ========== 核心动画计算 ==========
            function computeAnimationState(t) {
                // 全局X位移(底盘参考点初始位置)
                let globalBaseX = 155;
                // 前后轮组中心全局坐标
                let frontHubGX, frontHubGY;
                let rearHubGX, rearHubGY;
                // 轮组自转角度
                let frontRotation = 0;
                let rearRotation = 0;
                // 支点相关
                let pivotX = 0,
                    pivotY = 0;
                let pivotGlowAlpha = 0;
                let showPivotGlow = false;
                let trajectoryAlpha = 0;
                let showTrajectory = false;
                let trajectoryD = '';
                let contactFlashAlpha = 0;
                let contactFlashX = 0,
                    contactFlashY = 0;
                let frontHubHighlightAlpha = 0;
                let rearHubHighlightAlpha = 0;

                // 平路时轮组中心Y坐标
                const hubYLow = GROUND_LOW_Y - HUB_RIDE_HEIGHT; // 445-81=364
                const hubYHigh = GROUND_HIGH_Y - HUB_RIDE_HEIGHT; // 340-81=259

                if (t < 2.3) {
                    // === 阶段0:平路滚动 ===
                    const phaseT = t / 2.3;
                    const easedT = easeInOutQuad(phaseT);
                    globalBaseX = 155 + easedT * 310;
                    frontHubGX = globalBaseX + CHASSIS_HALF;
                    frontHubGY = hubYLow;
                    rearHubGX = globalBaseX - CHASSIS_HALF;
                    rearHubGY = hubYLow;
                    frontRotation = easedT * 360 * 1.3;
                    rearRotation = easedT * 360 * 1.3;

                } else if (t < 3.3) {
                    // === 阶段1:前轮遇障 ===
                    const phaseT = (t - 2.3) / 1.0;
                    const easedT = easeInOutCubic(phaseT);
                    globalBaseX = 465 + easedT * 35;
                    frontHubGX = globalBaseX + CHASSIS_HALF;
                    frontHubGY = hubYLow - easedT * 10;
                    rearHubGX = globalBaseX - CHASSIS_HALF;
                    rearHubGY = hubYLow;
                    frontRotation = 468 + easedT * 85;
                    rearRotation = 468 + easedT * 105;
                    // 接触闪光
                    if (easedT > 0.5) {
                        contactFlashAlpha = (easedT - 0.5) / 0.5 * 0.7;
                        contactFlashX = frontHubGX;
                        contactFlashY = GROUND_LOW_Y - WHEEL_RADIUS;
                    }
                    if (easedT > 0.65) {
                        pivotGlowAlpha = (easedT - 0.65) / 0.35 * 0.5;
                        showPivotGlow = true;
                        pivotX = contactFlashX;
                        pivotY = contactFlashY;
                    }

                } else if (t < 5.5) {
                    // === 阶段2:前轮组翻转越障(核心) ===
                    const phaseT = (t - 3.3) / 2.2;
                    const easedT = easeOutBack(clamp(phaseT, 0, 1));
                    // 支点固定:前轮组最前方小轮接触台阶立面底部
                    pivotX = 465 + CHASSIS_HALF; // 约640(实际会随globalBaseX变化,这里取遇障结束时的位置)
                    // 重新精确计算:globalBaseX在阶段1结束时约500,前轮组中心约675
                    // 但实际上轮组绕支点翻转,支点大约在(STEP_X + WHEEL_RADIUS, GROUND_LOW_Y - WHEEL_RADIUS)
                    pivotX = STEP_X + WHEEL_RADIUS; // 790
                    pivotY = GROUND_LOW_Y - WHEEL_RADIUS; // 405
                    // 轮组中心绕支点旋转:初始角度约-55°(左上方),翻转120°后约65°(右上方)
                    const startAngleDeg = -58;
                    const flipAngleDeg = startAngleDeg + easedT * 120; // 从-58°到62°
                    const flipAngleRad = flipAngleDeg * Math.PI / 180;
                    frontHubGX = pivotX + ARM_RADIUS * Math.cos(flipAngleRad);
                    frontHubGY = pivotY + ARM_RADIUS * Math.sin(flipAngleRad);
                    // 后轮组继续在地面滚动
                    globalBaseX = 500 + easedT * 55;
                    rearHubGX = globalBaseX - CHASSIS_HALF + easedT * 20;
                    rearHubGY = hubYLow + easedT * 5;
                    frontRotation = 553 + easedT * 210;
                    rearRotation = 573 + easedT * 140;
                    showPivotGlow = true;
                    pivotGlowAlpha = 0.45 + easedT * 0.4;
                    showTrajectory = true;
                    trajectoryAlpha = 0.3 + easedT * 0.5;
                    // 轨迹弧线
                    const trajStartX = pivotX + ARM_RADIUS * Math.cos(startAngleDeg * Math.PI / 180);
                    const trajStartY = pivotY + ARM_RADIUS * Math.sin(startAngleDeg * Math.PI / 180);
                    const trajEndX = pivotX + ARM_RADIUS * Math.cos(62 * Math.PI / 180);
                    const trajEndY = pivotY + ARM_RADIUS * Math.sin(62 * Math.PI / 180);
                    const trajMidX = (trajStartX + trajEndX) / 2 + 50;
                    const trajMidY = Math.min(trajStartY, trajEndY) - 60;
                    trajectoryD =
                        `M ${trajStartX} ${trajStartY} Q ${trajMidX} ${trajMidY} ${trajEndX} ${trajEndY}`;
                    contactFlashAlpha = 0.6 - easedT * 0.4;
                    contactFlashX = pivotX;
                    contactFlashY = pivotY;
                    frontHubHighlightAlpha = 0.5 + easedT * 0.4;

                } else if (t < 7.0) {
                    // === 阶段3:前轮登台过渡 ===
                    const phaseT = (t - 5.5) / 1.5;
                    const easedT = easeInOutCubic(phaseT);
                    // 前轮组在台阶顶部
                    frontHubGX = pivotX + ARM_RADIUS * Math.cos(62 * Math.PI / 180) + easedT * 85;
                    frontHubGY = hubYHigh + (1 - easedT) * 8;
                    globalBaseX = 555 + easedT * 85;
                    rearHubGX = globalBaseX - CHASSIS_HALF + 20 - easedT * 15;
                    rearHubGY = hubYLow + 5 - easedT * 2;
                    frontRotation = 763 + easedT * 160;
                    rearRotation = 713 + easedT * 170;
                    pivotGlowAlpha = 0.85 - easedT * 0.55;
                    showPivotGlow = pivotGlowAlpha > 0.04;
                    trajectoryAlpha = 0.8 - easedT * 0.6;
                    showTrajectory = trajectoryAlpha > 0.04;
                    contactFlashAlpha = 0.2 - easedT * 0.2;
                    frontHubHighlightAlpha = 0.9 - easedT * 0.5;

                } else if (t < 8.6) {
                    // === 阶段4:后轮组翻转 ===
                    const phaseT = (t - 7.0) / 1.6;
                    const easedT = easeOutBack(clamp(phaseT, 0, 1));
                    // 后轮组支点
                    const rPivotX = STEP_X + WHEEL_RADIUS; // 790
                    const rPivotY = GROUND_LOW_Y - WHEEL_RADIUS; // 405
                    const rStartAngleDeg = -55;
                    const rFlipAngleDeg = rStartAngleDeg + easedT * 118;
                    const rFlipAngleRad = rFlipAngleDeg * Math.PI / 180;
                    rearHubGX = rPivotX + ARM_RADIUS * Math.cos(rFlipAngleRad);
                    rearHubGY = rPivotY + ARM_RADIUS * Math.sin(rFlipAngleRad);
                    // 前轮组已在台阶上
                    globalBaseX = 640 + easedT * 50;
                    frontHubGX = globalBaseX + CHASSIS_HALF + easedT * 30;
                    frontHubGY = hubYHigh + (1 - easedT) * 3;
                    frontRotation = 923 + easedT * 110;
                    rearRotation = 883 + easedT * 220;
                    pivotX = rPivotX;
                    pivotY = rPivotY;
                    showPivotGlow = easedT > 0.2 && easedT < 0.92;
                    pivotGlowAlpha = showPivotGlow ? 0.5 : 0;
                    showTrajectory = easedT > 0.15 && easedT < 0.88;
                    trajectoryAlpha = showTrajectory ? 0.38 : 0;
                    const rtStartX = rPivotX + ARM_RADIUS * Math.cos(rStartAngleDeg * Math.PI / 180);
                    const rtStartY = rPivotY + ARM_RADIUS * Math.sin(rStartAngleDeg * Math.PI / 180);
                    const rtEndX = rPivotX + ARM_RADIUS * Math.cos((rStartAngleDeg + 118) * Math.PI / 180);
                    const rtEndY = rPivotY + ARM_RADIUS * Math.sin((rStartAngleDeg + 118) * Math.PI / 180);
                    const rtMidX = (rtStartX + rtEndX) / 2 + 45;
                    const rtMidY = Math.min(rtStartY, rtEndY) - 55;
                    trajectoryD = `M ${rtStartX} ${rtStartY} Q ${rtMidX} ${rtMidY} ${rtEndX} ${rtEndY}`;
                    contactFlashAlpha = (easedT > 0.25 && easedT < 0.75) ? 0.55 : 0;
                    contactFlashX = rPivotX;
                    contactFlashY = rPivotY;
                    rearHubHighlightAlpha = 0.5 + easedT * 0.35;

                } else if (t < 10.0) {
                    // === 阶段5:整车登台 ===
                    const phaseT = (t - 8.6) / 1.4;
                    const easedT = easeInOutCubic(phaseT);
                    const rPivotX = STEP_X + WHEEL_RADIUS;
                    const rPivotY = GROUND_LOW_Y - WHEEL_RADIUS;
                    const rEndAngleRad = (-55 + 118) * Math.PI / 180;
                    const rEndX = rPivotX + ARM_RADIUS * Math.cos(rEndAngleRad);
                    const rEndY = rPivotY + ARM_RADIUS * Math.sin(rEndAngleRad);
                    rearHubGX = rEndX + easedT * 100;
                    rearHubGY = hubYHigh + (1 - easedT) * 5;
                    globalBaseX = 690 + easedT * 120;
                    frontHubGX = globalBaseX + CHASSIS_HALF + 30 + easedT * 10;
                    frontHubGY = hubYHigh;
                    frontRotation = 1033 + easedT * 80;
                    rearRotation = 1103 + easedT * 80;
                    pivotGlowAlpha = Math.max(0, pivotGlowAlpha - 0.06);
                    showPivotGlow = pivotGlowAlpha > 0.02;
                    trajectoryAlpha = Math.max(0, trajectoryAlpha - 0.06);
                    showTrajectory = trajectoryAlpha > 0.02;
                    contactFlashAlpha = Math.max(0, contactFlashAlpha - 0.05);
                    frontHubHighlightAlpha = Math.max(0, frontHubHighlightAlpha - 0.04);
                    rearHubHighlightAlpha = Math.max(0, rearHubHighlightAlpha - 0.04);

                } else {
                    // === 阶段6:平稳驶离 ===
                    const phaseT = (t - 10.0) / (TOTAL_DURATION - 10.0);
                    const easedT = easeInOutQuad(phaseT);
                    globalBaseX = 810 + easedT * 170;
                    frontHubGX = globalBaseX + CHASSIS_HALF;
                    frontHubGY = hubYHigh;
                    rearHubGX = globalBaseX - CHASSIS_HALF;
                    rearHubGY = hubYHigh;
                    frontRotation = 1113 + easedT * 65;
                    rearRotation = 1183 + easedT * 65;
                    pivotGlowAlpha = 0;
                    showPivotGlow = false;
                    trajectoryAlpha = 0;
                    showTrajectory = false;
                    contactFlashAlpha = 0;
                    frontHubHighlightAlpha = 0;
                    rearHubHighlightAlpha = 0;
                }

                return {
                    frontHubGX,
                    frontHubGY,
                    rearHubGX,
                    rearHubGY,
                    frontRotation,
                    rearRotation,
                    pivotX,
                    pivotY,
                    showPivotGlow,
                    pivotGlowAlpha,
                    showTrajectory,
                    trajectoryAlpha,
                    trajectoryD,
                    contactFlashAlpha,
                    contactFlashX,
                    contactFlashY,
                    frontHubHighlightAlpha,
                    rearHubHighlightAlpha,
                };
            }

            // ========== 更新SVG ==========
            function updateSVG(state) {
                // 前后轮组定位
                frontHubGroup.setAttribute('transform',
                    `translate(${state.frontHubGX}, ${state.frontHubGY}) rotate(${state.frontRotation})`);
                rearHubGroup.setAttribute('transform',
                    `translate(${state.rearHubGX}, ${state.rearHubGY}) rotate(${state.rearRotation})`);

                // 底盘主梁:从后轮组中心到前轮组中心
                chassisBeam.setAttribute('x1', state.rearHubGX);
                chassisBeam.setAttribute('y1', state.rearHubGY);
                chassisBeam.setAttribute('x2', state.frontHubGX);
                chassisBeam.setAttribute('y2', state.frontHubGY);
                chassisBeamHighlight.setAttribute('x1', state.rearHubGX);
                chassisBeamHighlight.setAttribute('y1', state.rearHubGY);
                chassisBeamHighlight.setAttribute('x2', state.frontHubGX);
                chassisBeamHighlight.setAttribute('y2', state.frontHubGY);

                // 货箱:挂在底盘中点,允许阻尼倾斜
                const midX = (state.rearHubGX + state.frontHubGX) / 2;
                const midY = (state.rearHubGY + state.frontHubGY) / 2;
                const beamAngle = Math.atan2(
                    state.frontHubGY - state.rearHubGY,
                    state.frontHubGX - state.rearHubGX
                ) * 180 / Math.PI;
                // 阻尼铰链:货箱倾斜为底盘角度的约25%(反向),最大±20°
                const dampingFactor = 0.25;
                const rawCargoTilt = -beamAngle * dampingFactor;
                const cargoTilt = clamp(rawCargoTilt, -20, 20);
                cargoGroup.setAttribute('transform',
                    `translate(${midX}, ${midY - 28}) rotate(${cargoTilt})`);

                // 支点发光
                if (state.showPivotGlow && state.pivotGlowAlpha > 0.02) {
                    pivotGlow.setAttribute('cx', state.pivotX);
                    pivotGlow.setAttribute('cy', state.pivotY);
                    pivotGlow.setAttribute('opacity', state.pivotGlowAlpha);
                    pivotGlowInner.setAttribute('cx', state.pivotX);
                    pivotGlowInner.setAttribute('cy', state.pivotY);
                    pivotGlowInner.setAttribute('opacity', state.pivotGlowAlpha * 0.85);
                } else {
                    pivotGlow.setAttribute('opacity', '0');
                    pivotGlowInner.setAttribute('opacity', '0');
                }

                // 翻转轨迹
                if (state.showTrajectory && state.trajectoryAlpha > 0.02) {
                    flipTrajectory.setAttribute('d', state.trajectoryD);
                    flipTrajectory.setAttribute('opacity', state.trajectoryAlpha);
                } else {
                    flipTrajectory.setAttribute('opacity', '0');
                }

                // 接触闪光
                if (state.contactFlashAlpha > 0.02) {
                    contactFlash.setAttribute('cx', state.contactFlashX);
                    contactFlash.setAttribute('cy', state.contactFlashY);
                    contactFlash.setAttribute('opacity', state.contactFlashAlpha);
                    contactFlash.setAttribute('r', 9 + state.contactFlashAlpha * 10);
                } else {
                    contactFlash.setAttribute('opacity', '0');
                }

                // 轮组中心高亮
                frontHubHighlight.setAttribute('cx', state.frontHubGX);
                frontHubHighlight.setAttribute('cy', state.frontHubGY);
                frontHubHighlight.setAttribute('opacity', state.frontHubHighlightAlpha);
                rearHubHighlight.setAttribute('cx', state.rearHubGX);
                rearHubHighlight.setAttribute('cy', state.rearHubGY);
                rearHubHighlight.setAttribute('opacity', state.rearHubHighlightAlpha);

                // 地面接触指示点
                const fOnHigh = state.frontHubGY < GROUND_LOW_Y - 60;
                const rOnHigh = state.rearHubGY < GROUND_LOW_Y - 60;
                frontContactDot.setAttribute('cx', state.frontHubGX);
                frontContactDot.setAttribute('cy', fOnHigh ? GROUND_HIGH_Y : GROUND_LOW_Y);
                rearContactDot.setAttribute('cx', state.rearHubGX);
                rearContactDot.setAttribute('cy', rOnHigh ? GROUND_HIGH_Y : GROUND_LOW_Y);
            }

            // ========== 更新UI ==========
            function updateUI(t) {
                const phaseInfo = getPhaseInfo(t);
                if (phaseInfo && phaseInfo.dotIndex !== currentPhaseIndex) {
                    currentPhaseIndex = phaseInfo.dotIndex;
                    tagPhase.textContent = '阶段:' + phaseInfo.name;
                    phaseDots.forEach((dot, i) => {
                        dot.classList.remove('active', 'done');
                        if (i < currentPhaseIndex) dot.classList.add('done');
                        if (i === currentPhaseIndex) dot.classList.add('active');
                    });
                }
                // 动态创新标签
                if (t > 2.8 && t < 5.8) {
                    tagInnovation.textContent = '💡 台阶成为翻转支点 · 支架随轮组整体跨越';
                    tagInnovation.classList.add('highlight-active');
                } else if (t > 6.8 && t < 8.8) {
                    tagInnovation.textContent = '💡 后轮组同样利用台阶完成翻转跨越';
                    tagInnovation.classList.add('highlight-active');
                } else {
                    tagInnovation.textContent = '💡 台阶从"障碍"变为翻转"支点"';
                    tagInnovation.classList.remove('highlight-active');
                }
            }

            // ========== 动画循环 ==========
            function animate(timestamp) {
                if (!isRunning) return;
                if (lastFrameTime === null) lastFrameTime = timestamp;
                let delta = (timestamp - lastFrameTime) / 1000;
                if (delta > 0.25) delta = 0.25;
                if (delta <= 0) delta = 0.016;
                lastFrameTime = timestamp;
                animTime += delta * speedMultiplier;
                if (animTime >= TOTAL_DURATION) {
                    animTime -= TOTAL_DURATION;
                    currentPhaseIndex = -1;
                }
                if (animTime < 0) animTime = 0;
                const state = computeAnimationState(animTime);
                updateSVG(state);
                updateUI(animTime);
                animationId = requestAnimationFrame(animate);
            }

            // ========== 启动与重置 ==========
            function startAnimation() {
                if (animationId) cancelAnimationFrame(animationId);
                animTime = 0;
                lastFrameTime = null;
                currentPhaseIndex = -1;
                isRunning = true;
                phaseDots.forEach((dot, i) => {
                    dot.classList.remove('active', 'done');
                    if (i === 0) dot.classList.add('active');
                });
                currentPhaseIndex = 0;
                tagPhase.textContent = '阶段:平路滚动';
                tagInnovation.textContent = '💡 台阶从"障碍"变为翻转"支点"';
                tagInnovation.classList.remove('highlight-active');
                pivotGlow.setAttribute('opacity', '0');
                pivotGlowInner.setAttribute('opacity', '0');
                flipTrajectory.setAttribute('opacity', '0');
                contactFlash.setAttribute('opacity', '0');
                frontHubHighlight.setAttribute('opacity', '0');
                rearHubHighlight.setAttribute('opacity', '0');
                // 初始状态渲染一帧
                const initState = computeAnimationState(0);
                updateSVG(initState);
                animationId = requestAnimationFrame(animate);
            }

            function stopAnimation() {
                isRunning = false;
                if (animationId) { cancelAnimationFrame(animationId);
                    animationId = null; }
            }

            // ========== 事件监听 ==========
            if (document.readyState === 'interactive' || document.readyState === 'complete') {
                startAnimation();
            } else {
                window.addEventListener('DOMContentLoaded', startAnimation);
            }

            document.addEventListener('visibilitychange', () => {
                if (document.hidden) {
                    stopAnimation();
                } else {
                    lastFrameTime = null;
                    isRunning = true;
                    if (!animationId) animationId = requestAnimationFrame(animate);
                }
            });

            speedSlider.addEventListener('input', () => {
                speedMultiplier = parseFloat(speedSlider.value);
                speedLabel.textContent = speedMultiplier.toFixed(2) + '×';
            });

            console.log('🚀 行星翻转轮组动画就绪 | TRIZ 最终理想解 (IFR)');
            console.log('   ✓ 轮组支架与轮子始终整体联动');
            console.log('   ✓ 底盘主梁动态连接前后轮组中心');
            console.log('   ✓ 翻转阶段展示支点圆弧轨迹');
        })();
    </script>
</body>
</html>

越障动画解析

这个动画重点展示了“中间支架始终与轮子保持一体不脱离”以及台阶被用作翻转支点的关键过程。

  • 核心机制:动画模拟了行星翻转轮组的越障过程。关键在于,当小轮接触台阶立面后,整个轮组(包含三个小轮和连接它们的旋转臂/支架)会整体绕台阶边缘翻转,支架随轮子一同跨越台阶,而非单独移动。
  • 操作方式:动画页面加载后会自动循环播放。您可以使用右下角的速度滑块来调整动画快慢,便于仔细观察翻转瞬间。页面底部有阶段指示点,左上角会显示当前阶段名称。
  • 视觉风格:采用深色工业风格背景,突出金属质感的轮组和货箱。在翻转阶段,台阶接触点会出现金色发光点虚线运动轨迹,同时轮组中心支架会高亮显示,强化“支架随动”这一视觉印象。货箱通过阻尼铰链连接,会在跨越台阶时略微摆动,体现被动保持水平的特性。

优化建议: 您可以在代码开头的<style>标签中修改--accent等CSS变量来调整主题色;关键物理参数(如回转半径ARM_RADIUS、台阶高度STEP_HEIGHT)位于<script>标签开头的常量区域,可按需微调。

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