分享图
A
动画渲染工坊
就绪

这是为您生成的一个展示蛇腹鳞片“被动自适应各向异性摩擦”原理的高保真 SVG 交互动画,用于直观演示无电机驱动的横向抓地与纵向顺滑机理。

<!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: #0d1117;
            --surface: #161b22;
            --border: #30363d;
            --text: #c9d1d9;
            --text-secondary: #8b949e;
            --accent-gold: #d4a853;
            --accent-amber: #f0c060;
            --accent-cyan: #58a6ff;
            --accent-green: #3fb950;
            --accent-red: #f85149;
            --accent-magenta: #db61a2;
            --glow-gold: rgba(212, 168, 83, 0.5);
            --glow-cyan: rgba(88, 166, 255, 0.4);
            --glow-green: rgba(63, 185, 80, 0.4);
            --glow-red: rgba(248, 81, 73, 0.5);
            --font-display: 'Georgia', 'Times New Roman', serif;
            --font-mono: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
            --font-ui: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
        }

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

        body {
            background: var(--bg);
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: var(--font-ui);
            color: var(--text);
            background-image:
                radial-gradient(ellipse at 50% 30%, rgba(88, 166, 255, 0.04) 0%, transparent 70%),
                radial-gradient(ellipse at 70% 60%, rgba(212, 168, 83, 0.05) 0%, transparent 60%),
                radial-gradient(ellipse at 30% 70%, rgba(63, 185, 80, 0.03) 0%, transparent 50%);
            background-attachment: fixed;
            padding: 20px;
            overflow-x: hidden;
        }

        .main-container {
            width: 100%;
            max-width: 960px;
            display: flex;
            flex-direction: column;
            gap: 24px;
            align-items: center;
        }

        .header {
            text-align: center;
            display: flex;
            flex-direction: column;
            gap: 6px;
        }

        .header .badge {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 20px;
            padding: 6px 16px;
            font-size: 0.8rem;
            letter-spacing: 0.06em;
            color: var(--accent-amber);
            font-family: var(--font-mono);
            align-self: center;
            text-transform: uppercase;
        }

        .header .badge .dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: var(--accent-amber);
            animation: pulse-dot 2s ease-in-out infinite;
            box-shadow: 0 0 8px var(--glow-gold);
        }

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

        .header h1 {
            font-family: var(--font-display);
            font-size: 1.6rem;
            font-weight: 400;
            letter-spacing: 0.04em;
            color: #e6edf3;
            line-height: 1.4;
        }
        .header h1 .highlight {
            color: var(--accent-amber);
            font-style: italic;
        }

        .header .subtitle {
            font-size: 0.85rem;
            color: var(--text-secondary);
            font-family: var(--font-mono);
            letter-spacing: 0.03em;
        }

        .svg-wrapper {
            width: 100%;
            aspect-ratio: 900 / 600;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 16px;
            overflow: hidden;
            position: relative;
            box-shadow:
                0 4px 32px rgba(0, 0, 0, 0.4),
                0 0 0 1px rgba(255, 255, 255, 0.03) inset;
            transition: box-shadow 0.5s ease;
        }

        .svg-wrapper:hover {
            box-shadow:
                0 8px 48px rgba(0, 0, 0, 0.5),
                0 0 0 1px rgba(255, 255, 255, 0.05) inset,
                0 0 80px rgba(212, 168, 83, 0.08);
        }

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

        .controls-panel {
            display: flex;
            flex-wrap: wrap;
            gap: 14px;
            justify-content: center;
            align-items: center;
            width: 100%;
            max-width: 700px;
        }

        .btn-group {
            display: flex;
            gap: 2px;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 10px;
            overflow: hidden;
        }

        .btn {
            padding: 10px 18px;
            font-family: var(--font-ui);
            font-size: 0.85rem;
            font-weight: 500;
            cursor: pointer;
            border: none;
            background: transparent;
            color: var(--text-secondary);
            transition: all 0.25s ease;
            letter-spacing: 0.03em;
            white-space: nowrap;
            position: relative;
            outline: none;
        }
        .btn:hover {
            color: #e6edf3;
            background: rgba(255, 255, 255, 0.04);
        }
        .btn.active {
            color: #fff;
            background: rgba(212, 168, 83, 0.18);
            box-shadow: inset 0 0 0 1px rgba(212, 168, 83, 0.35);
            font-weight: 600;
        }
        .btn.active::after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 20%;
            width: 60%;
            height: 2px;
            background: var(--accent-amber);
            border-radius: 1px;
        }

        .btn.play-btn {
            border-radius: 10px;
            padding: 10px 22px;
            font-weight: 600;
            letter-spacing: 0.05em;
            border: 1px solid var(--border);
            background: var(--surface);
            color: var(--accent-cyan);
            transition: all 0.3s ease;
        }
        .btn.play-btn:hover {
            border-color: var(--accent-cyan);
            box-shadow: 0 0 20px var(--glow-cyan);
        }
        .btn.play-btn.playing {
            color: var(--accent-green);
            border-color: var(--accent-green);
            box-shadow: 0 0 20px var(--glow-green);
            animation: btn-glow-pulse 2s ease-in-out infinite;
        }
        @keyframes btn-glow-pulse {
            0%,
            100% {
                box-shadow: 0 0 16px var(--glow-green);
            }
            50% {
                box-shadow: 0 0 32px var(--glow-green), 0 0 48px rgba(63, 185, 80, 0.2);
            }
        }

        .slider-group {
            display: flex;
            align-items: center;
            gap: 10px;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 10px;
            padding: 8px 16px;
        }
        .slider-group label {
            font-size: 0.8rem;
            color: var(--text-secondary);
            font-family: var(--font-mono);
            letter-spacing: 0.04em;
            white-space: nowrap;
        }
        .slider-group input[type="range"] {
            -webkit-appearance: none;
            width: 100px;
            height: 6px;
            border-radius: 3px;
            background: var(--border);
            outline: none;
            cursor: pointer;
        }
        .slider-group input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 22px;
            height: 22px;
            border-radius: 50%;
            background: var(--accent-amber);
            cursor: pointer;
            border: 2px solid var(--bg);
            box-shadow: 0 0 12px var(--glow-gold);
            transition: all 0.2s ease;
        }
        .slider-group input[type="range"]::-webkit-slider-thumb:hover {
            transform: scale(1.15);
            box-shadow: 0 0 20px var(--glow-gold), 0 0 36px rgba(212, 168, 83, 0.3);
        }
        .slider-group .val-display {
            font-family: var(--font-mono);
            font-size: 0.8rem;
            color: var(--accent-amber);
            min-width: 32px;
            text-align: center;
            font-weight: 600;
        }

        .legend-mini {
            display: flex;
            flex-wrap: wrap;
            gap: 14px;
            justify-content: center;
            font-size: 0.75rem;
            color: var(--text-secondary);
            font-family: var(--font-mono);
            letter-spacing: 0.03em;
        }
        .legend-mini span {
            display: inline-flex;
            align-items: center;
            gap: 6px;
        }
        .legend-mini .dot-sm {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            display: inline-block;
            flex-shrink: 0;
        }
        .dot-sm.gold {
            background: var(--accent-amber);
            box-shadow: 0 0 6px var(--glow-gold);
        }
        .dot-sm.cyan {
            background: var(--accent-cyan);
            box-shadow: 0 0 6px var(--glow-cyan);
        }
        .dot-sm.green {
            background: var(--accent-green);
            box-shadow: 0 0 6px var(--glow-green);
        }
        .dot-sm.red {
            background: var(--accent-red);
            box-shadow: 0 0 6px var(--glow-red);
        }

        @media (max-width: 640px) {
            .header h1 {
                font-size: 1.2rem;
            }
            .btn {
                padding: 8px 12px;
                font-size: 0.75rem;
            }
            .slider-group {
                padding: 6px 10px;
                gap: 6px;
            }
            .slider-group input[type="range"] {
                width: 60px;
            }
            .controls-panel {
                gap: 8px;
            }
        }
    </style>
