分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>高保真 IFR 原理演示:重型多连杆屏幕驱动机构</title>
    <style>
        :root {
            --bg-color: #09090b;
            --grid-color: rgba(39, 39, 42, 0.6);
            --truss-color: #27272a;
            --truss-edge: #52525b;
            --screen-color: #0ea5e9;
            --screen-glow: rgba(14, 165, 233, 0.4);
            --link-color: #d4d4d8;
            --link-stroke: #18181b;
            --motor-color: #f59e0b;
            --traj-color: #10b981;
            --vic-color: #f43f5e;
            --text-main: #f4f4f5;
            --text-muted: #a1a1aa;
            --panel-bg: rgba(9, 9, 11, 0.85);
        }

        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        #app-container {
            position: relative;
            width: 100vw;
            height: 100vh;
        }

        svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        /* 绝对定位的信息面板,小字号,靠边缘,不遮挡主体 */
        .info-panel {
            position: absolute;
            background: var(--panel-bg);
            border: 1px solid #3f3f46;
            padding: 16px;
            border-radius: 8px;
            backdrop-filter: blur(8px);
            -webkit-backdrop-filter: blur(8px);
            pointer-events: none; /* 避免干扰鼠标交互,虽然本例主要是自动播放 */
            z-index: 10;
        }

        .panel-top-left {
            top: 24px;
            left: 24px;
            max-width: 320px;
        }

        .panel-bottom-right {
            bottom: 24px;
            right: 24px;
            max-width: 280px;
        }

        h1 {
            font-size: 14px;
            margin: 0 0 12px 0;
            color: #fff;
            font-weight: 600;
            letter-spacing: 1px;
            border-bottom: 1px solid #3f3f46;
            padding-bottom: 8px;
        }

        .tag-group {
            display: flex;
            gap: 6px;
            margin-bottom: 12px;
            flex-wrap: wrap;
        }

        .tag {
            font-size: 10px;
            padding: 2px 6px;
            background: rgba(245, 158, 11, 0.15);
            color: var(--motor-color);
            border: 1px solid rgba(245, 158, 11, 0.3);
            border-radius: 4px;
        }

        p {
            font-size: 12px;
            line-height: 1.6;
            color: var(--text-muted);
            margin: 0 0 8px 0;
        }
        
        p:last-child {
            margin-bottom: 0;
        }

        .data-row {
            display: flex;
            justify-content: space-between;
            font-size: 12px;
            margin-bottom: 6px;
            border-bottom: 1px dashed #27272a;
            padding-bottom: 4px;
        }

        .data-label { color: var(--text-muted); }
        .data-value { color: var(--text-main); font-weight: bold; }
        .highlight-value { color: var(--vic-color); }

        .legend {
            margin-top: 12px;
            display: flex;
            flex-direction: column;
            gap: 6px;
        }

        .legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 11px;
            color: var(--text-muted);
        }

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

        /* SVG 元素动画与样式 */
        .glow-screen {
            filter: drop-shadow(0 0 15px var(--screen-glow)) drop-shadow(0 0 5px var(--screen-glow));
        }
        
        .glow-motor {
            filter: drop-shadow(0 0 10px rgba(245, 158, 11, 0.6));
        }

        #trajectory-path {
            stroke-dasharray: 6 6;
            animation: dashFlow 20s linear infinite;
        }

        @keyframes dashFlow {
            from { stroke-dashoffset: 1000; }
            to { stroke-dashoffset: 0; }
        }
        
        @keyframes rotateMotor {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }
    </style>
</head>
<body>

<div id="app-container">
    <!-- 背景图层与动画图层 -->
    <svg viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid meet">
        <defs>
            <!-- 背景网格纹理 -->
            <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                <path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="1"/>
            </pattern>
            <!-- 桁架斜纹理 -->
            <pattern id="hatch" width="10" height="10" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
                <line x1="0" y1="0" x2="0" y2="10" stroke="#3f3f46" stroke-width="2" />
            </pattern>
            
            <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                <path d="M 0 1 L 10 5 L 0 9 z" fill="var(--vic-color)" />
            </marker>
        </defs>

        <!-- 背景 -->
        <rect width="100%" height="100%" fill="url(#grid)" />

        <g id="scene-center" transform="translate(0, 0)">
            <!-- 固定桁架 (雷亚架) -->
            <rect x="200" y="200" width="1200" height="80" fill="url(#hatch)" stroke="var(--truss-edge)" stroke-width="4" rx="4"/>
            <text x="800" y="245" fill="var(--text-muted)" font-size="24" text-anchor="middle" font-weight="bold" letter-spacing="8">FIXED TRUSS (固定雷亚架)</text>

            <!-- 轨迹层 -->
            <path id="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="3" opacity="0.7"/>
            
            <!-- 虚拟瞬心连线层 -->
            <line id="vic-line-ad" stroke="var(--vic-color)" stroke-width="1" stroke-dasharray="8 4" opacity="0.6"/>
            <line id="vic-line-bc" stroke="var(--vic-color)" stroke-width="1" stroke-dasharray="8 4" opacity="0.6"/>
            
            <!-- 机构主体层 -->
            <g id="mechanism">
                <!-- 连杆 BC (从动) -->
                <line id="link-bc" stroke="var(--link-color)" stroke-width="16" stroke-linecap="round"/>
                <line id="link-bc-inner" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
                
                <!-- 连杆 AD (主动) -->
                <line id="link-ad" stroke="var(--link-color)" stroke-width="20" stroke-linecap="round"/>
                <line id="link-ad-inner" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>

                <!-- 屏幕桁架 (Coupler 延伸) -->
                <line id="screen-bar" class="glow-screen" stroke="var(--screen-color)" stroke-width="24" stroke-linecap="round"/>
                <!-- 屏幕厚度细节 -->
                <line id="screen-bar-detail" stroke="#fff" stroke-width="4" stroke-linecap="round" opacity="0.8"/>

                <!-- 铰接点 / Pivot Points -->
                <!-- 屏幕上的铰点 D 和 C -->
                <circle id="pivot-d" r="10" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
                <circle id="pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>

                <!-- 固定铰点 B -->
                <circle cx="1000" cy="280" r="12" fill="#52525b" stroke="#18181b" stroke-width="4"/>
                <circle cx="1000" cy="280" r="4" fill="#a1a1aa"/>
                
                <!-- 固定主动铰点 A (电机) -->
                <g class="glow-motor" transform="translate(600, 280)">
                    <circle r="18" fill="var(--motor-color)" stroke="#18181b" stroke-width="4"/>
                    <!-- 旋转指示器 -->
                    <g id="motor-rotator">
                        <line x1="-12" y1="0" x2="12" y2="0" stroke="#18181b" stroke-width="4" stroke-linecap="round"/>
                        <line x1="0" y1="-12" x2="0" y2="12" stroke="#18181b" stroke-width="4" stroke-linecap="round"/>
                    </g>
                    <circle r="4" fill="#fff"/>
                </g>
                
                <!-- 虚拟瞬心 (VIC) 标记 -->
                <g id="vic-point-group" opacity="0">
                    <circle id="vic-point" r="6" fill="var(--vic-color)"/>
                    <circle r="12" fill="none" stroke="var(--vic-color)" stroke-width="2">
                        <animate attributeName="r" values="6;24;6" dur="2s" repeatCount="indefinite"/>
                        <animate attributeName="opacity" values="1;0;1" dur="2s" repeatCount="indefinite"/>
                    </circle>
                    <text x="15" y="4" fill="var(--vic-color)" font-size="12">虚拟瞬心 (VIC)</text>
                </g>
            </g>
        </g>
    </svg>

    <!-- UI 面板 -->
    <div class="info-panel panel-top-left">
        <h1>TRIZ 最终理想解 (IFR)</h1>
        <div class="tag-group">
            <span class="tag">单一驱动源</span>
            <span class="tag">连续动作路径</span>
            <span class="tag">四连杆机构</span>
        </div>
        <p><strong>方案原理:</strong>引入重型四连杆机构,废弃单一转轴。利用曲柄摇杆机构的耦合轨迹特性,将原本离散的“先平移、后旋转”两段式动作,融合成一条连续、丝滑的空间运动曲线。</p>
        <p><strong>核心机理:</strong>屏幕开启初段,受连杆几何约束,瞬时旋转中心位于极远处,屏幕表现为近乎直线的“向外平移抽出”;随后瞬心迅速拉近,屏幕平滑过渡至大角度旋转展开。</p>
        
        <div class="legend">
            <div class="legend-item"><div class="legend-color" style="background: var(--screen-color); box-shadow: 0 0 5px var(--screen-color);"></div> 移动屏幕桁架 (12m×4m 缩比)</div>
            <div class="legend-item"><div class="legend-color" style="background: var(--motor-color);"></div> 主动驱动源 (单一电机)</div>
            <div class="legend-item"><div class="legend-color" style="background: var(--traj-color);"></div> 屏幕远端连续运动轨迹</div>
            <div class="legend-item"><div class="legend-color" style="border-top: 2px dashed var(--vic-color);"></div> 动态虚拟瞬心 (Instant Center) 连线</div>
        </div>
    </div>

    <div class="info-panel panel-bottom-right">
        <div class="data-row">
            <span class="data-label">运行状态:</span>
            <span class="data-value" style="color: var(--traj-color);">自动演示中 / Auto Play</span>
        </div>
        <div class="data-row">
            <span class="data-label">主连杆 (Crank) 长度:</span>
            <span class="data-value">800 mm</span>
        </div>
        <div class="data-row">
            <span class="data-label">从动杆 (Rocker) 长度:</span>
            <span class="data-value">1200 mm</span>
        </div>
        <div class="data-row">
            <span class="data-label">屏幕远端偏移角:</span>
            <span class="data-value" id="ui-angle">0.0°</span>
        </div>
        <div class="data-row">
            <span class="data-label">瞬心(VIC)距表面:</span>
            <span class="data-value highlight-value" id="ui-vic">计算中...</span>
        </div>
    </div>
