<!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>
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@300;500;700&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<style>
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#050a14;--bg2:#0b1628;--fg:#e4ddd3;--muted:#4a5c75;
--accent:#ff6b2b;--cool:#00d4ff;--aerogel:#c9956a;--aerogel-dim:rgba(201,149,106,0.25);
--solar:#ffd700;--danger:#ff2244;--hud:#00ff88;--cable:#7a8fa5;
}
html{font-size:16px}
body{background:var(--bg);color:var(--fg);font-family:'Rajdhani',sans-serif;min-height:100vh;display:flex;flex-direction:column;align-items:center;overflow-x:hidden}
header{text-align:center;padding:1.5rem 1rem 0.5rem;width:100%}
header h1{font-size:2rem;font-weight:700;letter-spacing:0.06em;background:linear-gradient(135deg,var(--aerogel),var(--accent));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
header p{font-size:0.95rem;color:var(--muted);letter-spacing:0.12em;margin-top:0.2rem;font-weight:300}
#scene-wrap{width:100%;max-width:1400px;flex:1;display:flex;justify-content:center;align-items:center;padding:0.5rem}
#scene-wrap svg{width:100%;height:auto;max-height:78vh;display:block}
#controls{width:100%;max-width:1200px;padding:0.8rem 1.5rem 1.5rem;display:flex;flex-wrap:wrap;gap:1rem;align-items:center;justify-content:center}
.ctrl-group{display:flex;align-items:center;gap:0.6rem;background:var(--bg2);border:1px solid rgba(201,149,106,0.15);border-radius:8px;padding:0.5rem 1rem}
.ctrl-group label{font-size:0.8rem;color:var(--muted);letter-spacing:0.08em;white-space:nowrap;font-family:'JetBrains Mono',monospace}
.ctrl-group input[type=range]{-webkit-appearance:none;width:180px;height:4px;background:linear-gradient(90deg,var(--cool),var(--accent));border-radius:2px;outline:none;cursor:pointer}
.ctrl-group input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;border-radius:50%;background:var(--fg);border:2px solid var(--accent);cursor:grab}
.ctrl-group .val{font-family:'JetBrains Mono',monospace;font-size:0.85rem;color:var(--hud);min-width:48px;text-align:right}
button.ctrl-btn{background:transparent;border:1px solid var(--muted);color:var(--fg);padding:0.45rem 1rem;border-radius:6px;cursor:pointer;font-family:'Rajdhani',sans-serif;font-size:0.85rem;font-weight:500;letter-spacing:0.06em;transition:all .25s}
button.ctrl-btn:hover{border-color:var(--accent);color:var(--accent);box-shadow:0 0 12px rgba(255,107,43,0.2)}
button.ctrl-btn.active{border-color:var(--danger);color:var(--danger);box-shadow:0 0 12px rgba(255,34,68,0.3)}
.toast{position:fixed;top:1.5rem;right:1.5rem;background:var(--bg2);border:1px solid var(--danger);color:var(--danger);padding:0.7rem 1.2rem;border-radius:8px;font-size:0.85rem;font-family:'JetBrains Mono',monospace;opacity:0;transform:translateY(-10px);transition:all .3s;pointer-events:none;z-index:99}
.toast.show{opacity:1;transform:translateY(0)}
@keyframes pulseGlow{0%,100%{opacity:0.6}50%{opacity:1}}
@keyframes dashFlow{to{stroke-dashoffset:-20}}
@media(max-width:768px){
header h1{font-size:1.4rem}
.ctrl-group input[type=range]{width:120px}
#controls{gap:0.5rem}
}
</style>
</head>
<body>
<header>
<h1>毛细血管式微元浮力块</h1>
<p>分布式浮力 · 消灭应力集中 · 无限冗余 — IFR 理想解原理动画</p>
</header>
<div id="scene-wrap">
<svg id="scene" viewBox="0 0 1400 820" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 天空渐变(动态修改) -->
<linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1">
<stop id="skyStop1" offset="0%" stop-color="#050a14"/>
<stop id="skyStop2" offset="60%" stop-color="#0b1628"/>
<stop id="skyStop3" offset="100%" stop-color="#111d33"/>
</linearGradient>
<!-- 太阳能薄膜渐变 -->
<linearGradient id="solarGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#ffd700" stop-opacity="0.7"/>
<stop offset="50%" stop-color="#ffaa00" stop-opacity="0.9"/>
<stop offset="100%" stop-color="#ffd700" stop-opacity="0.7"/>
</linearGradient>
<!-- 舱壁渐变 -->
<radialGradient id="cabinGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ff6b2b" stop-opacity="0.5"/>
<stop offset="100%" stop-color="#ff6b2b" stop-opacity="0"/>
</radialGradient>
<radialGradient id="cabinCoolGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#00d4ff" stop-opacity="0.4"/>
<stop offset="100%" stop-color="#00d4ff" stop-opacity="0"/>
</radialGradient>
<!-- 蜂窝图案 -->
<pattern id="hexPat" width="16" height="27.71" patternUnits="userSpaceOnUse" patternTransform="rotate(0)">
<polygon points="8,0 16,4.62 16,13.86 8,18.48 0,13.86 0,4.62" fill="rgba(201,149,106,0.08)" stroke="rgba(201,149,106,0.3)" stroke-width="0.5"/>
<polygon points="8,9.24 16,13.86 16,23.09 8,27.71 0,23.09 0,13.86" fill="rgba(201,149,106,0.08)" stroke="rgba(201,149,106,0.3)" stroke-width="0.5"/>
</pattern>
<!-- 放大区蜂窝 -->
<pattern id="hexPatZoom" width="52" height="90.07" patternUnits="userSpaceOnUse">
<polygon points="26,0 52,15 52,45 26,60 0,45 0,15" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.45)" stroke-width="1.2"/>
<polygon points="26,30.02 52,45.02 52,75.02 26,90.02 0,75.02 0,45.02" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.45)" stroke-width="1.2"/>
</pattern>
<!-- 张拉索虚线 -->
<filter id="glowFilter" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<clipPath id="hullClip">
<path d="M180,410 C180,310 280,230 480,230 C680,230 780,310 780,410 C780,510 680,590 480,590 C280,590 180,510 180,410 Z"/>
</clipPath>
<clipPath id="innerClip">
<path d="M240,410 C240,335 320,275 480,275 C640,275 720,335 720,410 C720,485 640,545 480,545 C320,545 240,485 240,410 Z"/>
</clipPath>
<!-- 裁剪环壳区域 -->
<clipPath id="shellClip">
<path d="M180,410 C180,310 280,230 480,230 C680,230 780,310 780,410 C780,510 680,590 480,590 C280,590 180,510 180,410 Z M240,410 C240,335 320,275 480,275 C640,275 720,335 720,410 C720,485 640,545 480,545 C320,545 240,485 240,410 Z" fill-rule="evenodd"/>
</clipPath>
</defs>
<!-- ====== 天空背景 ====== -->
<rect id="skyRect" x="0" y="0" width="1400" height="820" fill="url(#skyGrad)"/>
<!-- 星星 -->
<g id="stars" opacity="0.8">
<circle cx="120" cy="60" r="1.2" fill="#fff"/><circle cx="340" cy="40" r="0.8" fill="#fff"/>
<circle cx="560" cy="80" r="1" fill="#fff"/><circle cx="780" cy="30" r="1.3" fill="#fff"/>
<circle cx="1020" cy="70" r="0.9" fill="#fff"/><circle cx="1250" cy="50" r="1.1" fill="#fff"/>
<circle cx="200" cy="130" r="0.7" fill="#fff"/><circle cx="900" cy="110" r="1" fill="#fff"/>
<circle cx="1100" cy="140" r="0.8" fill="#fff"/><circle cx="50" cy="100" r="0.9" fill="#fff"/>
<circle cx="650" cy="150" r="0.6" fill="#fff"/><circle cx="1350" cy="90" r="1" fill="#fff"/>
</g>
<!-- 太阳/月亮 -->
<g id="celestial">
<circle id="sun" cx="480" cy="100" r="40" fill="#ffd700" opacity="0" filter="url(#softGlow)"/>
<circle id="moon" cx="480" cy="120" r="25" fill="#c0c8d8" opacity="0.6"/>
</g>
<!-- ====== 高度标尺 ====== -->
<g id="altScale" transform="translate(60, 200)">
<line x1="0" y1="0" x2="0" y2="500" stroke="rgba(201,149,106,0.2)" stroke-width="1"/>
<text x="-5" y="5" fill="var(--muted)" font-size="10" font-family="JetBrains Mono" text-anchor="end">20km 平流层</text>
<line x1="-8" y1="0" x2="8" y2="0" stroke="var(--muted)" stroke-width="0.8"/>
<text x="-5" y="250" fill="var(--muted)" font-size="10" font-family="JetBrains Mono" text-anchor="end">10km 对流层顶</text>
<line x1="-8" y1="250" x2="8" y2="250" stroke="var(--muted)" stroke-width="0.8"/>
<text x="-5" y="500" fill="var(--muted)" font-size="10" font-family="JetBrains Mono" text-anchor="end">0km 地面</text>
<line x1="-8" y1="500" x2="8" y2="500" stroke="var(--muted)" stroke-width="0.8"/>
<!-- 高度指示三角 -->
<polygon id="altPointer" points="-12,395 0,390 0,400" fill="var(--hud)" opacity="0.9"/>
</g>
<!-- ====== 飞艇主体 ====== -->
<g id="airshipGroup">
<!-- 外壳(蜂窝图案填充) -->
<g clip-path="url(#shellClip)">
<rect x="170" y="220" width="620" height="380" fill="url(#hexPat)"/>
<!-- 受损区域遮罩 -->
<g id="damageOverlay" opacity="0"></g>
</g>
<!-- 外壳轮廓 -->
<path d="M180,410 C180,310 280,230 480,230 C680,230 780,310 780,410 C780,510 680,590 480,590 C280,590 180,510 180,410 Z" fill="none" stroke="rgba(201,149,106,0.5)" stroke-width="1.5"/>
<!-- 内腔轮廓 -->
<path d="M240,410 C240,335 320,275 480,275 C640,275 720,335 720,410 C720,485 640,545 480,545 C320,545 240,485 240,410 Z" fill="rgba(5,10,20,0.7)" stroke="rgba(201,149,106,0.2)" stroke-width="1" stroke-dasharray="6,4"/>
<!-- 太阳能薄膜(顶部弧线) -->
<path id="solarFilm" d="M200,395 C200,320 290,250 480,250 C670,250 760,320 760,395" fill="none" stroke="url(#solarGrad)" stroke-width="6" stroke-linecap="round" opacity="0.5"/>
<!-- 太阳能光子粒子 -->
<g id="solarParticles"></g>
<!-- 内腔气体发光效果 -->
<ellipse id="innerGlow" cx="480" cy="410" rx="220" ry="120" fill="url(#cabinGlow)" opacity="0.3"/>
<!-- 热泵标识 -->
<g id="heatPump" transform="translate(480,410)">
<circle r="22" fill="rgba(5,10,20,0.8)" stroke="var(--accent)" stroke-width="1.5" opacity="0.7"/>
<text y="1" text-anchor="middle" fill="var(--accent)" font-size="11" font-family="JetBrains Mono" font-weight="500">HP</text>
<!-- 旋转指示环 -->
<circle id="hpRing" r="28" fill="none" stroke="var(--accent)" stroke-width="1" stroke-dasharray="8,6" opacity="0.5">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="4s" repeatCount="indefinite"/>
</circle>
</g>
<!-- 能量流粒子(太阳能→热泵→舱壁) -->
<g id="energyFlows"></g>
<!-- 张拉索 -->
<g id="tensionCables">
<line x1="350" y1="540" x2="380" y2="660" stroke="var(--cable)" stroke-width="1.2" stroke-dasharray="8,4" opacity="0.6">
<animate attributeName="stroke-dashoffset" from="0" to="-24" dur="1.5s" repeatCount="indefinite"/>
</line>
<line x1="420" y1="548" x2="430" y2="660" stroke="var(--cable)" stroke-width="1.2" stroke-dasharray="8,4" opacity="0.6">
<animate attributeName="stroke-dashoffset" from="0" to="-24" dur="1.5s" repeatCount="indefinite"/>
</line>
<line x1="540" y1="548" x2="530" y2="660" stroke="var(--cable)" stroke-width="1.2" stroke-dasharray="8,4" opacity="0.6">
<animate attributeName="stroke-dashoffset" from="0" to="-24" dur="1.5s" repeatCount="indefinite"/>
</line>
<line x1="610" y1="540" x2="580" y2="660" stroke="var(--cable)" stroke-width="1.2" stroke-dasharray="8,4" opacity="0.6">
<animate attributeName="stroke-dashoffset" from="0" to="-24" dur="1.5s" repeatCount="indefinite"/>
</line>
</g>
<!-- 核心甲板/货舱 -->
<g id="cargoDeck">
<rect x="360" y="660" width="240" height="40" rx="4" fill="rgba(20,30,50,0.9)" stroke="var(--cable)" stroke-width="1.5"/>
<text x="480" y="685" text-anchor="middle" fill="var(--muted)" font-size="11" font-family="JetBrains Mono">CARGO DECK</text>
<!-- 预紧力标注 -->
<text x="330" y="670" fill="var(--cool)" font-size="9" font-family="JetBrains Mono" opacity="0.7">F=500kN</text>
</g>
</g>
<!-- ====== 放大镜:微舱细节 ====== -->
<g id="zoomInset" transform="translate(950, 60)">
<!-- 背景框 -->
<rect x="-10" y="-10" width="400" height="340" rx="12" fill="rgba(5,10,20,0.85)" stroke="rgba(201,149,106,0.25)" stroke-width="1"/>
<text x="190" y="18" text-anchor="middle" fill="var(--aerogel)" font-size="13" font-family="Rajdhani" font-weight="700" letter-spacing="0.1em">微舱结构放大视图</text>
<line x1="10" y1="28" x2="370" y2="28" stroke="rgba(201,149,106,0.15)" stroke-width="0.5"/>
<!-- 7个六边形微舱 -->
<g id="zoomCabins" transform="translate(190, 185)">
<!-- 中心舱 -->
<g class="zcabin" data-idx="0" transform="translate(0,0)">
<polygon points="0,-44 38.1,-22 38.1,22 0,44 -38.1,22 -38.1,-22" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.6)" stroke-width="1.5"/>
<circle class="zgas" r="18" fill="rgba(255,107,43,0.3)"/>
<text y="3" text-anchor="middle" fill="var(--muted)" font-size="8" font-family="JetBrains Mono">H₂</text>
<!-- 阀门 -->
<rect class="zvalve" x="-3" y="-48" width="6" height="6" rx="1" fill="var(--cool)" opacity="0.5"/>
</g>
<!-- 上舱 -->
<g class="zcabin" data-idx="1" transform="translate(0,-76.2)">
<polygon points="0,-44 38.1,-22 38.1,22 0,44 -38.1,22 -38.1,-22" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.6)" stroke-width="1.5"/>
<circle class="zgas" r="18" fill="rgba(255,107,43,0.3)"/>
<text y="3" text-anchor="middle" fill="var(--muted)" font-size="8" font-family="JetBrains Mono">H₂</text>
<rect class="zvalve" x="-3" y="-48" width="6" height="6" rx="1" fill="var(--cool)" opacity="0.5"/>
</g>
<!-- 右上舱 -->
<g class="zcabin" data-idx="2" transform="translate(66,-38.1)">
<polygon points="0,-44 38.1,-22 38.1,22 0,44 -38.1,22 -38.1,-22" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.6)" stroke-width="1.5"/>
<circle class="zgas" r="18" fill="rgba(255,107,43,0.3)"/>
<text y="3" text-anchor="middle" fill="var(--muted)" font-size="8" font-family="JetBrains Mono">H₂</text>
<rect class="zvalve" x="-3" y="-48" width="6" height="6" rx="1" fill="var(--cool)" opacity="0.5"/>
</g>
<!-- 右下舱 -->
<g class="zcabin" data-idx="3" transform="translate(66,38.1)">
<polygon points="0,-44 38.1,-22 38.1,22 0,44 -38.1,22 -38.1,-22" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.6)" stroke-width="1.5"/>
<circle class="zgas" r="18" fill="rgba(255,107,43,0.3)"/>
<text y="3" text-anchor="middle" fill="var(--muted)" font-size="8" font-family="JetBrains Mono">H₂</text>
<rect class="zvalve" x="-3" y="-48" width="6" height="6" rx="1" fill="var(--cool)" opacity="0.5"/>
</g>
<!-- 下舱 -->
<g class="zcabin" data-idx="4" transform="translate(0,76.2)">
<polygon points="0,-44 38.1,-22 38.1,22 0,44 -38.1,22 -38.1,-22" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.6)" stroke-width="1.5"/>
<circle class="zgas" r="18" fill="rgba(255,107,43,0.3)"/>
<text y="3" text-anchor="middle" fill="var(--muted)" font-size="8" font-family="JetBrains Mono">H₂</text>
<rect class="zvalve" x="-3" y="-48" width="6" height="6" rx="1" fill="var(--cool)" opacity="0.5"/>
</g>
<!-- 左下舱 -->
<g class="zcabin" data-idx="5" transform="translate(-66,38.1)">
<polygon points="0,-44 38.1,-22 38.1,22 0,44 -38.1,22 -38.1,-22" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.6)" stroke-width="1.5"/>
<circle class="zgas" r="18" fill="rgba(255,107,43,0.3)"/>
<text y="3" text-anchor="middle" fill="var(--muted)" font-size="8" font-family="JetBrains Mono">H₂</text>
<rect class="zvalve" x="-3" y="-48" width="6" height="6" rx="1" fill="var(--cool)" opacity="0.5"/>
</g>
<!-- 左上舱 -->
<g class="zcabin" data-idx="6" transform="translate(-66,-38.1)">
<polygon points="0,-44 38.1,-22 38.1,22 0,44 -38.1,22 -38.1,-22" fill="rgba(201,149,106,0.06)" stroke="rgba(201,149,106,0.6)" stroke-width="1.5"/>
<circle class="zgas" r="18" fill="rgba(255,107,43,0.3)"/>
<text y="3" text-anchor="middle" fill="var(--muted)" font-size="8" font-family="JetBrains Mono">H₂</text>
<rect class="zvalve" x="-3" y="-48" width="6" height="6" rx="1" fill="var(--cool)" opacity="0.5"/>
</g>
</g>
<!-- 参数标注 -->
<g transform="translate(10, 290)" font-family="JetBrains Mono" font-size="10">
<text fill="var(--muted)">舱壁厚度</text>
<text x="100" fill="var(--hud)">0.5mm</text>
<text x="170" fill="var(--muted)">抗压</text>
<text x="220" fill="var(--hud)">1.2atm</text>
</g>
<!-- 连接线指向主图 -->
<line x1="0" y1="170" x2="-90" y2="370" stroke="rgba(201,149,106,0.2)" stroke-width="0.8" stroke-dasharray="4,3"/>
</g>
<!-- ====== HUD 数据面板 ====== -->
<g id="hudPanel" transform="translate(950, 430)">
<rect x="-10" y="-10" width="400" height="310" rx="10" fill="rgba(5,10,20,0.8)" stroke="rgba(0,255,136,0.15)" stroke-width="1"/>
<text x="190" y="16" text-anchor="middle" fill="var(--hud)" font-size="13" font-family="Rajdhani" font-weight="700" letter-spacing="0.12em" opacity="0.8">SYSTEM STATUS</text>
<line x1="10" y1="26" x2="370" y2="26" stroke="rgba(0,255,136,0.1)" stroke-width="0.5"/>
<!-- 数据行 -->
<g font-family="JetBrains Mono" font-size="12">
<text x="20" y="56" fill="var(--muted)">时间</text>
<text id="hudTime" x="370" y="56" fill="var(--fg)" text-anchor="end">12:00</text>
<text x="20" y="86" fill="var(--muted)">海拔高度</text>
<text id="hudAlt" x="370" y="86" fill="var(--hud)" text-anchor="end">20,000 m</text>
<text x="20" y="116" fill="var(--muted)">总浮力</text>
<text id="hudBuoyancy" x="370" y="116" fill="var(--accent)" text-anchor="end">100.0%</text>
<text x="20" y="146" fill="var(--muted)">微舱温度</text>
<text id="hudTemp" x="370" y="146" fill="var(--solar)" text-anchor="end">+45°C</text>
<text x="20" y="176" fill="var(--muted)">活跃微舱数</text>
<text id="hudCabins" x="370" y="176" fill="var(--aerogel)" text-anchor="end">1.2×10⁹</text>
<text x="20" y="206" fill="var(--muted)">张拉索预紧力</text>
<text id="hudTension" x="370" y="206" fill="var(--cool)" text-anchor="end">500 kN</text>
<text x="20" y="236" fill="var(--muted)">太阳能输出</text>
<text id="hudSolar" x="370" y="236" fill="var(--solar)" text-anchor="end">850 kW</text>
<text x="20" y="266" fill="var(--muted)">受损舱数</text>
<text id="hudDamage" x="370" y="266" fill="var(--danger)" text-anchor="end">0</text>
</g>
<!-- 浮力条 -->
<g transform="translate(20, 280)">
<rect width="350" height="8" rx="4" fill="rgba(255,255,255,0.05)"/>
<rect id="buoyancyBar" width="350" height="8" rx="4" fill="var(--accent)" opacity="0.7"/>
</g>
</g>
<!-- ====== IFR 标注箭头与文字 ====== -->
<g id="ifrAnnotations" font-family="Rajdhani" font-weight="500">
<!-- 分布式浮力标注 -->
<g transform="translate(110, 340)" opacity="0.7">
<line x1="0" y1="0" x2="55" y2="0" stroke="var(--accent)" stroke-width="1" marker-end="none"/>
<text x="0" y="-8" fill="var(--accent)" font-size="11" letter-spacing="0.05em">分布式浮力壳</text>
<text x="0" y="16" fill="var(--muted)" font-size="9">无集中弯矩</text>
</g>
<!-- 太阳能标注 -->
<g transform="translate(300, 220)" opacity="0.7">
<line x1="0" y1="12" x2="0" y2="30" stroke="var(--solar)" stroke-width="1"/>
<text x="5" y="8" fill="var(--solar)" font-size="11">柔性太阳能薄膜</text>
</g>
<!-- 热泵标注 -->
<g transform="translate(520, 430)" opacity="0.7">
<line x1="0" y1="-12" x2="0" y2="-28" stroke="var(--accent)" stroke-width="1"/>
<text x="5" y="-18" fill="var(--accent)" font-size="11">热泵核心</text>
</g>
</g>
<!-- 损伤模拟闪烁覆盖 -->
<g id="damageFlash" opacity="0">
<circle r="30" fill="var(--danger)" opacity="0.3" filter="url(#softGlow)"/>
</g>
</svg>
</div>
<!-- 控制面板 -->
<div id="controls">
<div class="ctrl-group">
<label><i class="fas fa-clock"></i> 时刻</label>
<input type="range" id="timeSlider" min="0" max="24" step="0.1" value="12"/>
<span class="val" id="timeVal">12:00</span>
</div>
<div class="ctrl-group">
<label><i class="fas fa-temperature-high"></i> 热泵功率</label>
<input type="range" id="pumpSlider" min="0" max="100" step="1" value="70"/>
<span class="val" id="pumpVal">70%</span>
</div>
<button class="ctrl-btn" id="btnDamage"><i class="fas fa-bolt"></i> 模拟损伤</button>
<button class="ctrl-btn" id="btnAutoPlay"><i class="fas fa-play"></i> 自动循环</button>
<button class="ctrl-btn" id="btnReset"><i class="fas fa-undo"></i> 重置</button>
</div>
<!-- 提示Toast -->
<div class="toast" id="toast"></div>
<script>
// ====== 状态管理 ======
const state = {
time: 12, // 0-24小时
pumpPower: 70, // 热泵功率 0-100%
autoPlay: false,
autoDir: 1,
damaged: false,
damageCabins: 0, // 损伤微舱数
damageX: 0,
damageY: 0,
animFrame: null,
particles: [], // 能量流粒子
solarPhotons: [], // 太阳能光子
lastTime: 0
};
// ====== DOM 引用 ======
const $ = id => document.getElementById(id);
const svg = $('scene');
const timeSlider = $('timeSlider');
const pumpSlider = $('pumpSlider');
const timeVal = $('timeVal');
const pumpVal = $('pumpVal');
const btnDamage = $('btnDamage');
const btnAutoPlay = $('btnAutoPlay');
const btnReset = $('btnReset');
const toast = $('toast');
// ====== 工具函数 ======
function lerp(a, b, t) { return a + (b - a) * t; }
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
function lerpColor(c1, c2, t) {
// c1, c2 为 [r,g,b]
return `rgb(${Math.round(lerp(c1[0],c2[0],t))},${Math.round(lerp(c1[1],c2[1],t))},${Math.round(lerp(c1[2],c2[2],t))})`;
}
// ====== 日照与浮力计算 ======
function getSunFactor(t) {
// 6时日出, 18时日落, 正弦曲线
if (t < 5 || t > 19) return 0;
return Math.max(0, Math.sin((t - 5) * Math.PI / 14));
}
function getAltitude(t, pump) {
// 基于日照+热泵的综合高度 (0-1 归一化)
const sun = getSunFactor(t);
const heat = sun * 0.6 + (pump / 100) * 0.4;
return clamp(heat, 0.05, 1);
}
function getGasTemp(t, pump) {
// 气体温度 -20°C ~ +65°C
const sun = getSunFactor(t);
return lerp(-20, 65, sun * 0.5 + (pump / 100) * 0.5);
}
// ====== 天空渲染 ======
const skyStop1 = $('skyStop1');
const skyStop2 = $('skyStop2');
const skyStop3 = $('skyStop3');
const sunEl = $('sun');
const moonEl = $('moon');
const starsEl = $('stars');
const nightTop = [5, 10, 20];
const nightMid = [11, 22, 40];
const nightBot = [17, 29, 51];
const dayTop = [25, 55, 110];
const dayMid = [60, 120, 180];
const dayBot = [90, 150, 200];
const sunsetTop = [40, 20, 50];
const sunsetMid = [120, 50, 40];
const sunsetBot = [80, 80, 60];
function updateSky(t) {
const sun = getSunFactor(t);
// 判断是否在日出/日落时段
const isSunrise = t >= 5 && t <= 7;
const isSunset = t >= 17 && t <= 19;
const twilight = isSunrise ? (t - 5) / 2 : isSunset ? (19 - t) / 2 : 0;
let topC, midC, botC;
if (twilight > 0 && sun < 0.5) {
const tw = twilight;
topC = dayTop.map((v, i) => lerp(nightTop[i], sunsetTop[i], tw));
midC = dayMid.map((v, i) => lerp(nightMid[i], sunsetMid[i], tw));
botC = dayBot.map((v, i) => lerp(nightBot[i], sunsetBot[i], tw));
} else {
topC = dayTop.map((v, i) => lerp(nightTop[i], v, sun));
midC = dayMid.map((v, i) => lerp(nightMid[i], v, sun));
botC = dayBot.map((v, i) => lerp(nightBot[i], v, sun));
}
skyStop1.setAttribute('stop-color', `rgb(${topC.map(Math.round).join(',')})`);
skyStop2.setAttribute('stop-color', `rgb(${midC.map(Math.round).join(',')})`);
skyStop3.setAttribute('stop-color', `rgb(${botC.map(Math.round).join(',')})`);
// 太阳
const sunAngle = (t - 6) * Math.PI / 12; // 6时升起, 18时落下
const sunX = 480 + 300 * Math.cos(sunAngle - Math.PI / 2);
const sunY = 100 - 80 * Math.sin(sunAngle);
sunEl.setAttribute('cx', sunX);
sunEl.setAttribute('cy', Math.max(40, sunY));
sunEl.setAttribute('opacity', sun * 0.9);
// 月亮
moonEl.setAttribute('opacity', (1 - sun) * 0.6);
const moonAngle = sunAngle + Math.PI;
moonEl.setAttribute('cx', 480 + 250 * Math.cos(moonAngle - Math.PI / 2));
moonEl.setAttribute('cy', Math.max(50, 100 - 60 * Math.sin(moonAngle)));
// 星星
starsEl.setAttribute('opacity', (1 - sun) * 0.8);
}
// ====== 飞艇位置与状态 ======
const airshipGroup = $('airshipGroup');
const solarFilm = $('solarFilm');
const innerGlow = $('innerGlow');
const hpRing = $('hpRing');
function updateAirship(t, pump) {
const alt = getAltitude(t, pump);
const sun = getSunFactor(t);
// 飞艇垂直位置 (高度越高越靠上)
// 基准位置y=0, 最大上移120px
const yOffset = -alt * 120;
airshipGroup.setAttribute('transform', `translate(0, ${yOffset})`);
// 太阳能薄膜发光
solarFilm.setAttribute('opacity', 0.3 + sun * 0.7);
solarFilm.setAttribute('stroke-width', 4 + sun * 4);
// 内腔发光 (加热时橙色, 冷却时蓝色)
const temp = getGasTemp(t, pump);
const heatFactor = clamp((temp + 20) / 85, 0, 1); // 0=冷 1=热
const glowR = Math.round(lerp(0, 255, heatFactor));
const glowG = Math.round(lerp(212, 107, heatFactor));
const glowB = Math.round(lerp(255, 43, heatFactor));
innerGlow.setAttribute('fill', `rgba(${glowR},${glowG},${glowB},${0.15 + heatFactor * 0.25})`);
// 热泵环颜色
const hpColor = heatFactor > 0.5 ? 'var(--accent)' : 'var(--cool)';
hpRing.setAttribute('stroke', hpColor);
// 高度指针
const altPointer = $('altPointer');
const pointerY = 500 - alt * 500; // 0km=500, 20km=0
altPointer.setAttribute('transform', `translate(0, ${pointerY})`);
return { alt, sun, heatFactor, temp, yOffset };
}
// ====== 放大区微舱更新 ======
const zoomCabins = document.querySelectorAll('.zcabin');
const zgasElements = document.querySelectorAll('.zgas');
const zvalveElements = document.querySelectorAll('.zvalve');
function updateZoomCabins(t, pump) {
const sun = getSunFactor(t);
const heatFactor = clamp((getGasTemp(t, pump) + 20) / 85, 0, 1);
// 加热时气体膨胀(半径增大), 冷却时收缩
const gasR = lerp(12, 24, heatFactor);
// 颜色:冷蓝 → 暖橙
const r = Math.round(lerp(0, 255, heatFactor));
const g = Math.round(lerp(180, 107, heatFactor));
const b = Math.round(lerp(255, 43, heatFactor));
const a = lerp(0.2, 0.5, heatFactor);
zgasElements.forEach((el, i) => {
// 微小随机差异,让每个舱不完全同步
const jitter = Math.sin(Date.now() * 0.002 + i * 1.3) * 0.08;
const localHeat = clamp(heatFactor + jitter, 0, 1);
const localR = lerp(12, 24, localHeat);
const lr = Math.round(lerp(0, 255, localHeat));
const lg = Math.round(lerp(180, 107, localHeat));
const lb = Math.round(lerp(255, 43, localHeat));
const la = lerp(0.2, 0.5, localHeat);
el.setAttribute('r', localR);
el.setAttribute('fill', `rgba(${lr},${lg},${lb},${la})`);
});
// 阀门:夜间/降温时打开(闪烁)
const valveOpen = heatFactor < 0.35;
zvalveElements.forEach((el, i) => {
if (valveOpen) {
const blink = Math.sin(Date.now() * 0.008 + i) > 0;
el.setAttribute('opacity', blink ? 0.9 : 0.3);
el.setAttribute('fill', 'var(--cool)');
} else {
el.setAttribute('opacity', 0.3);
el.setAttribute('fill', 'var(--muted)');
}
});
// 损伤舱变红
if (state.damaged) {
zoomCabins.forEach((cabin, i) => {
if (i === 3 || i === 4) { // 模拟受损的2个舱
const poly = cabin.querySelector('polygon');
poly.setAttribute('fill', 'rgba(255,34,68,0.2)');
poly.setAttribute('stroke', 'var(--danger)');
const gas = cabin.querySelector('.zgas');
gas.setAttribute('fill', 'rgba(255,34,68,0.1)');
gas.setAttribute('r', 6);
const valve = cabin.querySelector('.zvalve');
valve.setAttribute('fill', 'var(--danger)');
valve.setAttribute('opacity', 0.9);
}
});
}
}
// ====== HUD 更新 ======
function updateHUD(t, pump) {
const alt = getAltitude(t, pump);
const temp = getGasTemp(t, pump);
const sun = getSunFactor(t);
const totalCabins = 1.2e9;
const damagedCount = state.damaged ? state.damageCabins : 0;
const buoyancyPct = ((totalCabins - damagedCount) / totalCabins * 100);
const solarOutput = Math.round(sun * 1200);
const hours = Math.floor(t);
const mins = Math.floor((t - hours) * 60);
$('hudTime').textContent = `${String(hours).padStart(2,'0')}:${String(mins).padStart(2,'0')}`;
$('hudAlt').textContent = `${Math.round(alt * 20000).toLocaleString()} m`;
$('hudBuoyancy').textContent = `${buoyancyPct.toFixed(4)}%`;
$('hudTemp').textContent = `${temp > 0 ? '+' : ''}${Math.round(temp)}°C`;
$('hudCabins').textContent = `${(totalCabins - damagedCount).toExponential(1)}`;
$('hudTension').textContent = `${Math.round(500 * (0.9 + alt * 0.2))} kN`;
$('hudSolar').textContent = solarOutput > 0 ? `${solarOutput} kW` : '0 kW';
$('hudDamage').textContent = damagedCount.toLocaleString();
$('hudDamage').setAttribute('fill', damagedCount > 0 ? 'var(--danger)' : 'var(--muted)');
// 浮力条
$('buoyancyBar').setAttribute('width', buoyancyPct * 3.5);
$('buoyancyBar').setAttribute('fill', buoyancyPct > 99 ? 'var(--accent)' : 'var(--danger)');
}
// ====== 粒子系统 ======
const energyFlowsG = $('energyFlows');
const solarParticlesG = $('solarParticles');
const MAX_PARTICLES = 40;
const MAX_PHOTONS = 15;
function initParticles() {
// 能量流粒子:热泵→舱壁
for (let i = 0; i < MAX_PARTICLES; i++) {
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('r', '2');
circle.setAttribute('fill', 'var(--accent)');
circle.setAttribute('opacity', '0');
energyFlowsG.appendChild(circle);
state.particles.push({
el: circle,
angle: Math.random() * Math.PI * 2,
speed: 0.3 + Math.random() * 0.5,
dist: 0,
maxDist: 60 + Math.random() * 140,
life: 0,
maxLife: 80 + Math.random() * 60
});
}
// 太阳能光子
for (let i = 0; i < MAX_PHOTONS; i++) {
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('stroke', 'var(--solar)');
line.setAttribute('stroke-width', '1.5');
line.setAttribute('opacity', '0');
line.setAttribute('stroke-linecap', 'round');
solarParticlesG.appendChild(line);
state.solarPhotons.push({
el: line,
x: 250 + Math.random() * 460,
y: 0,
speed: 1.5 + Math.random() * 2,
life: 0,
maxLife: 40 + Math.random() * 30,
delay: Math.random() * 100
});
}
}
function updateParticles(t, pump) {
const sun = getSunFactor(t);
const heatFactor = clamp((getGasTemp(t, pump) + 20) / 85, 0, 1);
// 能量流粒子
state.particles.forEach(p => {
p.life++;
if (p.life > p.maxLife) {
p.life = 0;
p.angle = Math.random() * Math.PI * 2;
p.dist = 0;
p.speed = 0.3 + Math.random() * 0.5;
p.maxDist = 60 + Math.random() * 140;
}
p.dist += p.speed * (heatFactor * 1.5);
const progress = p.dist / p.maxDist;
const opacity = progress < 0.1 ? progress * 10 : progress > 0.8 ? (1 - progress) * 5 : 1;
const cx = 480 + Math.cos(p.angle) * p.dist;
const cy = 410 + Math.sin(p.angle) * p.dist * 0.55;
p.el.setAttribute('cx', cx);
p.el.setAttribute('cy', cy);
p.el.setAttribute('opacity', opacity * heatFactor * 0.7);
p.el.setAttribute('fill', heatFactor > 0.5 ? '#ff6b2b' : '#00d4ff');
p.el.setAttribute('r', 1.5 + heatFactor);
});
// 太阳能光子
state.solarPhotons.forEach(p => {
if (p.delay > 0) { p.delay--; return; }
p.life++;
if (p.life > p.maxLife) {
p.life = 0;
p.x = 250 + Math.random() * 460;
p.y = 0;
p.delay = Math.random() * 60;
}
p.y += p.speed;
const progress = p.life / p.maxLife;
const opacity = progress < 0.2 ? progress * 5 : progress > 0.7 ? (1 - progress) * 3.3 : 1;
// 获取飞艇当前偏移
const alt = getAltitude(t, pump);
const yOffset = -alt * 120;
const filmY = 395 + yOffset - (1 - progress) * 60;
p.el.setAttribute('x1', p.x);
p.el.setAttribute('y1', filmY - 15);
p.el.setAttribute('x2', p.x);
p.el.setAttribute('y2', filmY);
p.el.setAttribute('opacity', opacity * sun * 0.6);
});
}
// ====== 损伤模拟 ======
function simulateDamage() {
if (state.damaged) {
resetDamage();
return;
}
state.damaged = true;
state.damageCabins = 2400000; // 240万个微舱受损
btnDamage.classList.add('active');
btnDamage.innerHTML = '<i class="fas fa-undo"></i> 修复损伤';
// 在外壳上显示损伤区域
const overlay = $('damageOverlay');
overlay.innerHTML = '';
overlay.setAttribute('opacity', '1');
// 创建损伤标记
const dmgCx = 620, dmgCy = 360;
for (let i = 0; i < 12; i++) {
const angle = (i / 12) * Math.PI * 2;
const r = 15 + Math.random() * 25;
const cx = dmgCx + Math.cos(angle) * r;
const cy = dmgCy + Math.sin(angle) * r * 0.6;
const hex = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
const s = 6 + Math.random() * 4;
const pts = [];
for (let j = 0; j < 6; j++) {
const a = (j / 6) * Math.PI * 2 - Math.PI / 6;
pts.push(`${cx + Math.cos(a) * s},${cy + Math.sin(a) * s}`);
}
hex.setAttribute('points', pts.join(' '));
hex.setAttribute('fill', 'rgba(255,34,68,0.4)');
hex.setAttribute('stroke', 'var(--danger)');
hex.setAttribute('stroke-width', '1');
overlay.appendChild(hex);
}
// 损伤中心闪光
const flash = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
flash.setAttribute('cx', dmgCx);
flash.setAttribute('cy', dmgCy);
flash.setAttribute('r', '35');
flash.setAttribute('fill', 'rgba(255,34,68,0.15)');
flash.setAttribute('filter', 'url(#softGlow)');
overlay.appendChild(flash);
// 损伤闪烁动画
let flashCount = 0;
const flashInterval = setInterval(() => {
flash.setAttribute('opacity', flashCount % 2 === 0 ? '0.8' : '0.2');
flashCount++;
if (flashCount > 8) {
clearInterval(flashInterval);
flash.setAttribute('opacity', '0.4');
}
}, 150);
showToast(`损伤模拟:${state.damageCabins.toLocaleString()} 个微舱失效,浮力仅丧失 ${((state.damageCabins / 1.2e9) * 100).toFixed(4)}%`);
}
function resetDamage() {
state.damaged = false;
state.damageCabins = 0;
btnDamage.classList.remove('active');
btnDamage.innerHTML = '<i class="fas fa-bolt"></i> 模拟损伤';
$('damageOverlay').setAttribute('opacity', '0');
$('damageOverlay').innerHTML = '';
// 恢复放大区微舱颜色
zoomCabins.forEach((cabin, i) => {
const poly = cabin.querySelector('polygon');
poly.setAttribute('fill', 'rgba(201,149,106,0.06)');
poly.setAttribute('stroke', 'rgba(201,149,106,0.6)');
});
}
// ====== Toast 提示 ======
function showToast(msg) {
toast.textContent = msg;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 4000);
}
// ====== 主动画循环 ======
function animate(timestamp) {
if (!state.lastTime) state.lastTime = timestamp;
const dt = timestamp - state.lastTime;
state.lastTime = timestamp;
// 自动播放
if (state.autoPlay) {
state.time += state.autoDir * dt * 0.001; // 每秒1小时
if (state.time >= 24) { state.time = 24; state.autoDir = -1; }
if (state.time <= 0) { state.time = 0; state.autoDir = 1; }
timeSlider.value = state.time;
}
const t = state.time;
const pump = state.pumpPower;
updateSky(t);
const airshipState = updateAirship(t, pump);
updateZoomCabins(t, pump);
updateParticles(t, pump);
updateHUD(t, pump);
// 更新时间显示
const hours = Math.floor(t);
const mins = Math.floor((t - hours) * 60);
timeVal.textContent = `${String(hours).padStart(2,'0')}:${String(mins).padStart(2,'0')}`;
state.animFrame = requestAnimationFrame(animate);
}
// ====== 事件绑定 ======
timeSlider.addEventListener('input', e => {
state.time = parseFloat(e.target.value);
});
pumpSlider.addEventListener('input', e => {
state.pumpPower = parseFloat(e.target.value);
pumpVal.textContent = `${Math.round(state.pumpPower)}%`;
});
btnDamage.addEventListener('click', simulateDamage);
btnAutoPlay.addEventListener('click', () => {
state.autoPlay = !state.autoPlay;
btnAutoPlay.classList.toggle('active', state.autoPlay);
btnAutoPlay.innerHTML = state.autoPlay
? '<i class="fas fa-pause"></i> 暂停'
: '<i class="fas fa-play"></i> 自动循环';
});
btnReset.addEventListener('click', () => {
state.time = 12;
state.pumpPower = 70;
state.autoPlay = false;
timeSlider.value = 12;
pumpSlider.value = 70;
pumpVal.textContent = '70%';
btnAutoPlay.classList.remove('active');
btnAutoPlay.innerHTML = '<i class="fas fa-play"></i> 自动循环';
resetDamage();
});
// ====== 初始化 ======
function init() {
initParticles();
state.animFrame = requestAnimationFrame(animate);
}
init();
</script>
</body>
</html>
实现说明
这个高保真 SVG 原理动画聚焦于 TRIZ 最终理想解 (IFR) 思想,直接展示"毛细血管式微元浮力块"在理想工作状态下的运作原理:
视觉设计
- 深色技术蓝图风格:以深蓝黑色为基底,琥珀色(气凝胶结构)为主色调,橙色/青色分别表征热态/冷态,营造出活体工程图的美学
- 蜂窝图案外壳:使用 SVG
<pattern>在飞艇壳体环域内铺设六角微舱纹理,直观传达"分布式浮力"概念 - 7 舱放大视图:右侧放大的 7 个六边形微舱展示内部气体膨胀/收缩、阀门启闭等微观机理
IFR 核心表达
- 消除问题本身:动画不对比传统方案,直接展示"无骨架、无应力集中"的理想状态——壳体即是结构、即是浮力源
- 资源自利用:太阳能薄膜 → 热泵加热 → 气体膨胀 → 爬升,整条链路仅依赖日照这一"免费资源"
- 损伤冗余:点击"模拟损伤"后,240 万个微舱失效,浮力仅下降 0.2%,直观证明"无限冗余单元"如何消灭宏观失效风险
交互控制
| 控件 | 功能 |
|---|---|
| 时刻滑块 | 0~24 小时,控制日照/天空/浮力/高度全链路变化 |
| 热泵功率 | 调节加热强度,影响气体膨胀与升限 |
| 模拟损伤 | 在壳体制造局部破损,观察浮力几乎无变化 |
| 自动循环 | 自动播放完整日夜循环,展示"日升→平流层巡航→夜降→避风"时序 |
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