</head>
<body>
    <div class="main-container">
        <!-- Header -->
        <div class="header">
            <div class="badge">
                <span class="dot"></span> TRIZ · 最终理想解 IFR
            </div>
            <h1>
                被动自适应<span class="highlight">各向异性摩擦</span>原理
            </h1>
            <p class="subtitle">无电机 · 纯机械 · 横向抓 / 纵向顺</p>
        </div>

        <!-- SVG Animation Container -->
        <div class="svg-wrapper" id="svgContainer">
            <svg id="mainSvg" viewBox="0 0 900 600" xmlns="http://www.w3.org/2000/svg">
                <!-- Definitions -->
                <defs>
                    <!-- Ground texture gradient -->
                    <linearGradient id="groundGrad" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#5a5d63" />
                        <stop offset="15%" stop-color="#4a4d53" />
                        <stop offset="100%" stop-color="#2a2d33" />
                    </linearGradient>

                    <!-- Snake body gradient -->
                    <linearGradient id="bodyGrad" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#3d4a3b" />
                        <stop offset="40%" stop-color="#2d382b" />
                        <stop offset="100%" stop-color="#1a2319" />
                    </linearGradient>

                    <!-- Scale (鳞片) gradient - active/high friction -->
                    <linearGradient id="scaleActiveGrad" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#f0c060" />
                        <stop offset="100%" stop-color="#c48530" />
                    </linearGradient>

                    <!-- Scale gradient - passive/low friction -->
                    <linearGradient id="scalePassiveGrad" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#8b9db5" />
                        <stop offset="100%" stop-color="#5a6d80" />
                    </linearGradient>

                    <!-- Glow filters -->
                    <filter id="glowGold" x="-40%" y="-40%" width="180%" height="180%">
                        <feGaussianBlur stdDeviation="4" result="blur" />
                        <feMerge>
                            <feMergeNode in="blur" />
                            <feMergeNode in="SourceGraphic" />
                        </feMerge>
                    </filter>
                    <filter id="glowCyan" x="-40%" y="-40%" width="180%" height="180%">
                        <feGaussianBlur stdDeviation="3.5" result="blur" />
                        <feMerge>
                            <feMergeNode in="blur" />
                            <feMergeNode in="SourceGraphic" />
                        </feMerge>
                    </filter>
                    <filter id="glowGreen" x="-40%" y="-40%" width="180%" height="180%">
                        <feGaussianBlur stdDeviation="3" result="blur" />
                        <feMerge>
                            <feMergeNode in="blur" />
                            <feMergeNode in="SourceGraphic" />
                        </feMerge>
                    </filter>
                    <filter id="glowRed" x="-40%" y="-40%" width="180%" height="180%">
                        <feGaussianBlur stdDeviation="4" result="blur" />
                        <feMerge>
                            <feMergeNode in="blur" />
                            <feMergeNode in="SourceGraphic" />
                        </feMerge>
                    </filter>
                    <filter id="softShadow" x="-20%" y="-10%" width="140%" height="160%">
                        <feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#000000" flood-opacity="0.5" />
                    </filter>

                    <!-- Noise texture for ground -->
                    <filter id="groundNoise">
                        <feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" result="noise" />
                        <feColorMatrix type="saturate" values="0" in="noise" result="grayNoise" />
                        <feBlend in="SourceGraphic" in2="grayNoise" mode="multiply" result="textured" />
                        <feComponentTransfer in="textured">
                            <feFuncA type="linear" slope="1" />
                        </feComponentTransfer>
                    </filter>

                    <!-- Arrow marker -->
                    <marker id="arrowCyan" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
                        <path d="M0,0 L8,4 L0,8 L2,4 Z" fill="#58a6ff" />
                    </marker>
                    <marker id="arrowRed" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
                        <path d="M0,0 L8,4 L0,8 L2,4 Z" fill="#f85149" />
                    </marker>
                    <marker id="arrowGreen" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
                        <path d="M0,0 L8,4 L0,8 L2,4 Z" fill="#3fb950" />
                    </marker>
                    <marker id="arrowAmber" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto">
                        <path d="M0,0 L7,3.5 L0,7 L1.8,3.5 Z" fill="#d4a853" />
                    </marker>

                    <!-- Clip path for ground surface highlight -->
                    <clipPath id="groundClip">
                        <rect x="20" y="435" width="860" height="165" />
                    </clipPath>
                </defs>

                <!-- Background subtle grid -->
                <g opacity="0.06">
                    <pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
                        <path d="M30 0L0 0L0 30" fill="none" stroke="#ffffff" stroke-width="0.5" />
                    </pattern>
                    <rect x="0" y="0" width="900" height="600" fill="url(#grid)" />
                </g>

                <!-- Ground surface -->
                <g id="groundGroup">
                    <!-- Ground body -->
                    <rect id="groundBody" x="20" y="440" width="860" height="160" rx="4" fill="url(#groundGrad)"
                    filter="url(#groundNoise)" />
                    <!-- Ground top highlight line -->
                    <line x1="25" y1="440" x2="875" y2="440" stroke="#7a7d84" stroke-width="1.5" opacity="0.7" />
                    <!-- Ground texture particles -->
                    <g opacity="0.25">
                        <circle cx="120" cy="465" r="1.2" fill="#9a9da4" />
                        <circle cx="340" cy="478" r="0.8" fill="#9a9da4" />
                        <circle cx="560" cy="460" r="1" fill="#9a9da4" />
                        <circle cx="710" cy="490" r="1.3" fill="#9a9da4" />
                        <circle cx="800" cy="472" r="0.9" fill="#9a9da4" />
                        <circle cx="200" cy="500" r="1.1" fill="#8a8d94" />
                        <circle cx="450" cy="510" r="0.7" fill="#8a8d94" />
                        <circle cx="630" cy="495" r="1.4" fill="#8a8d94" />
                    </g>
                </g>

                <!-- Ground contact zone highlight - dynamically shown -->
                <g id="contactHighlight" opacity="0">
                    <ellipse cx="450" cy="442" rx="180" ry="18" fill="none" stroke="#f0c060" stroke-width="2.5"
                    stroke-dasharray="8 4" filter="url(#glowGold)" opacity="0.8">
                    <animate attributeName="stroke-dashoffset" from="0" to="-24" dur="1.5s" repeatCount="indefinite" />
                </ellipse>
            </g>

            <!-- Snake body segments -->
            <g id="snakeBodyGroup" filter="url(#softShadow)">
                <!-- Segment 1 (left) -->
                <g id="segment1" class="segment" data-index="0">
                    <rect x="100" y="280" width="140" height="85" rx="12" fill="url(#bodyGrad)" stroke="#4a5748"
                    stroke-width="1.5" />
                    <!-- Ventral surface (腹部) highlight -->
                    <rect x="104" y="345" width="132" height="16" rx="6" fill="#252f23" stroke="#3a4538"
                    stroke-width="1" />
                    <!-- Scale slots (斜槽) - 15° inclination -->
                    <g class="slots-group">
                        <rect x="140" y="356" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 144, 359)"
                        stroke="#2a3328" stroke-width="0.8" />
                        <rect x="170" y="356" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 174, 359)"
                        stroke="#2a3328" stroke-width="0.8" />
                        <rect x="200" y="356" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 204, 359)"
                        stroke="#2a3328" stroke-width="0.8" />
                    </g>
                    <!-- Scales for segment 1 -->
                    <g class="scales-group" id="scales1">
                        <!-- These will be dynamically generated/updated -->
                    </g>
                </g>

                <!-- Segment 2 (center) -->
                <g id="segment2" class="segment" data-index="1">
                    <rect x="280" y="275" width="150" height="88" rx="13" fill="url(#bodyGrad)" stroke="#4a5748"
                    stroke-width="1.5" />
                    <rect x="284" y="343" width="142" height="16" rx="6" fill="#252f23" stroke="#3a4538"
                    stroke-width="1" />
                    <g class="slots-group">
                        <rect x="325" y="354" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 329, 357)"
                        stroke="#2a3328" stroke-width="0.8" />
                        <rect x="358" y="354" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 362, 357)"
                        stroke="#2a3328" stroke-width="0.8" />
                        <rect x="391" y="354" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 395, 357)"
                        stroke="#2a3328" stroke-width="0.8" />
                    </g>
                    <g class="scales-group" id="scales2">
                    </g>
                </g>

                <!-- Segment 3 (right) -->
                <g id="segment3" class="segment" data-index="2">
                    <rect x="470" y="278" width="145" height="86" rx="12" fill="url(#bodyGrad)" stroke="#4a5748"
                    stroke-width="1.5" />
                    <rect x="474" y="344" width="137" height="16" rx="6" fill="#252f23" stroke="#3a4538"
                    stroke-width="1" />
                    <g class="slots-group">
                        <rect x="515" y="355" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 519, 358)"
                        stroke="#2a3328" stroke-width="0.8" />
                        <rect x="548" y="355" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 552, 358)"
                        stroke="#2a3328" stroke-width="0.8" />
                        <rect x="581" y="355" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 585, 358)"
                        stroke="#2a3328" stroke-width="0.8" />
                    </g>
                    <g class="scales-group" id="scales3">
                    </g>
                </g>

                <!-- Segment 4 (far right) -->
                <g id="segment4" class="segment" data-index="3">
                    <rect x="655" y="281" width="135" height="83" rx="11" fill="url(#bodyGrad)" stroke="#4a5748"
                    stroke-width="1.5" />
                    <rect x="659" y="343" width="127" height="15" rx="5" fill="#252f23" stroke="#3a4538"
                    stroke-width="1" />
                    <g class="slots-group">
                        <rect x="698" y="354" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 702, 357)"
                        stroke="#2a3328" stroke-width="0.8" />
                        <rect x="730" y="354" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 734, 357)"
                        stroke="#2a3328" stroke-width="0.8" />
                        <rect x="762" y="354" width="8" height="6" rx="1" fill="#1a2018" transform="rotate(-15, 766, 357)"
                        stroke="#2a3328" stroke-width="0.8" />
                    </g>
                    <g class="scales-group" id="scales4">
                    </g>
                </g>
            </g>

            <!-- Force arrows and annotations - dynamic group -->
            <g id="annotationsGroup">
                <!-- Horizontal motion arrow -->
                <g id="arrowHorizontal" opacity="0">
                    <line x1="380" y1="520" x2="580" y2="520" stroke="#58a6ff" stroke-width="2.5"
                    marker-end="url(#arrowCyan)" stroke-dasharray="6 3" filter="url(#glowCyan)" />
                    <text x="480" y="540" text-anchor="middle" font-family="'SF Mono','Consolas',monospace" font-size="12"
                    fill="#58a6ff" letter-spacing="0.05em">纵向滑动方向(低摩擦)</text>
                </g>

                <!-- Vertical/diagonal force arrow -->
                <g id="arrowVertical" opacity="0">
                    <line x1="450" y1="500" x2="450" y2="400" stroke="#f85149" stroke-width="2.5"
                    marker-end="url(#arrowRed)" stroke-dasharray="6 3" filter="url(#glowRed)" />
                    <text x="460" y="460" text-anchor="start" font-family="'SF Mono','Consolas',monospace" font-size="12"
                    fill="#f85149" letter-spacing="0.05em">横向压地力(高摩擦)</text>
                </g>

                <!-- IFR annotation -->
                <g id="ifrBadge" opacity="0.9">
                    <rect x="620" y="70" width="240" height="48" rx="24" fill="rgba(22,27,34,0.85)"
                    stroke="rgba(212,168,83,0.5)" stroke-width="1.2" />
                    <circle cx="648" cy="94" r="7" fill="#d4a853" filter="url(#glowGold)" />
                    <text x="664" y="89" font-family="'Georgia',serif" font-size="13" fill="#d4a853"
                    letter-spacing="0.04em" font-style="italic">理想解 IFR</text>
                    <text x="664" y="107" font-family="'PingFang SC','Microsoft YaHei',sans-serif" font-size="10"
                    fill="#8b949e" letter-spacing="0.03em">零电机 · 纯被动自适应</text>
                </g>

                <!-- Friction coefficient indicators -->
                <g id="frictionIndicator" opacity="0">
                    <!-- High friction - transverse -->
                    <rect x="55" y="490" width="110" height="55" rx="10" fill="rgba(248,81,73,0.1)"
                    stroke="rgba(248,81,73,0.4)" stroke-width="1.5" />
                    <text x="110" y="512" text-anchor="middle" font-family="'SF Mono','Consolas',monospace" font-size="11"
                    fill="#f85149" letter-spacing="0.04em">横向 μ<sub>⊥</sub> ≈ 高</text>
                    <text x="110" y="532" text-anchor="middle" font-family="'SF Mono','Consolas',monospace" font-size="10"
                    fill="#f85149" opacity="0.7">鳞片压平 · 抓地</text>
                    <!-- Low friction - longitudinal -->
                    <rect x="730" y="490" width="120" height="55" rx="10" fill="rgba(63,185,80,0.1)"
                    stroke="rgba(63,185,80,0.4)" stroke-width="1.5" />
                    <text x="790" y="512" text-anchor="middle" font-family="'SF Mono','Consolas',monospace" font-size="11"
                    fill="#3fb950" letter-spacing="0.04em">纵向 μ<sub>∥</sub> ≈ 低</text>
                    <text x="790" y="532" text-anchor="middle" font-family="'SF Mono','Consolas',monospace" font-size="10"
                    fill="#3fb950" opacity="0.7">鳞片滑行 · 顺滑</text>
                </g>
            </g>

            <!-- Scale detail callout -->
            <g id="scaleCallout" opacity="0.85">
                <line x1="430" y1="310" x2="530" y2="195" stroke="#d4a853" stroke-width="1" stroke-dasharray="4 3"
                opacity="0.6" />
                <circle cx="430" cy="310" r="4" fill="#d4a853" filter="url(#glowGold)" />
                <rect x="470" y="155" width="200" height="70" rx="8" fill="rgba(22,27,34,0.9)"
                stroke="rgba(212,168,83,0.45)" stroke-width="1" />
                <text x="485" y="176" font-family="'SF Mono','Consolas',monospace" font-size="10" fill="#d4a853"
                letter-spacing="0.04em">鳞片参数</text>
                <text x="485" y="193" font-family="'PingFang SC','Microsoft YaHei',sans-serif" font-size="10"
                fill="#c9d1d9">翘角 25° · 长 4mm · 槽倾角 15°</text>
                <text x="485" y="210" font-family="'PingFang SC','Microsoft YaHei',sans-serif" font-size="10"
                fill="#8b949e">间距 2mm · 厚 0.3mm · 聚氨酯</text>
            </g>

            <!-- Particle effects for friction visualization -->
            <g id="frictionParticles" opacity="0">
            </g>
        </svg>
    </div>

    <!-- Controls -->
    <div class="controls-panel" id="controlsPanel">
        <button class="btn play-btn" id="btnAutoPlay" title="自动循环演示">
            ▶ 自动演示
        </button>
        <div class="btn-group" id="modeGroup">
            <button class="btn active" data-mode="transverse" id="btnTransverse">
                ←→ 横向抓地
            </button>
            <button class="btn" data-mode="longitudinal" id="btnLongitudinal">
                ↑↓ 纵向顺滑
            </button>
            <button class="btn" data-mode="free" id="btnFree">
                ◇ 自由状态
            </button>
        </div>
        <div class="slider-group">
            <label for="forceSlider">接触力度</label>
            <input type="range" id="forceSlider" min="0" max="100" value="60" />
            <span class="val-display" id="forceVal">60%</span>
        </div>
    </div>

    <!-- Legend -->
    <div class="legend-mini">
        <span><span class="dot-sm gold"></span> 鳞片(弹性聚氨酯)</span>
        <span><span class="dot-sm cyan"></span> 纵向滑动(低摩擦)</span>
        <span><span class="dot-sm red"></span> 横向压地(高摩擦)</span>
        <span><span class="dot-sm green"></span> 弹性恢复</span>
    </div>