</div>

<script>
    // 机构几何参数定义 (基于 SVG 坐标系)
    const K = {
        Ax: 600, Ay: 280, // 主动铰点 A (电机)
        Bx: 1000, By: 280, // 从动固定铰点 B
        r1: 160,          // 主连杆 AD 长度 (对应 800mm)
        r2: 240,          // 从动连杆 BC 长度 (对应 1200mm)
        r3: 350,          // 连杆点距离 DC
        screenExtendLeft: 100,  // 屏幕向左延伸
        screenExtendRight: 350, // 屏幕向右延伸 (远端)
    };

    // DOM 元素引用
    const els = {
        linkAD: document.getElementById('link-ad'),
        linkADInner: document.getElementById('link-ad-inner'),
        linkBC: document.getElementById('link-bc'),
        linkBCInner: document.getElementById('link-bc-inner'),
        screenBar: document.getElementById('screen-bar'),
        screenBarDetail: document.getElementById('screen-bar-detail'),
        pivotD: document.getElementById('pivot-d'),
        pivotC: document.getElementById('pivot-c'),
        motorRotator: document.getElementById('motor-rotator'),
        trajPath: document.getElementById('trajectory-path'),
        vicLineAD: document.getElementById('vic-line-ad'),
        vicLineBC: document.getElementById('vic-line-bc'),
        vicPointGroup: document.getElementById('vic-point-group'),
        vicPoint: document.getElementById('vic-point'),
        uiAngle: document.getElementById('ui-angle'),
        uiVic: document.getElementById('ui-vic')
    };

    // 四连杆运动学求解器
    // 给定曲柄角度 theta1,求另外两点 D, C 的坐标
    function solveFourBar(theta1) {
        // 曲柄末端 D 坐标
        const Dx = K.Ax + K.r1 * Math.cos(theta1);
        const Dy = K.Ay + K.r1 * Math.sin(theta1);

        // B 到 D 的距离 Ldb
        const Ldb = Math.hypot(K.Bx - Dx, K.By - Dy);

        // 机构装配干涉保护
        if (Ldb > K.r2 + K.r3 || Ldb < Math.abs(K.r2 - K.r3)) {
            return null; // 无法构成三角形
        }

        // B->D 向量的角度
        const phi = Math.atan2(K.By - Dy, K.Bx - Dx);

        // 余弦定理求角 gamma (三角形 B-D-C,边长 r2, r3, Ldb)
        const cosGamma = (K.r3 * K.r3 + Ldb * Ldb - K.r2 * K.r2) / (2 * K.r3 * Ldb);
        const gamma = Math.acos(Math.max(-1, Math.min(1, cosGamma))); // 防止精度溢出

        // 两种装配形态,取其一 (elbow down configuration)
        const theta3 = phi - gamma; 

        // 从动连杆上铰点 C 坐标
        const Cx = Dx + K.r3 * Math.cos(theta3);
        const Cy = Dy + K.r3 * Math.sin(theta3);

        return { Dx, Dy, Cx, Cy };
    }

    // 计算两条直线的交点 (用于求虚拟瞬心 VIC)
    // 直线1过 (x1,y1) (x2,y2),直线2过 (x3,y3) (x4,y4)
    function getIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
        const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
        if (Math.abs(denom) < 0.1) return null; // 平行或重合,瞬心在无穷远
        const px = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom;
        const py = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom;
        return { x: px, y: py };
    }

    // 动画配置
    const animConfig = {
        startAngle: 0.1,             // 闭合状态角度 (接近水平向右)
        endAngle: Math.PI * 0.85,    // 开启状态角度 (左下)
        speed: 0.0006,               // 运行速度
        pathData: []                 // 预计算的轨迹点
    };

    // 预计算运动轨迹,用于绘制背景虚线
    function precalculatePath() {
        let pathString = "";
        const steps = 100;
        for (let i = 0; i <= steps; i++) {
            const t = i / steps;
            const theta = animConfig.startAngle + t * (animConfig.endAngle - animConfig.startAngle);
            const pts = solveFourBar(theta);
            if (pts) {
                // 计算屏幕远端点坐标
                const dx = pts.Cx - pts.Dx;
                const dy = pts.Cy - pts.Dy;
                const len = Math.hypot(dx, dy);
                const ux = dx / len;
                const uy = dy / len;
                // 远端点 E
                const Ex = pts.Dx - ux * K.screenExtendLeft; // 这里用左端作为轨迹演示,因为视觉上展开幅度更大
                const Ey = pts.Dy - uy * K.screenExtendLeft;
                
                if (i === 0) pathString += `M ${Ex} ${Ey} `;
                else pathString += `L ${Ex} ${Ey} `;
            }
        }
        els.trajPath.setAttribute('d', pathString);
    }

    // 主渲染帧
    function renderFrame(time) {
        // 利用正弦函数实现往复自动播放 (0 到 1 之间振荡)
        const progress = (Math.sin(time * animConfig.speed) + 1) / 2;
        // 映射到角度范围
        const currentTheta = animConfig.startAngle + progress * (animConfig.endAngle - animConfig.startAngle);
        
        // 更新电机视觉旋转
        els.motorRotator.setAttribute('transform', `rotate(${(currentTheta * 180 / Math.PI)} 0 0)`);

        const pts = solveFourBar(currentTheta);
        
        if (pts) {
            const { Dx, Dy, Cx, Cy } = pts;

            // 更新主动连杆 AD
            els.linkAD.setAttribute('x1', K.Ax); els.linkAD.setAttribute('y1', K.Ay);
            els.linkAD.setAttribute('x2', Dx); els.linkAD.setAttribute('y2', Dy);
            els.linkADInner.setAttribute('x1', K.Ax); els.linkADInner.setAttribute('y1', K.Ay);
            els.linkADInner.setAttribute('x2', Dx); els.linkADInner.setAttribute('y2', Dy);

            // 更新从动连杆 BC
            els.linkBC.setAttribute('x1', K.Bx); els.linkBC.setAttribute('y1', K.By);
            els.linkBC.setAttribute('x2', Cx); els.linkBC.setAttribute('y2', Cy);
            els.linkBCInner.setAttribute('x1', K.Bx); els.linkBCInner.setAttribute('y1', K.By);
            els.linkBCInner.setAttribute('x2', Cx); els.linkBCInner.setAttribute('y2', Cy);

            // 计算屏幕延伸端点
            const vecX = Cx - Dx;
            const vecY = Cy - Dy;
            const len = Math.hypot(vecX, vecY);
            const ux = vecX / len;
            const uy = vecY / len;

            const screenStartX = Dx - ux * K.screenExtendLeft;
            const screenStartY = Dy - uy * K.screenExtendLeft;
            const screenEndX = Cx + ux * K.screenExtendRight;
            const screenEndY = Cy + uy * K.screenExtendRight;

            // 更新屏幕 DOM
            els.screenBar.setAttribute('x1', screenStartX); els.screenBar.setAttribute('y1', screenStartY);
            els.screenBar.setAttribute('x2', screenEndX); els.screenBar.setAttribute('y2', screenEndY);
            els.screenBarDetail.setAttribute('x1', screenStartX); els.screenBarDetail.setAttribute('y1', screenStartY);
            els.screenBarDetail.setAttribute('x2', screenEndX); els.screenBarDetail.setAttribute('y2', screenEndY);

            // 更新铰点视觉
            els.pivotD.setAttribute('cx', Dx); els.pivotD.setAttribute('cy', Dy);
            els.pivotC.setAttribute('cx', Cx); els.pivotC.setAttribute('cy', Cy);

            // 计算并更新虚拟瞬心 (VIC)
            const vic = getIntersection(K.Ax, K.Ay, Dx, Dy, K.Bx, K.By, Cx, Cy);
            if (vic && vic.y > -2000 && vic.y < 3000 && vic.x > -2000 && vic.x < 3000) {
                // 瞬心在可视或合理计算范围内
                els.vicPointGroup.setAttribute('opacity', '1');
                els.vicPointGroup.setAttribute('transform', `translate(${vic.x}, ${vic.y})`);
                
                // 绘制延长线
                els.vicLineAD.setAttribute('x1', Dx); els.vicLineAD.setAttribute('y1', Dy);
                els.vicLineAD.setAttribute('x2', vic.x); els.vicLineAD.setAttribute('y2', vic.y);
                
                els.vicLineBC.setAttribute('x1', Cx); els.vicLineBC.setAttribute('y1', Cy);
                els.vicLineBC.setAttribute('x2', vic.x); els.vicLineBC.setAttribute('y2', vic.y);

                // 更新 UI 数据
                const distToScreen = Math.hypot(vic.x - (Dx+Cx)/2, vic.y - (Dy+Cy)/2);
                // 近似换算为物理尺寸,起步极远(>5000),随后迅速拉近
                let realDist = Math.round(distToScreen * 5); 
                if(realDist > 9999) realDist = "∞ (直线平移)";
                else realDist = realDist + " mm";
                
                els.uiVic.innerText = realDist;
                // 根据距离改变颜色提示(距离越远,颜色越趋近平移蓝色;距离近,显示为旋转红色)
                if(distToScreen > 1000) {
                    els.uiVic.style.color = "var(--screen-color)";
                } else {
                    els.uiVic.style.color = "var(--vic-color)";
                }

            } else {
                // 瞬心趋于无穷远 (绝对平移瞬间)
                els.vicPointGroup.setAttribute('opacity', '0');
                els.vicLineAD.setAttribute('x1', 0); els.vicLineAD.setAttribute('y1', 0); els.vicLineAD.setAttribute('x2', 0); els.vicLineAD.setAttribute('y2', 0);
                els.vicLineBC.setAttribute('x1', 0); els.vicLineBC.setAttribute('y1', 0); els.vicLineBC.setAttribute('x2', 0); els.vicLineBC.setAttribute('y2', 0);
                els.uiVic.innerText = "∞ (直线平移)";
                els.uiVic.style.color = "var(--screen-color)";
            }

            // 更新角度 UI
            const angleDeg = (progress * 95).toFixed(1); // 近似模拟开合角
            els.uiAngle.innerText = `${angleDeg}°`;
        }

        requestAnimationFrame(renderFrame);
    }

    // 初始化运行
    document.addEventListener("DOMContentLoaded", () => {
        precalculatePath();
        requestAnimationFrame(renderFrame);
    });

</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>高保真 IFR 原理演示:对开式重型多连杆屏幕驱动机构</title>
    <style>
        :root {
            --bg-color: #09090b;
            --grid-color: rgba(39, 39, 42, 0.6);
            --truss-color: #27272a;
            --truss-edge: #52525b;
            --screen-color: #0ea5e9;
            --screen-color-left: #06b6d4;
            --screen-glow: rgba(14, 165, 233, 0.4);
            --link-color: #d4d4d8;
            --link-stroke: #18181b;
            --motor-color: #f59e0b;
            --shaft-color: #fbbf24;
            --traj-color: #10b981;
            --text-main: #f4f4f5;
            --text-muted: #a1a1aa;
            --panel-bg: rgba(9, 9, 11, 0.85);
            --danger: #ef4444;
        }

        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        #app-container {
            position: relative;
            width: 100vw;
            height: 100vh;
        }

        svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        .info-panel {
            position: absolute;
            background: var(--panel-bg);
            border: 1px solid #3f3f46;
            padding: 16px;
            border-radius: 8px;
            backdrop-filter: blur(8px);
            -webkit-backdrop-filter: blur(8px);
            pointer-events: none;
            z-index: 10;
        }

        .panel-top-left {
            top: 24px;
            left: 24px;
            max-width: 340px;
        }

        .panel-bottom-right {
            bottom: 24px;
            right: 24px;
            max-width: 280px;
        }

        h1 {
            font-size: 14px;
            margin: 0 0 12px 0;
            color: #fff;
            font-weight: 600;
            letter-spacing: 1px;
            border-bottom: 1px solid #3f3f46;
            padding-bottom: 8px;
        }

        .tag-group {
            display: flex;
            gap: 6px;
            margin-bottom: 12px;
            flex-wrap: wrap;
        }

        .tag {
            font-size: 10px;
            padding: 2px 6px;
            background: rgba(245, 158, 11, 0.15);
            color: var(--motor-color);
            border: 1px solid rgba(245, 158, 11, 0.3);
            border-radius: 4px;
        }
        
        .tag-blue {
            background: rgba(14, 165, 233, 0.15);
            color: var(--screen-color);
            border-color: rgba(14, 165, 233, 0.3);
        }

        p {
            font-size: 12px;
            line-height: 1.6;
            color: var(--text-muted);
            margin: 0 0 8px 0;
        }

        .data-row {
            display: flex;
            justify-content: space-between;
            font-size: 12px;
            margin-bottom: 6px;
            border-bottom: 1px dashed #27272a;
            padding-bottom: 4px;
        }

        .data-label { color: var(--text-muted); }
        .data-value { color: var(--text-main); font-weight: bold; }

        .legend {
            margin-top: 12px;
            display: flex;
            flex-direction: column;
            gap: 6px;
        }

        .legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 11px;
            color: var(--text-muted);
        }

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

        .glow-screen { filter: drop-shadow(0 0 12px var(--screen-glow)); }
        .glow-motor { filter: drop-shadow(0 0 10px rgba(245, 158, 11, 0.8)); }
        .glow-shaft { filter: drop-shadow(0 0 4px rgba(251, 191, 36, 0.5)); }

        .trajectory-path {
            stroke-dasharray: 4 6;
            animation: dashFlow 15s linear infinite;
        }

        @keyframes dashFlow {
            from { stroke-dashoffset: 500; }
            to { stroke-dashoffset: 0; }
        }
    </style>
</head>
<body>