</div>

<script>
    (function() {
        // --- DOM references ---
        const svg = document.getElementById('mainSvg');
        const btnAutoPlay = document.getElementById('btnAutoPlay');
        const btnTransverse = document.getElementById('btnTransverse');
        const btnLongitudinal = document.getElementById('btnLongitudinal');
        const btnFree = document.getElementById('btnFree');
        const forceSlider = document.getElementById('forceSlider');
        const forceVal = document.getElementById('forceVal');
        const modeGroup = document.getElementById('modeGroup');

        const contactHighlight = document.getElementById('contactHighlight');
        const arrowHorizontal = document.getElementById('arrowHorizontal');
        const arrowVertical = document.getElementById('arrowVertical');
        const frictionIndicator = document.getElementById('frictionIndicator');
        const frictionParticles = document.getElementById('frictionParticles');
        const ifrBadge = document.getElementById('ifrBadge');
        const scaleCallout = document.getElementById('scaleCallout');

        // --- State ---
        let currentMode = 'transverse'; // 'transverse' | 'longitudinal' | 'free'
        let forceAmount = 60; // 0-100
        let isAutoPlaying = false;
        let autoPlayTimer = null;
        let autoPlayPhase = 0; // 0: transverse, 1: free, 2: longitudinal, 3: free
        let animFrameId = null;

        // --- Segment and scale definitions ---
        // Each segment has slots at specific x positions (in SVG coords)
        const segmentDefs = [
            { id: 'segment1', slots: [144, 174, 204], bodyBottom: 359, groundY: 440 },
            { id: 'segment2', slots: [329, 362, 395], bodyBottom: 357, groundY: 440 },
            { id: 'segment3', slots: [519, 552, 585], bodyBottom: 358, groundY: 440 },
            { id: 'segment4', slots: [702, 734, 766], bodyBottom: 357, groundY: 440 },
        ];

        // Scale data: for each slot, we create a scale path
        const scaleData = []; // { slotX, bodyBottomY, groundY, segmentIndex, slotIndex }

        segmentDefs.forEach((seg, segIdx) => {
            seg.slots.forEach((slotX, slotIdx) => {
                scaleData.push({
                    slotX: slotX,
                    bodyBottomY: seg.bodyBottom,
                    groundY: seg.groundY,
                    segmentIndex: segIdx,
                    slotIndex: slotIdx,
                    id: `scale-${segIdx}-${slotIdx}`,
                });
            });
        });

        // --- Create scale SVG elements ---
        const scalesGroups = {
            'scales1': document.getElementById('scales1'),
            'scales2': document.getElementById('scales2'),
            'scales3': document.getElementById('scales3'),
            'scales4': document.getElementById('scales4'),
        };

        const allScaleElements = [];

        scaleData.forEach((sd) => {
            const groupKey = `scales${sd.segmentIndex + 1}`;
            const parentGroup = scalesGroups[groupKey];
            if (!parentGroup) return;

            // Create scale path group
            const scaleG = document.createElementNS('http://www.w3.org/2000/svg', 'g');
            scaleG.setAttribute('class', 'scale-instance');
            scaleG.setAttribute('data-id', sd.id);
            scaleG.setAttribute('data-slot-x', sd.slotX);
            scaleG.setAttribute('data-body-bottom', sd.bodyBottomY);

            // Main scale body - thick path representing the thin elastic sheet
            const scalePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            scalePath.setAttribute('class', 'scale-path');
            scalePath.setAttribute('fill', 'none');
            scalePath.setAttribute('stroke', 'url(#scaleActiveGrad)');
            scalePath.setAttribute('stroke-width', '5');
            scalePath.setAttribute('stroke-linecap', 'round');
            scalePath.setAttribute('stroke-linejoin', 'round');
            scalePath.setAttribute('filter', 'url(#glowGold)');
            scalePath.setAttribute('data-id', sd.id);

            // Scale tip highlight
            const scaleTip = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
            scaleTip.setAttribute('class', 'scale-tip');
            scaleTip.setAttribute('r', '3.5');
            scaleTip.setAttribute('fill', '#f0c060');
            scaleTip.setAttribute('filter', 'url(#glowGold)');
            scaleTip.setAttribute('data-id', sd.id);

            scaleG.appendChild(scalePath);
            scaleG.appendChild(scaleTip);
            parentGroup.appendChild(scaleG);

            allScaleElements.push({
                group: scaleG,
                path: scalePath,
                tip: scaleTip,
                data: sd,
            });
        });

        // --- Compute scale geometry based on mode and force ---
        function getScaleState(sd, mode, force) {
            const slotX = sd.slotX;
            const bodyBottomY = sd.bodyBottomY;
            const groundY = sd.groundY;
            const scaleLength = 52; // SVG units representing ~4mm
            const freeAngle = 25; // degrees -翘角
            const slotAngle = -15; // degrees - 槽倾角(负表示向后倾斜)

            // Root point: where scale emerges from slot
            const rootX = slotX;
            const rootY = bodyBottomY + 2;

            // In free state: scale extends down-backward, tip curves up (翘起25°)
            // The scale emerges from the 15° slot, then the free end tilts up 25° relative to horizontal
            // Effectively the tip is at angle = -15° + 25° = 10° above horizontal? No...
            // The scale comes out of a 15° backward-inclined slot, so it initially goes down-backward at ~15° from vertical
            // Then the free end翘起25° means the tip is 25° above the line of the slot
            // In side view: slot inclines 15° backward from vertical -> scale base direction is 15° backward from downward
            // Free end翘起25° -> tip is 25° above that base direction
            // So total angle from horizontal: the scale goes mostly downward with a slight backward lean

            // Simplified model:
            // - Root at (rootX, rootY)
            // - Scale extends roughly downward and slightly backward
            // - In FREE state: tip is at angle such that the scale curves back up slightly (25°翘角)
            // - In TRANSVERSE (pressed): tip is pushed forward and down, contacting ground, scale flattens
            // - In LONGITUDINAL: scale can slide, tip stays near ground but with minimal resistance

            const rad = (deg) => deg * Math.PI / 180;

            // Base direction (from slot): 15° backward from vertical -> 105° from positive x-axis
            const baseAngle = 105; // degrees from positive x (pointing down-right-backward)
            const baseAngleRad = rad(baseAngle);

            // Free state tip position (翘起25° from base direction -> tip is 25° higher/closer to body)
            const freeTipAngle = baseAngle - 25; //翘起means tip rises, reducing the downward angle
            const freeTipAngleRad = rad(freeTipAngle);

            // Contact state: scale is pressed flat against ground
            // Tip is at ground level or slightly below, scale is flatter

            let tipX, tipY;
            let controlOffsetX, controlOffsetY;

            if (mode === 'free') {
                // Free state: scale翘起, tip well above ground
                const tipDist = scaleLength * (1 - force * 0.003);
                tipX = rootX + tipDist * Math.cos(freeTipAngleRad);
                tipY = rootY + tipDist * Math.sin(freeTipAngleRad);
                // Control point for bezier - gives the scale a slight S-curve
                const cpDist = scaleLength * 0.55;
                const cpAngle = baseAngle - 8;
                const cpAngleRad = rad(cpAngle);
                controlOffsetX = cpDist * Math.cos(cpAngleRad);
                controlOffsetY = cpDist * Math.sin(cpAngleRad);
            } else if (mode === 'transverse') {
                // Transverse: scale pressed flat, tip contacts ground
                // Force pushes scale down and forward, flattening it
                const flattenFactor = 0.15 + force * 0.0085; // 0.15 to 1.0
                const tipDist = scaleLength * (0.85 + flattenFactor * 0.15);
                // Tip moves toward ground and slightly forward
                const targetTipY = groundY - 2 + force * 0.06;
                const targetTipX = rootX + scaleLength * 0.7 * Math.cos(rad(baseAngle - 15));
                tipX = rootX + (targetTipX - rootX) * flattenFactor;
                tipY = rootY + (targetTipY - rootY) * flattenFactor;
                // Ensure tip doesn't go below ground
                if (tipY > groundY - 0.5) tipY = groundY - 0.5;
                // Flatter control point
                const cpDist = scaleLength * 0.5;
                const cpAngle = baseAngle - 20 * flattenFactor;
                const cpAngleRad = rad(Math.max(cpAngle, 75));
                controlOffsetX = cpDist * Math.cos(cpAngleRad);
                controlOffsetY = cpDist * Math.sin(cpAngleRad);
            } else {
                // Longitudinal: scale slides along ground, low resistance
                // Scale is in contact but oriented for easy sliding
                const slideFactor = 0.3 + force * 0.005;
                const tipDist = scaleLength * 0.9;
                // Tip is near ground but the scale orientation allows sliding
                const targetTipY = groundY - 1.5 + force * 0.04;
                const targetTipX = rootX + scaleLength * 0.8 * Math.cos(rad(baseAngle - 5));
                tipX = rootX + (targetTipX - rootX) * slideFactor;
                tipY = rootY + (targetTipY - rootY) * slideFactor;
                if (tipY > groundY - 0.3) tipY = groundY - 0.3;
                const cpDist = scaleLength * 0.5;
                const cpAngle = baseAngle - 10;
                const cpAngleRad = rad(Math.max(cpAngle, 78));
                controlOffsetX = cpDist * Math.cos(cpAngleRad);
                controlOffsetY = cpDist * Math.sin(cpAngleRad);
            }

            return {
                rootX,
                rootY,
                tipX,
                tipY,
                controlX: rootX + controlOffsetX,
                controlY: rootY + controlOffsetY,
                isContactingGround: tipY >= (sd.groundY - 3),
                contactIntensity: mode === 'transverse' ? force / 100 : (mode === 'longitudinal' ? force * 0.4 / 100 : 0),
            };
        }

        // --- Render all scales ---
        function renderAllScales(mode, force) {
            allScaleElements.forEach((el) => {
                const state = getScaleState(el.data, mode, force);
                const { rootX, rootY, tipX, tipY, controlX, controlY, isContactingGround, contactIntensity } =
                state;

                // Update path - use quadratic bezier for smooth curve
                const d =
                    `M${rootX},${rootY} Q${controlX},${controlY} ${tipX},${tipY}`;
                el.path.setAttribute('d', d);

                // Update tip circle
                el.tip.setAttribute('cx', tipX);
                el.tip.setAttribute('cy', tipY);

                // Color based on state
                if (mode === 'transverse' && contactIntensity > 0.3) {
                    // High friction - warm gold/amber
                    el.path.setAttribute('stroke', 'url(#scaleActiveGrad)');
                    el.path.setAttribute('stroke-width', '5.5');
                    el.path.setAttribute('opacity', '1');
                    el.tip.setAttribute('fill', '#f0c060');
                    el.tip.setAttribute('r', '4');
                    el.tip.setAttribute('opacity', '1');
                } else if (mode === 'longitudinal' && contactIntensity > 0.1) {
                    // Low friction - cooler tone
                    el.path.setAttribute('stroke', 'url(#scalePassiveGrad)');
                    el.path.setAttribute('stroke-width', '4.5');
                    el.path.setAttribute('opacity', '0.85');
                    el.tip.setAttribute('fill', '#8b9db5');
                    el.tip.setAttribute('r', '3');
                    el.tip.setAttribute('opacity', '0.8');
                } else {
                    // Free state - muted but visible
                    el.path.setAttribute('stroke', '#b8a070');
                    el.path.setAttribute('stroke-width', '4');
                    el.path.setAttribute('opacity', '0.75');
                    el.tip.setAttribute('fill', '#c4a870');
                    el.tip.setAttribute('r', '3');
                    el.tip.setAttribute('opacity', '0.7');
                }

                // Glow intensity
                if (mode === 'transverse' && contactIntensity > 0.5) {
                    el.path.setAttribute('filter', 'url(#glowGold)');
                    el.tip.setAttribute('filter', 'url(#glowGold)');
                } else if (mode === 'longitudinal') {
                    el.path.setAttribute('filter', 'url(#glowCyan)');
                    el.tip.setAttribute('filter', 'url(#glowCyan)');
                } else {
                    el.path.setAttribute('filter', 'none');
                    el.tip.setAttribute('filter', 'none');
                }
            });
        }

        // --- Update annotations and visual effects ---
        function updateAnnotations(mode, force) {
            // Contact highlight
            if (mode === 'transverse' && force > 30) {
                contactHighlight.setAttribute('opacity', force / 100 * 0.9);
            } else if (mode === 'longitudinal' && force > 20) {
                contactHighlight.setAttribute('opacity', force / 100 * 0.35);
            } else {
                contactHighlight.setAttribute('opacity', '0');
            }

            // Arrows
            if (mode === 'transverse') {
                arrowVertical.setAttribute('opacity', force / 100 * 1);
                arrowHorizontal.setAttribute('opacity', '0');
            } else if (mode === 'longitudinal') {
                arrowHorizontal.setAttribute('opacity', force / 100 * 1);
                arrowVertical.setAttribute('opacity', '0');
            } else {
                arrowVertical.setAttribute('opacity', '0');
                arrowHorizontal.setAttribute('opacity', '0');
            }

            // Friction indicators
            if (mode !== 'free' && force > 20) {
                frictionIndicator.setAttribute('opacity', '0.9');
            } else if (mode === 'free') {
                frictionIndicator.setAttribute('opacity', '0');
            } else {
                frictionIndicator.setAttribute('opacity', force / 100 * 0.8);
            }

            // IFR badge - always visible but subtle
            ifrBadge.setAttribute('opacity', '0.85');

            // Scale callout
            scaleCallout.setAttribute('opacity', '0.8');

            // Friction particles
            if (mode === 'transverse' && force > 40) {
                frictionParticles.setAttribute('opacity', '0.7');
                updateFrictionParticles('high', force);
            } else if (mode === 'longitudinal' && force > 30) {
                frictionParticles.setAttribute('opacity', '0.4');
                updateFrictionParticles('low', force);
            } else {
                frictionParticles.setAttribute('opacity', '0');
                frictionParticles.innerHTML = '';
            }
        }

        // --- Friction particles ---
        function updateFrictionParticles(type, force) {
            frictionParticles.innerHTML = '';
            const count = type === 'high' ? Math.floor(force * 0.3) : Math.floor(force * 0.12);
            const color = type === 'high' ? '#f0c060' : '#8b9db5';
            const spreadX = type === 'high' ? 200 : 300;
            const spreadY = type === 'high' ? 25 : 15;

            for (let i = 0; i < count; i++) {
                const particle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
                const cx = 350 + Math.random() * spreadX;
                const cy = 432 + Math.random() * spreadY;
                const r = 0.8 + Math.random() * 2.2;
                const opacity = 0.3 + Math.random() * 0.7;
                particle.setAttribute('cx', cx);
                particle.setAttribute('cy', cy);
                particle.setAttribute('r', r);
                particle.setAttribute('fill', color);
                particle.setAttribute('opacity', opacity);
                // Animation
                const anim = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
                anim.setAttribute('attributeName', 'opacity');
                anim.setAttribute('values', `${opacity};${opacity*0.2};${opacity}`);
                anim.setAttribute('dur', `${0.6 + Math.random()*1.2}s`);
                anim.setAttribute('repeatCount', 'indefinite');
                particle.appendChild(anim);

                const animY = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
                animY.setAttribute('attributeName', 'cy');
                animY.setAttribute('values',
                `${cy};${cy - 3 - Math.random()*6};${cy}`);
                animY.setAttribute('dur', `${0.8 + Math.random()*1.5}s`);
                animY.setAttribute('repeatCount', 'indefinite');
                particle.appendChild(animY);

                frictionParticles.appendChild(particle);
            }
        }

        // --- Update everything ---
        function updateAll(mode, force) {
            renderAllScales(mode, force);
            updateAnnotations(mode, force);
        }

        // --- Mode switching ---
        function setMode(mode) {
            currentMode = mode;
            // Update button states
            document.querySelectorAll('#modeGroup .btn').forEach(b => b.classList.remove('active'));
            if (mode === 'transverse') btnTransverse.classList.add('active');
            if (mode === 'longitudinal') btnLongitudinal.classList.add('active');
            if (mode === 'free') btnFree.classList.add('active');
            updateAll(mode, forceAmount);
        }

        // --- Auto-play ---
        function startAutoPlay() {
            if (isAutoPlaying) return;
            isAutoPlaying = true;
            btnAutoPlay.textContent = '⏸ 停止演示';
            btnAutoPlay.classList.add('playing');
            autoPlayPhase = 0;
            runAutoPlayCycle();
        }

        function stopAutoPlay() {
            isAutoPlaying = false;
            btnAutoPlay.textContent = '▶ 自动演示';
            btnAutoPlay.classList.remove('playing');
            if (autoPlayTimer) clearTimeout(autoPlayTimer);
            autoPlayTimer = null;
            if (animFrameId) cancelAnimationFrame(animFrameId);
            animFrameId = null;
        }

        function runAutoPlayCycle() {
            if (!isAutoPlaying) return;

            const phases = [
                { mode: 'transverse', forceTarget: 75, duration: 1800, label: '横向抓地' },
                { mode: 'transverse', forceTarget: 20, duration: 600, label: '释放' },
                { mode: 'free', forceTarget: 10, duration: 1000, label: '自由恢复' },
                { mode: 'longitudinal', forceTarget: 60, duration: 1600, label: '纵向顺滑' },
                { mode: 'longitudinal', forceTarget: 15, duration: 600, label: '释放' },
                { mode: 'free', forceTarget: 5, duration: 1000, label: '自由恢复' },
            ];

            const phase = phases[autoPlayPhase % phases.length];
            setMode(phase.mode);

            // Animate force to target
            const startForce = forceAmount;
            const targetForce = phase.forceTarget;
            const startTime = performance.now();
            const duration = phase.duration;

            function animateForce(ts) {
                const elapsed = ts - startTime;
                const progress = Math.min(elapsed / duration, 1);
                // Ease in-out
                const eased = progress < 0.5 ?
                    2 * progress * progress :
                    -1 + (4 - 2 * progress) * progress;
                const currentForce = startForce + (targetForce - startForce) * eased;
                forceAmount = Math.round(currentForce);
                forceSlider.value = forceAmount;
                forceVal.textContent = forceAmount + '%';
                updateAll(phase.mode, forceAmount);

                if (progress < 1) {
                    animFrameId = requestAnimationFrame(animateForce);
                } else {
                    forceAmount = targetForce;
                    forceSlider.value = forceAmount;
                    forceVal.textContent = forceAmount + '%';
                    updateAll(phase.mode, forceAmount);
                    autoPlayPhase++;
                    autoPlayTimer = setTimeout(() => {
                        runAutoPlayCycle();
                    }, 300);
                }
            }
            animFrameId = requestAnimationFrame(animateForce);
        }

        // --- Event listeners ---
        btnAutoPlay.addEventListener('click', () => {
            if (isAutoPlaying) {
                stopAutoPlay();
            } else {
                startAutoPlay();
            }
        });

        btnTransverse.addEventListener('click', () => {
            stopAutoPlay();
            setMode('transverse');
        });
        btnLongitudinal.addEventListener('click', () => {
            stopAutoPlay();
            setMode('longitudinal');
        });
        btnFree.addEventListener('click', () => {
            stopAutoPlay();
            setMode('free');
        });

        forceSlider.addEventListener('input', () => {
            forceAmount = parseInt(forceSlider.value);
            forceVal.textContent = forceAmount + '%';
            updateAll(currentMode, forceAmount);
        });

        // --- Keyboard controls ---
        document.addEventListener('keydown', (e) => {
            switch (e.key.toLowerCase()) {
                case '1':
                    stopAutoPlay();
                    setMode('transverse');
                    break;
                case '2':
                    stopAutoPlay();
                    setMode('longitudinal');
                    break;
                case '3':
                    stopAutoPlay();
                    setMode('free');
                    break;
                case ' ':
                    e.preventDefault();
                    if (isAutoPlaying) stopAutoPlay();
                    else startAutoPlay();
                    break;
                case 'arrowleft':
                    forceAmount = Math.max(0, forceAmount - 10);
                    forceSlider.value = forceAmount;
                    forceVal.textContent = forceAmount + '%';
                    updateAll(currentMode, forceAmount);
                    break;
                case 'arrowright':
                    forceAmount = Math.min(100, forceAmount + 10);
                    forceSlider.value = forceAmount;
                    forceVal.textContent = forceAmount + '%';
                    updateAll(currentMode, forceAmount);
                    break;
                default:
                    break;
            }
        });

        // --- Touch support for slider ---
        forceSlider.addEventListener('touchmove', (e) => {
            forceAmount = parseInt(forceSlider.value);
            forceVal.textContent = forceAmount + '%';
            updateAll(currentMode, forceAmount);
        });

        // --- Initial render ---
        setMode('transverse');
        forceAmount = 60;
        forceSlider.value = 60;
        forceVal.textContent = '60%';
        updateAll('transverse', 60);

        // --- Handle SVG container resize ---
        const resizeObserver = new ResizeObserver(() => {
            // SVG viewBox handles scaling automatically
            updateAll(currentMode, forceAmount);
        });
        const svgContainer = document.getElementById('svgContainer');
        if (svgContainer) resizeObserver.observe(svgContainer);

        console.log('🐍 蛇腹鳞片各向异性摩擦原理动画已就绪');
        console.log('   TRIZ IFR:零电机 · 纯被动自适应');
        console.log('   操作:按钮切换模式 | 空格键自动演示 | ← → 调整力度 | 1/2/3 切换状态');
    })();