<div id="app-container">
    <svg viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid meet">
        <defs>
            <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                <path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="1"/>
                <circle cx="0" cy="0" r="1" fill="#3f3f46" />
            </pattern>
            <pattern id="hatch" width="12" height="12" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
                <line x1="0" y1="0" x2="0" y2="12" stroke="#3f3f46" stroke-width="3" />
            </pattern>
        </defs>

        <!-- 基础网格 -->
        <rect width="100%" height="100%" fill="url(#grid)" />
        
        <!-- 中心对称轴 -->
        <line x1="800" y1="0" x2="800" y2="1000" stroke="#3f3f46" stroke-width="1" stroke-dasharray="10 10" opacity="0.5"/>
        <text x="810" y="50" fill="#52525b" font-size="12" letter-spacing="2">SYMMETRY AXIS / X=800</text>

        <!-- 顶部固定雷亚架 (俯视图) -->
        <g id="fixed-frame">
            <rect x="300" y="160" width="1000" height="100" fill="url(#hatch)" stroke="var(--truss-edge)" stroke-width="4" rx="4"/>
            <text x="800" y="215" fill="#71717a" font-size="20" text-anchor="middle" font-weight="bold" letter-spacing="6">MAIN GANTRY TRUSS (TOP VIEW)</text>
        </g>

        <!-- 中央驱动源与传动轴 -->
        <g id="central-drive">
            <!-- 传动轴 -->
            <line id="shaft-left" class="glow-shaft" x1="800" y1="210" x2="650" y2="210" stroke="var(--shaft-color)" stroke-width="6" stroke-linecap="round"/>
            <line id="shaft-right" class="glow-shaft" x1="800" y1="210" x2="950" y2="210" stroke="var(--shaft-color)" stroke-width="6" stroke-linecap="round"/>
            
            <!-- 中央电机 -->
            <g class="glow-motor" transform="translate(800, 210)">
                <rect x="-30" y="-25" width="60" height="50" fill="#27272a" stroke="var(--motor-color)" stroke-width="3" rx="6"/>
                <circle r="14" fill="var(--motor-color)"/>
                <g id="central-rotator">
                    <line x1="-10" y1="0" x2="10" y2="0" stroke="#18181b" stroke-width="3"/>
                    <line x1="0" y1="-10" x2="0" y2="10" stroke="#18181b" stroke-width="3"/>
                </g>
                <text x="0" y="45" fill="var(--motor-color)" font-size="11" text-anchor="middle" font-weight="bold">SINGLE DRIVE</text>
            </g>
        </g>

        <!-- 轨迹层 -->
        <path id="traj-right" class="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="2" opacity="0.8"/>
        <path id="traj-left" class="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="2" opacity="0.8"/>

        <!-- 右侧机构 -->
        <g id="mech-right">
            <line id="r-link-bc" stroke="var(--link-color)" stroke-width="16" stroke-linecap="round"/>
            <line id="r-link-bc-in" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
            
            <line id="r-link-ad" stroke="var(--link-color)" stroke-width="20" stroke-linecap="round"/>
            <line id="r-link-ad-in" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>

            <!-- 右侧门体 -->
            <line id="r-door" class="glow-screen" stroke="var(--screen-color)" stroke-width="28" stroke-linecap="square"/>
            <line id="r-door-hl" stroke="#fff" stroke-width="4" stroke-linecap="square" opacity="0.6"/>
            <!-- 门体密封齿 -->
            <circle id="r-door-tip" r="10" fill="#fff" stroke="var(--screen-color)" stroke-width="4"/>

            <circle id="r-pivot-d" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
            <circle id="r-pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
            <circle cx="1150" cy="210" r="10" fill="#52525b" stroke="#18181b" stroke-width="3"/> <!-- B点固定 -->
            <circle cx="950" cy="210" r="12" fill="var(--shaft-color)" stroke="#18181b" stroke-width="3"/> <!-- A点主动 -->
        </g>

        <!-- 左侧机构 (对称镜像) -->
        <g id="mech-left">
            <line id="l-link-bc" stroke="var(--link-color)" stroke-width="16" stroke-linecap="round"/>
            <line id="l-link-bc-in" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
            
            <line id="l-link-ad" stroke="var(--link-color)" stroke-width="20" stroke-linecap="round"/>
            <line id="l-link-ad-in" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>

            <!-- 左侧门体 -->
            <line id="l-door" class="glow-screen" stroke="var(--screen-color-left)" stroke-width="28" stroke-linecap="square"/>
            <line id="l-door-hl" stroke="#fff" stroke-width="4" stroke-linecap="square" opacity="0.6"/>
            <circle id="l-door-tip" r="10" fill="#fff" stroke="var(--screen-color-left)" stroke-width="4"/>

            <circle id="l-pivot-d" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
            <circle id="l-pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
            <circle cx="450" cy="210" r="10" fill="#52525b" stroke="#18181b" stroke-width="3"/> <!-- B点对称 -->
            <circle cx="650" cy="210" r="12" fill="var(--shaft-color)" stroke="#18181b" stroke-width="3"/> <!-- A点对称 -->
        </g>
    </svg>

    <!-- UI 面板 -->
    <div class="info-panel panel-top-left">
        <h1>IFR: 对开式连杆驱动系统 (俯视图)</h1>
        <div class="tag-group">
            <span class="tag">单一中央驱动源</span>
            <span class="tag">对称四连杆</span>
            <span class="tag-blue">0°直线平齐 ↔ 90°完全展开</span>
        </div>
        <p><strong>最终理想解:</strong>利用中央电机驱动左右对称的重型多连杆机构,摒弃传统转轴。通过机构耦合特性,屏幕内侧边缘在开启瞬间产生<strong>“向外并远离中心”</strong>的位移,完美避开间隙干涉,随后连续过渡至 90 度垂直开启。</p>
        <p><strong>动作连贯:</strong>整个开合过程平滑且无停顿,无需在直线导轨和平移电机之间切换,极大降低了系统复杂度和故障率。</p>
        
        <div class="legend">
            <div class="legend-item"><div class="legend-color" style="background: var(--screen-color); box-shadow: 0 0 5px var(--screen-color);"></div> 重型屏幕/门体 (闭合呈 180° 一字)</div>
            <div class="legend-item"><div class="legend-color" style="background: var(--motor-color);"></div> 单一中央电机 & 传动轴</div>
            <div class="legend-item"><div class="legend-color" style="background: var(--traj-color);"></div> 门体内侧边缘(防撞尖端) 运动轨迹</div>
        </div>
    </div>

    <div class="info-panel panel-bottom-right">
        <div class="data-row">
            <span class="data-label">运行状态:</span>
            <span class="data-value" style="color: var(--traj-color);">自动循环同步 / Sync</span>
        </div>
        <div class="data-row">
            <span class="data-label">左右门体夹角:</span>
            <span class="data-value" id="ui-total-angle" style="color: var(--screen-color);">180.0°</span>
        </div>
        <div class="data-row">
            <span class="data-label">单侧开启角度:</span>
            <span class="data-value" id="ui-single-angle">0.0°</span>
        </div>
        <div class="data-row">
            <span class="data-label">连杆安全余量:</span>
            <span class="data-value" style="color: var(--traj-color);">正常 (无奇异点)</span>
        </div>
        <div class="data-row">
            <span class="data-label">中央驱动负荷:</span>
            <span class="data-value" id="ui-load">45%</span>
        </div>
    </div>
</div>

<script>
    // --- 运动学核心模型 (基于右侧机构,左侧自动镜像) ---
    const K = {
        Ax: 950, Ay: 210, // 主动曲柄铰点
        Bx: 1150, By: 210, // 固定摇杆铰点
        r1: 170,          // 主动曲柄 AD
        r2: 240,          // 从动摇杆 BC
        r3: 240,          // 连杆体 DC (屏幕装配基准)
        
        // 以下参数在初始化时自动计算,以保证完美的 0度->90度 几何映射
        thetaStart: 0,
        thetaEnd: 0,
        alphaStart: 0,
        
        // 门体相对 D 点的局部坐标 (确保闭合时水平,且内侧对齐 X=804)
        doorLocalStartX: 0, doorLocalStartY: 0,
        doorLocalEndX: 0, doorLocalEndY: 0
    };

    // DOM 元素缓存
    const els = {
        centralRotator: document.getElementById('central-rotator'),
        trajRight: document.getElementById('traj-right'),
        trajLeft: document.getElementById('traj-left'),
        uiTotalAngle: document.getElementById('ui-total-angle'),
        uiSingleAngle: document.getElementById('ui-single-angle'),
        uiLoad: document.getElementById('ui-load'),
        
        right: {
            linkAD: document.getElementById('r-link-ad'), linkADIn: document.getElementById('r-link-ad-in'),
            linkBC: document.getElementById('r-link-bc'), linkBCIn: document.getElementById('r-link-bc-in'),
            door: document.getElementById('r-door'), doorHl: document.getElementById('r-door-hl'),
            doorTip: document.getElementById('r-door-tip'),
            pivotD: document.getElementById('r-pivot-d'), pivotC: document.getElementById('r-pivot-c')
        },
        left: {
            linkAD: document.getElementById('l-link-ad'), linkADIn: document.getElementById('l-link-ad-in'),
            linkBC: document.getElementById('l-link-bc'), linkBCIn: document.getElementById('l-link-bc-in'),
            door: document.getElementById('l-door'), doorHl: document.getElementById('l-door-hl'),
            doorTip: document.getElementById('l-door-tip'),
            pivotD: document.getElementById('l-pivot-d'), pivotC: document.getElementById('l-pivot-c')
        }
    };

    // 四连杆正向求解器
    function solveFourBar(theta) {
        const Dx = K.Ax + K.r1 * Math.cos(theta);
        const Dy = K.Ay + K.r1 * Math.sin(theta);
        const Ldb = Math.hypot(K.Bx - Dx, K.By - Dy);
        
        if (Ldb > K.r2 + K.r3 || Ldb < Math.abs(K.r2 - K.r3)) return null; // 机构卡死
        
        const phi = Math.atan2(K.By - Dy, K.Bx - Dx);
        const cosGamma = (K.r3 * K.r3 + Ldb * Ldb - K.r2 * K.r2) / (2 * K.r3 * Ldb);
        const gamma = Math.acos(Math.max(-1, Math.min(1, cosGamma)));
        
        const theta3 = phi - gamma; // 取外展装配形态
        const Cx = Dx + K.r3 * Math.cos(theta3);
        const Cy = Dy + K.r3 * Math.sin(theta3);
        
        const alpha = Math.atan2(Cy - Dy, Cx - Dx);
        return { Dx, Dy, Cx, Cy, alpha };
    }

    // 初始化计算:寻找精确的 0度 和 90度 映射点
    function initMechanism() {
        // 设定初始曲柄角 (位于第四象限,向右下方)
        K.thetaStart = 2.5; 
        const startRes = solveFourBar(K.thetaStart);
        if (!startRes) return;
        
        K.alphaStart = startRes.alpha;
        
        // 我们需要门体闭合时严格位于 Y=340 的水平线上
        const doorY = 340;
        // 右门内侧起点 (留出中心 8px 间隙,800 是中线)
        const doorStartX_Global = 804;
        // 右门外侧终点 (宽度 380px)
        const doorEndX_Global = 1184;

        // 绑定门体相对 D 点的局部刚性坐标
        K.doorLocalStartX = doorStartX_Global - startRes.Dx;
        K.doorLocalStartY = doorY - startRes.Dy;
        K.doorLocalEndX = doorEndX_Global - startRes.Dx;
        K.doorLocalEndY = doorY - startRes.Dy;

        // 向下搜索曲柄角,直到连杆体旋转 90 度 (Math.PI / 2)
        // 俯视图中,右侧门向外打开对应顺时针旋转 (在屏幕坐标系中 alpha 增加)
        const targetRotation = Math.PI / 2;
        let found = false;
        
        for (let i = 0; i < 1000; i++) {
            let testTheta = K.thetaStart - i * 0.005; // 电机顺时针旋转 (角度减小)
            let res = solveFourBar(testTheta);
            if (res) {
                // 角度差可能跨越 PI 边界,做归一化
                let delta = res.alpha - K.alphaStart;
                while(delta < -Math.PI) delta += Math.PI * 2;
                while(delta > Math.PI) delta -= Math.PI * 2;
                
                if (Math.abs(delta) >= targetRotation) {
                    K.thetaEnd = testTheta;
                    found = true;
                    break;
                }
            } else {
                break;
            }
        }

        if (!found) {
            // 安全后备方案
            K.thetaEnd = K.thetaStart - 1.2;
        }

        // 预计算轨迹路径
        precalculateTrajectories();
    }

    // 预渲染两门尖端的运动轨迹虚线
    function precalculateTrajectories() {
        let pathR = "", pathL = "";
        const steps = 60;
        for (let i = 0; i <= steps; i++) {
            const t = i / steps;
            const theta = K.thetaStart + t * (K.thetaEnd - K.thetaStart);
            const res = solveFourBar(theta);
            if (res) {
                const deltaAlpha = res.alpha - K.alphaStart;
                const cosA = Math.cos(deltaAlpha);
                const sinA = Math.sin(deltaAlpha);
                
                // 右门尖端 (Inner Tip)
                const tipRx = res.Dx + K.doorLocalStartX * cosA - K.doorLocalStartY * sinA;
                const tipRy = res.Dy + K.doorLocalStartX * sinA + K.doorLocalStartY * cosA;
                
                // 左门尖端 (基于对称中线 X=800 镜像)
                const tipLx = 1600 - tipRx;
                
                if (i === 0) {
                    pathR += `M ${tipRx} ${tipRy} `;
                    pathL += `M ${tipLx} ${tipRy} `; // Y 轴一致
                } else {
                    pathR += `L ${tipRx} ${tipRy} `;
                    pathL += `L ${tipLx} ${tipRy} `;
                }
            }
        }
        els.trajRight.setAttribute('d', pathR);
        els.trajLeft.setAttribute('d', pathL);
    }

    // 更新单侧 DOM
    function updateSide(sideEls, isLeft, Ax, Bx, res) {
        // 如果是左侧,执行 X 轴镜像变换 (1600 - X)
        const mirrorX = (x) => isLeft ? 1600 - x : x;
        
        const mAx = mirrorX(Ax);
        const mBx = mirrorX(Bx);
        const mDx = mirrorX(res.Dx);
        const mCx = mirrorX(res.Cx);
        
        // 连杆 AD
        sideEls.linkAD.setAttribute('x1', mAx); sideEls.linkAD.setAttribute('y1', K.Ay);
        sideEls.linkAD.setAttribute('x2', mDx); sideEls.linkAD.setAttribute('y2', res.Dy);
        sideEls.linkADIn.setAttribute('x1', mAx); sideEls.linkADIn.setAttribute('y1', K.Ay);
        sideEls.linkADIn.setAttribute('x2', mDx); sideEls.linkADIn.setAttribute('y2', res.Dy);

        // 连杆 BC
        sideEls.linkBC.setAttribute('x1', mBx); sideEls.linkBC.setAttribute('y1', K.By);
        sideEls.linkBC.setAttribute('x2', mCx); sideEls.linkBC.setAttribute('y2', res.Cy);
        sideEls.linkBCIn.setAttribute('x1', mBx); sideEls.linkBCIn.setAttribute('y1', K.By);
        sideEls.linkBCIn.setAttribute('x2', mCx); sideEls.linkBCIn.setAttribute('y2', res.Cy);

        // 铰点
        sideEls.pivotD.setAttribute('cx', mDx); sideEls.pivotD.setAttribute('cy', res.Dy);
        sideEls.pivotC.setAttribute('cx', mCx); sideEls.pivotC.setAttribute('cy', res.Cy);

        // 门体刚性旋转
        const deltaAlpha = res.alpha - K.alphaStart;
        const cosA = Math.cos(deltaAlpha);
        const sinA = Math.sin(deltaAlpha);

        // 计算右门全局坐标
        const rightStartX = res.Dx + K.doorLocalStartX * cosA - K.doorLocalStartY * sinA;
        const rightStartY = res.Dy + K.doorLocalStartX * sinA + K.doorLocalStartY * cosA;
        const rightEndX = res.Dx + K.doorLocalEndX * cosA - K.doorLocalEndY * sinA;
        const rightEndY = res.Dy + K.doorLocalEndX * sinA + K.doorLocalEndY * cosA;

        // 应用镜像
        const finalStartX = mirrorX(rightStartX);
        const finalEndX = mirrorX(rightEndX);

        // 渲染门体线条
        sideEls.door.setAttribute('x1', finalStartX); sideEls.door.setAttribute('y1', rightStartY);
        sideEls.door.setAttribute('x2', finalEndX); sideEls.door.setAttribute('y2', rightEndY);
        
        sideEls.doorHl.setAttribute('x1', finalStartX); sideEls.doorHl.setAttribute('y1', rightStartY);
        sideEls.doorHl.setAttribute('x2', finalEndX); sideEls.doorHl.setAttribute('y2', rightEndY);

        // 尖端防撞圆
        sideEls.doorTip.setAttribute('cx', finalStartX);
        sideEls.doorTip.setAttribute('cy', rightStartY);

        return deltaAlpha; // 返回实际旋转角用于 UI
    }

    // 动画引擎
    const animSpeed = 0.0007; // 运行速度
    function renderFrame(time) {
        // 利用平滑缓动生成 0~1 的周期往复
        let raw = Math.sin(time * animSpeed);
        // 使用钳制放大产生停顿感 (在闭合和全开状态停留观察)
        let clamped = Math.max(-0.85, Math.min(0.85, raw)) / 0.85;
        // 缓动曲线
        let progress = (Math.sin(clamped * Math.PI / 2) + 1) / 2;

        const currentTheta = K.thetaStart + progress * (K.thetaEnd - K.thetaStart);
        
        // 中央电机视觉旋转 (加速转动以显现传动感)
        els.centralRotator.setAttribute('transform', `rotate(${(currentTheta * -300) % 360} 0 0)`);

        const res = solveFourBar(currentTheta);
        if (res) {
            // 同步驱动右侧与左侧
            const rightRot = updateSide(els.right, false, K.Ax, K.Bx, res);
            updateSide(els.left, true, K.Ax, K.Bx, res);

            // 更新 UI 仪表数据
            // rightRot 是弧度,转为角度。因为绝对值,取 abs
            const singleDeg = Math.abs(rightRot * 180 / Math.PI);
            els.uiSingleAngle.innerText = singleDeg.toFixed(1) + "°";
            
            // 总夹角 = 180 - 2*单侧开启角
            const totalDeg = 180 - 2 * singleDeg;
            els.uiTotalAngle.innerText = totalDeg.toFixed(1) + "°";
            
            // 动态负荷模拟 (启动时最高,展开时降低)
            const load = 15 + Math.abs(Math.sin(progress * Math.PI)) * 65;
            els.uiLoad.innerText = load.toFixed(0) + "%";
        }

        requestAnimationFrame(renderFrame);
    }

    // 启动
    document.addEventListener("DOMContentLoaded", () => {
        initMechanism();
        requestAnimationFrame(renderFrame);
    });