</script>
</body>
</html>

动画机制与交互

这个动画演绎了您提出的“最后理想化”构想,借助鳞片弹性变形来呈现“横向抓地/纵向顺滑”的被动摩擦分离效应。

  • 核心机制:动画的核心在于鳞片形态的动态计算。它模拟了鳞片根部嵌入倾角为-15°的斜槽,在横向压地时,鳞片受“地面反力”被压平,增大接触面积并呈现暖金色高亮;在纵向滑动时,鳞片顺向滑行,呈现冷色调低摩擦状态。
  • 操作方式:您可以通过下方的按钮组切换“横向抓地”、“纵向顺滑”或“自由状态”。使用力度滑块可以控制接触力的大小,直观感受摩擦差异。点击“自动演示”按钮或按下空格键,动画将循环展示完整的“抓地-释放-滑行-恢复”时序。
  • 视觉风格:整体采用深色科技感主题。鳞片在受压时会产生金色光晕与粒子特效,突出高摩擦接触点;滑动时则切换为青色光效。画面中的箭头、摩擦系数指示器和参数标注,清晰地引导您关注核心创新点。

优化建议: 您可以在代码开头的<style>标签中修改CSS变量(如--accent-gold)来调整主题色;鳞片的物理参数(如翘角25°、长度4mm)位于getScaleState函数开头的变量中,可根据需要微调。

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