</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>高保真 IFR 原理演示:对开式向后收纳连杆门</title>
    <style>
        :root {
            /* 工业黑/冷蓝主色调 */
            --bg-color: #050505;
            --grid-color: rgba(63, 63, 70, 0.4);
            --stage-front: rgba(16, 185, 129, 0.05);
            --stage-back: rgba(225, 29, 72, 0.03);
            
            --truss-color: #18181b;
            --truss-edge: #3f3f46;
            
            --screen-color: #0ea5e9;
            --screen-color-left: #06b6d4;
            --screen-glow: rgba(14, 165, 233, 0.5);
            
            --link-color: #a1a1aa;
            --link-stroke: #000;
            --motor-color: #f59e0b;
            --shaft-color: #fbbf24;
            --traj-color: #f43f5e;
            
            --text-main: #f4f4f5;
            --text-muted: #a1a1aa;
            --panel-bg: rgba(5, 5, 5, 0.85);
        }

        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        #app-container {
            position: relative;
            width: 100vw;
            height: 100vh;
        }

        svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        .info-panel {
            position: absolute;
            background: var(--panel-bg);
            border: 1px solid #27272a;
            padding: 16px;
            border-radius: 6px;
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            pointer-events: none;
            z-index: 10;
            box-shadow: 0 10px 30px rgba(0,0,0,0.5);
        }

        .panel-top-left {
            top: 24px;
            left: 24px;
            max-width: 340px;
            border-top: 3px solid var(--screen-color);
        }

        .panel-bottom-right {
            bottom: 24px;
            right: 24px;
            max-width: 280px;
        }

        h1 {
            font-size: 14px;
            margin: 0 0 12px 0;
            color: #fff;
            font-weight: 600;
            letter-spacing: 1px;
            border-bottom: 1px dashed #27272a;
            padding-bottom: 8px;
            text-transform: uppercase;
        }

        .tag-group {
            display: flex;
            gap: 6px;
            margin-bottom: 12px;
            flex-wrap: wrap;
        }

        .tag {
            font-size: 10px;
            padding: 3px 6px;
            background: rgba(245, 158, 11, 0.1);
            color: var(--motor-color);
            border: 1px solid rgba(245, 158, 11, 0.3);
            border-radius: 2px;
        }
        
        .tag-blue {
            background: rgba(14, 165, 233, 0.1);
            color: var(--screen-color);
            border-color: rgba(14, 165, 233, 0.3);
        }

        .tag-red {
            background: rgba(244, 63, 94, 0.1);
            color: var(--traj-color);
            border-color: rgba(244, 63, 94, 0.3);
        }

        p {
            font-size: 12px;
            line-height: 1.6;
            color: var(--text-muted);
            margin: 0 0 8px 0;
        }

        .data-row {
            display: flex;
            justify-content: space-between;
            font-size: 12px;
            margin-bottom: 6px;
            border-bottom: 1px dashed #27272a;
            padding-bottom: 4px;
        }

        .data-label { color: #71717a; }
        .data-value { color: var(--text-main); font-weight: bold; }

        .legend {
            margin-top: 12px;
            display: flex;
            flex-direction: column;
            gap: 6px;
            background: rgba(255,255,255,0.02);
            padding: 8px;
            border-radius: 4px;
        }

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

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

        .glow-screen { filter: drop-shadow(0 0 15px var(--screen-glow)); }
        .glow-motor { filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.5)); }

        .trajectory-path {
            stroke-dasharray: 4 6;
            animation: dashFlow 15s linear infinite;
        }

        @keyframes dashFlow {
            from { stroke-dashoffset: 500; }
            to { stroke-dashoffset: 0; }
        }
    </style>
</head>
<body>

<div id="app-container">
    <svg viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid meet">
        <defs>
            <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                <path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="1"/>
                <circle cx="0" cy="0" r="1.5" fill="#3f3f46" />
            </pattern>
            <pattern id="hatch-back" width="16" height="16" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
                <line x1="0" y1="0" x2="0" y2="16" stroke="#27272a" stroke-width="4" />
            </pattern>
            <linearGradient id="front-gradient" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stop-color="transparent"/>
                <stop offset="100%" stop-color="rgba(14, 165, 233, 0.05)"/>
            </linearGradient>
            <linearGradient id="back-gradient" x1="0" y1="1" x2="0" y2="0">
                <stop offset="0%" stop-color="transparent"/>
                <stop offset="100%" stop-color="rgba(244, 63, 94, 0.05)"/>
            </linearGradient>
        </defs>

        <!-- 基础环境划分 -->
        <rect width="100%" height="100%" fill="url(#grid)" />
        
        <!-- 后台区域 (上半部) -->
        <rect x="0" y="0" width="1600" height="450" fill="url(#back-gradient)" />
        <text x="800" y="50" fill="#f43f5e" font-size="24" text-anchor="middle" font-weight="bold" letter-spacing="8" opacity="0.3">STAGE BACK / 舞台后方 (收纳区)</text>
        
        <!-- 观众席区域 (下半部) -->
        <rect x="0" y="450" width="1600" height="550" fill="url(#front-gradient)" />
        <text x="800" y="900" fill="#0ea5e9" font-size="28" text-anchor="middle" font-weight="bold" letter-spacing="12" opacity="0.4">AUDIENCE / 观众席区域</text>
        
        <!-- 中心对齐线与舞台边缘 -->
        <line x1="800" y1="0" x2="800" y2="1000" stroke="#3f3f46" stroke-width="1" stroke-dasharray="8 8" opacity="0.5"/>
        <line x1="0" y1="450" x2="1600" y2="450" stroke="#3f3f46" stroke-width="2" stroke-dasharray="10 5" opacity="0.4"/>

        <!-- 舞台后方:主承重雷亚架 (俯视图) -->
        <g id="fixed-frame">
            <rect x="300" y="100" width="1000" height="120" fill="url(#hatch-back)" stroke="var(--truss-edge)" stroke-width="3" rx="2"/>
            <text x="800" y="165" fill="#52525b" font-size="18" text-anchor="middle" font-weight="bold" letter-spacing="4">MAIN GANTRY (BEHIND SCREEN)</text>
        </g>

        <!-- 舞台后方:中央驱动源与传动轴 -->
        <g id="central-drive">
            <!-- 传动轴 -->
            <line class="glow-motor" x1="800" y1="210" x2="650" y2="210" stroke="var(--shaft-color)" stroke-width="6" stroke-linecap="round"/>
            <line class="glow-motor" x1="800" y1="210" x2="950" y2="210" stroke="var(--shaft-color)" stroke-width="6" stroke-linecap="round"/>
            
            <!-- 中央电机 -->
            <g class="glow-motor" transform="translate(800, 210)">
                <rect x="-35" y="-30" width="70" height="60" fill="#18181b" stroke="var(--motor-color)" stroke-width="3" rx="4"/>
                <circle r="16" fill="var(--motor-color)"/>
                <g id="central-rotator">
                    <line x1="-12" y1="0" x2="12" y2="0" stroke="#000" stroke-width="4"/>
                    <line x1="0" y1="-12" x2="0" y2="12" stroke="#000" stroke-width="4"/>
                </g>
                <text x="0" y="-40" fill="var(--motor-color)" font-size="11" text-anchor="middle" font-weight="bold">DRIVE UNIT</text>
            </g>
        </g>

        <!-- 向后收纳的轨迹层 -->
        <path id="traj-right" class="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="2.5" opacity="0.9"/>
        <path id="traj-left" class="trajectory-path" fill="none" stroke="var(--traj-color)" stroke-width="2.5" opacity="0.9"/>

        <!-- 右侧机构 (均在 Y=450 后方) -->
        <g id="mech-right">
            <!-- 连杆 -->
            <line id="r-link-bc" stroke="var(--link-color)" stroke-width="14" stroke-linecap="round"/>
            <line id="r-link-bc-in" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
            <line id="r-link-ad" stroke="var(--link-color)" stroke-width="18" stroke-linecap="round"/>
            <line id="r-link-ad-in" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>

            <!-- 右侧门体 -->
            <line id="r-door" class="glow-screen" stroke="var(--screen-color)" stroke-width="32" stroke-linecap="square"/>
            <line id="r-door-hl" stroke="#fff" stroke-width="4" stroke-linecap="square" opacity="0.7"/>
            <circle id="r-door-tip" r="12" fill="#fff" stroke="var(--screen-color)" stroke-width="4"/>

            <!-- 铰点 -->
            <circle id="r-pivot-d" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
            <circle id="r-pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
            <circle cx="1180" cy="210" r="10" fill="#52525b" stroke="#000" stroke-width="3"/> <!-- B点(固定) -->
            <circle cx="950" cy="210" r="12" fill="var(--shaft-color)" stroke="#000" stroke-width="3"/> <!-- A点(主动) -->
        </g>

        <!-- 左侧机构 (对称镜像) -->
        <g id="mech-left">
            <line id="l-link-bc" stroke="var(--link-color)" stroke-width="14" stroke-linecap="round"/>
            <line id="l-link-bc-in" stroke="var(--link-stroke)" stroke-width="4" stroke-linecap="round"/>
            <line id="l-link-ad" stroke="var(--link-color)" stroke-width="18" stroke-linecap="round"/>
            <line id="l-link-ad-in" stroke="var(--link-stroke)" stroke-width="6" stroke-linecap="round"/>

            <!-- 左侧门体 -->
            <line id="l-door" class="glow-screen" stroke="var(--screen-color-left)" stroke-width="32" stroke-linecap="square"/>
            <line id="l-door-hl" stroke="#fff" stroke-width="4" stroke-linecap="square" opacity="0.7"/>
            <circle id="l-door-tip" r="12" fill="#fff" stroke="var(--screen-color-left)" stroke-width="4"/>

            <circle id="l-pivot-d" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
            <circle id="l-pivot-c" r="8" fill="#fff" stroke="var(--link-stroke)" stroke-width="3"/>
            <circle cx="420" cy="210" r="10" fill="#52525b" stroke="#000" stroke-width="3"/> <!-- B点对称 -->
            <circle cx="650" cy="210" r="12" fill="var(--shaft-color)" stroke="#000" stroke-width="3"/> <!-- A点对称 -->
        </g>
    </svg>

    <!-- UI 面板 -->
    <div class="info-panel panel-top-left">
        <h1>IFR: 对开式连杆门 (向后方收纳)</h1>
        <div class="tag-group">
            <span class="tag-blue">闭合在同一直线</span>
            <span class="tag-red">向舞台后方开合 90°</span>
            <span class="tag">结构全隐藏于后台</span>
        </div>
        <p><strong>布局优化:</strong>将传动轴、雷亚架及所有连杆机构统一后置于舞台深处。门体位于系统最前端,完全面向观众(不受机构遮挡)。</p>
        <p><strong>逆向收纳原理:</strong>利用四连杆曲柄摇杆的逆向死点特性,中央电机驱动主曲柄反转,使得屏幕内侧首先<strong>向后方且向外</strong>抽出,随后在后台空间内完美翻转 90 度,全程远离前侧的观众席。</p>
        
        <div class="legend">
            <div class="legend-item"><div class="legend-color" style="background: var(--screen-color); box-shadow: 0 0 5px var(--screen-color);"></div> 前置屏幕/门体 (隔绝后台)</div>
            <div class="legend-item"><div class="legend-color" style="background: var(--traj-color);"></div> 门体向后方收纳的运动轨迹</div>
            <div class="legend-item"><div class="legend-color" style="background: #52525b;"></div> 隐藏式后置机械结构</div>
        </div>
    </div>

    <div class="info-panel panel-bottom-right">
        <div class="data-row">
            <span class="data-label">运行状态:</span>
            <span class="data-value" style="color: var(--traj-color);">后台收纳循环</span>
        </div>
        <div class="data-row">
            <span class="data-label">相对观众席位移:</span>
            <span class="data-value" style="color: var(--screen-color);">0 mm (安全)</span>
        </div>
        <div class="data-row">
            <span class="data-label">单侧开启角度:</span>
            <span class="data-value" id="ui-single-angle">0.0°</span>
        </div>
        <div class="data-row">
            <span class="data-label">左右门体夹角:</span>
            <span class="data-value" id="ui-total-angle" style="color: var(--screen-color);">180.0°</span>
        </div>
        <div class="data-row">
            <span class="data-label">连杆干涉监测:</span>
            <span class="data-value" style="color: #10b981;">CLEAR (无碰撞)</span>
        </div>
    </div>
</div>

<script>
    // --- 运动学核心模型 (基于右侧机构,逆向收纳) ---
    const K = {
        // 固定基座位于舞台后方 (Y=210)
        Ax: 950, Ay: 210, // 主动曲柄铰点
        Bx: 1180, By: 210, // 固定摇杆铰点 (拉开距离确保后台有足够空间收纳门体)
        
        // 连杆长度优化,确保能够实现无死点的 90 度向后偏转
        r1: 180,  // 主动曲柄 AD
        r2: 280,  // 从动摇杆 BC
        r3: 280,  // 连杆体 DC
        
        thetaStart: 0,
        thetaEnd: 0,
        alphaStart: 0,
        
        // 门体刚性绑定坐标
        doorLocalStartX: 0, doorLocalStartY: 0,
        doorLocalEndX: 0, doorLocalEndY: 0
    };

    const els = {
        centralRotator: document.getElementById('central-rotator'),
        trajRight: document.getElementById('traj-right'),
        trajLeft: document.getElementById('traj-left'),
        uiTotalAngle: document.getElementById('ui-total-angle'),
        uiSingleAngle: document.getElementById('ui-single-angle'),
        
        right: {
            linkAD: document.getElementById('r-link-ad'), linkADIn: document.getElementById('r-link-ad-in'),
            linkBC: document.getElementById('r-link-bc'), linkBCIn: document.getElementById('r-link-bc-in'),
            door: document.getElementById('r-door'), doorHl: document.getElementById('r-door-hl'),
            doorTip: document.getElementById('r-door-tip'),
            pivotD: document.getElementById('r-pivot-d'), pivotC: document.getElementById('r-pivot-c')
        },
        left: {
            linkAD: document.getElementById('l-link-ad'), linkADIn: document.getElementById('l-link-ad-in'),
            linkBC: document.getElementById('l-link-bc'), linkBCIn: document.getElementById('l-link-bc-in'),
            door: document.getElementById('l-door'), doorHl: document.getElementById('l-door-hl'),
            doorTip: document.getElementById('l-door-tip'),
            pivotD: document.getElementById('l-pivot-d'), pivotC: document.getElementById('l-pivot-c')
        }
    };

    // 求解器
    function solveFourBar(theta) {
        const Dx = K.Ax + K.r1 * Math.cos(theta);
        const Dy = K.Ay + K.r1 * Math.sin(theta);
        const Ldb = Math.hypot(K.Bx - Dx, K.By - Dy);
        
        if (Ldb > K.r2 + K.r3 || Ldb < Math.abs(K.r2 - K.r3)) return null;
        
        const phi = Math.atan2(K.By - Dy, K.Bx - Dx);
        const cosGamma = (K.r3 * K.r3 + Ldb * Ldb - K.r2 * K.r2) / (2 * K.r3 * Ldb);
        const gamma = Math.acos(Math.max(-1, Math.min(1, cosGamma)));
        
        // 取外展装配形态
        const theta3 = phi - gamma; 
        const Cx = Dx + K.r3 * Math.cos(theta3);
        const Cy = Dy + K.r3 * Math.sin(theta3);
        
        const alpha = Math.atan2(Cy - Dy, Cx - Dx);
        return { Dx, Dy, Cx, Cy, alpha };
    }

    // 动态求解 90度向后收纳 路径
    function initMechanism() {
        let found = false;
        // 右门要想向后(上方 Y=0方向)开启,必须逆时针旋转 -90度
        const targetRotation = -Math.PI / 2; 
        
        // 自动寻迹:遍历可能的初始角度,寻找能够使连杆逆时针旋转90度的曲柄区间
        for (let testThetaStart = 0; testThetaStart < Math.PI * 2; testThetaStart += 0.2) {
            K.thetaStart = testThetaStart;
            let startRes = solveFourBar(K.thetaStart);
            if (!startRes) continue;
            K.alphaStart = startRes.alpha;
            
            for (let dir of [1, -1]) {
                let tempTheta = K.thetaStart;
                let success = false;
                for (let i = 0; i < 1500; i++) {
                    tempTheta += dir * 0.005;
                    let res = solveFourBar(tempTheta);
                    if (!res) break; 
                    
                    let delta = res.alpha - K.alphaStart;
                    while(delta < -Math.PI) delta += Math.PI * 2;
                    while(delta > Math.PI) delta -= Math.PI * 2;
                    
                    if (delta <= targetRotation) {
                        K.thetaEnd = tempTheta;
                        success = true;
                        found = true;
                        break;
                    }
                }
                if (success) break;
            }
            if (found) break;
        }

        if (!found) {
            // 安全后备
            K.thetaEnd = K.thetaStart - 1.5;
        }

        // 重新获取初始状态并刚性绑定门体
        const startRes = solveFourBar(K.thetaStart);
        // 门体强制位于靠前的安全位置 Y=450 (完全遮挡后台机构)
        const doorY = 450;
        // 门体内边缘在中心 X=804,外边缘在 X=1184
        const doorStartX_Global = 804;
        const doorEndX_Global = 1184;

        // 绑定相对 D 点的局部坐标
        K.doorLocalStartX = doorStartX_Global - startRes.Dx;
        K.doorLocalStartY = doorY - startRes.Dy;
        K.doorLocalEndX = doorEndX_Global - startRes.Dx;
        K.doorLocalEndY = doorY - startRes.Dy;

        precalculateTrajectories();
    }

    // 预渲染轨迹 (显示向后开启路线)
    function precalculateTrajectories() {
        let pathR = "", pathL = "";
        const steps = 60;
        for (let i = 0; i <= steps; i++) {
            const t = i / steps;
            const theta = K.thetaStart + t * (K.thetaEnd - K.thetaStart);
            const res = solveFourBar(theta);
            if (res) {
                const deltaAlpha = res.alpha - K.alphaStart;
                const cosA = Math.cos(deltaAlpha);
                const sinA = Math.sin(deltaAlpha);
                
                // 门体内侧防撞尖端坐标
                const tipRx = res.Dx + K.doorLocalStartX * cosA - K.doorLocalStartY * sinA;
                const tipRy = res.Dy + K.doorLocalStartX * sinA + K.doorLocalStartY * cosA;
                const tipLx = 1600 - tipRx;
                
                if (i === 0) {
                    pathR += `M ${tipRx} ${tipRy} `;
                    pathL += `M ${tipLx} ${tipRy} `;
                } else {
                    pathR += `L ${tipRx} ${tipRy} `;
                    pathL += `L ${tipLx} ${tipRy} `;
                }
            }
        }
        els.trajRight.setAttribute('d', pathR);
        els.trajLeft.setAttribute('d', pathL);
    }

    // 渲染单侧机构
    function updateSide(sideEls, isLeft, Ax, Bx, res) {
        const mirrorX = (x) => isLeft ? 1600 - x : x;
        
        const mAx = mirrorX(Ax), mBx = mirrorX(Bx);
        const mDx = mirrorX(res.Dx), mCx = mirrorX(res.Cx);
        
        // 更新连杆坐标
        sideEls.linkAD.setAttribute('x1', mAx); sideEls.linkAD.setAttribute('y1', K.Ay);
        sideEls.linkAD.setAttribute('x2', mDx); sideEls.linkAD.setAttribute('y2', res.Dy);
        sideEls.linkADIn.setAttribute('x1', mAx); sideEls.linkADIn.setAttribute('y1', K.Ay);
        sideEls.linkADIn.setAttribute('x2', mDx); sideEls.linkADIn.setAttribute('y2', res.Dy);

        sideEls.linkBC.setAttribute('x1', mBx); sideEls.linkBC.setAttribute('y1', K.By);
        sideEls.linkBC.setAttribute('x2', mCx); sideEls.linkBC.setAttribute('y2', res.Cy);
        sideEls.linkBCIn.setAttribute('x1', mBx); sideEls.linkBCIn.setAttribute('y1', K.By);
        sideEls.linkBCIn.setAttribute('x2', mCx); sideEls.linkBCIn.setAttribute('y2', res.Cy);

        sideEls.pivotD.setAttribute('cx', mDx); sideEls.pivotD.setAttribute('cy', res.Dy);
        sideEls.pivotC.setAttribute('cx', mCx); sideEls.pivotC.setAttribute('cy', res.Cy);

        // 应用门体的刚性旋转
        const deltaAlpha = res.alpha - K.alphaStart;
        const cosA = Math.cos(deltaAlpha);
        const sinA = Math.sin(deltaAlpha);

        // 根据局部坐标计算新的全局坐标
        const rightStartX = res.Dx + K.doorLocalStartX * cosA - K.doorLocalStartY * sinA;
        const rightStartY = res.Dy + K.doorLocalStartX * sinA + K.doorLocalStartY * cosA;
        const rightEndX = res.Dx + K.doorLocalEndX * cosA - K.doorLocalEndY * sinA;
        const rightEndY = res.Dy + K.doorLocalEndX * sinA + K.doorLocalEndY * cosA;

        // 镜像应用到左门
        const finalStartX = mirrorX(rightStartX);
        const finalEndX = mirrorX(rightEndX);

        sideEls.door.setAttribute('x1', finalStartX); sideEls.door.setAttribute('y1', rightStartY);
        sideEls.door.setAttribute('x2', finalEndX); sideEls.door.setAttribute('y2', rightEndY);
        
        sideEls.doorHl.setAttribute('x1', finalStartX); sideEls.doorHl.setAttribute('y1', rightStartY);
        sideEls.doorHl.setAttribute('x2', finalEndX); sideEls.doorHl.setAttribute('y2', rightEndY);

        sideEls.doorTip.setAttribute('cx', finalStartX); sideEls.doorTip.setAttribute('cy', rightStartY);

        return deltaAlpha;
    }

    // 主循环
    const animSpeed = 0.0006;
    function renderFrame(time) {
        // 平滑缓动 (带停顿)
        let raw = Math.sin(time * animSpeed);
        let clamped = Math.max(-0.85, Math.min(0.85, raw)) / 0.85;
        let progress = (Math.sin(clamped * Math.PI / 2) + 1) / 2;

        const currentTheta = K.thetaStart + progress * (K.thetaEnd - K.thetaStart);
        
        // 中心驱动旋转
        els.centralRotator.setAttribute('transform', `rotate(${(currentTheta * -300) % 360} 0 0)`);

        const res = solveFourBar(currentTheta);
        if (res) {
            const rightRot = updateSide(els.right, false, K.Ax, K.Bx, res);
            updateSide(els.left, true, K.Ax, K.Bx, res);

            // 更新仪表盘 (绝对值处理使得显示符合直觉)
            const singleDeg = Math.abs(rightRot * 180 / Math.PI);
            els.uiSingleAngle.innerText = singleDeg.toFixed(1) + "°";
            
            const totalDeg = 180 - 2 * singleDeg;
            els.uiTotalAngle.innerText = totalDeg.toFixed(1) + "°";
        }

        requestAnimationFrame(renderFrame);
    }

    // 启动
    document.addEventListener("DOMContentLoaded", () => {
        initMechanism();
        requestAnimationFrame(renderFrame);
    });

</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分