
据传说是glm的新模型 这是我的鹈鹕 效果好的很夸张 鹈鹕特化吗
创建一个HTML,内容是SVG绘制一个鹈鹕骑自行车的2D动画,放到本机目录,你不需要任何测试
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>落日公路 · 鹈鹕骑行</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{
background:#101318;min-height:100vh;display:flex;align-items:center;justify-content:center;
font-family:'ZCOOL KuaiLe','Comic Sans MS',cursive;padding:24px;
}
.frame{
position:relative;width:min(1080px,94vw);border-radius:18px;overflow:hidden;
box-shadow:0 30px 80px rgba(0,0,0,.55), inset 0 0 0 1px rgba(255,255,255,.06);
cursor:pointer;user-select:none;-webkit-user-select:none;
}
svg{display:block;width:100%;height:auto}
/* 摄影式暗角,收拢视线 */
.vignette{
position:absolute;inset:0;pointer-events:none;
background:radial-gradient(120% 92% at 50% 42%, transparent 58%, rgba(8,10,16,.4));
}
.ui{position:absolute;pointer-events:none;color:#f8ecd8}
.title{top:20px;left:24px;text-shadow:0 2px 14px rgba(0,0,0,.4)}
.title h1{font-size:clamp(18px,2.6vw,27px);font-weight:normal;letter-spacing:.14em}
.title p{font-size:10px;letter-spacing:.42em;opacity:.55;margin-top:4px;font-family:Georgia,serif}
.speed{left:24px;bottom:18px;text-shadow:0 2px 10px rgba(0,0,0,.45)}
.speed .num{font-size:clamp(22px,3vw,32px)}
.speed .unit{font-size:12px;opacity:.6;margin-left:4px}
.speed .lab{font-size:11px;opacity:.5;letter-spacing:.3em;margin-bottom:2px}
.hint{
right:24px;bottom:18px;font-size:12px;letter-spacing:.18em;opacity:.5;
transition:opacity .3s,color .3s,transform .3s;
}
.hint.on{opacity:1;color:#ffd9a0;transform:translateY(-2px)}
</style>
</head>
<body>
<div class="frame" id="stage">
<svg id="scene" viewBox="0 0 960 600" preserveAspectRatio="xMidYMid meet" aria-label="鹈鹕骑自行车的黄昏海岸动画">
<defs>
<linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#16233c"/>
<stop offset="38%" stop-color="#2e4262"/>
<stop offset="62%" stop-color="#8a6a5c"/>
<stop offset="82%" stop-color="#d99a63"/>
<stop offset="100%" stop-color="#f6c284"/>
</linearGradient>
<radialGradient id="sunGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffd9a3" stop-opacity=".5"/>
<stop offset="100%" stop-color="#ffd9a3" stop-opacity="0"/>
</radialGradient>
<linearGradient id="seaGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#143c4a"/>
<stop offset="100%" stop-color="#2e7a7d"/>
</linearGradient>
<!-- 远景素材:一棵棕榈剪影(原点在树底) -->
<g id="palm">
<path d="M0,0 C4,-26 -2,-52 12,-78 C14,-82 16,-84 18,-86" fill="none" stroke="#1d2b3a" stroke-width="8" stroke-linecap="round"/>
<g fill="#1d2b3a">
<path d="M18,-86 Q52,-98 78,-88 Q50,-90 20,-80 Z"/>
<path d="M18,-86 Q44,-112 70,-114 Q42,-104 20,-82 Z"/>
<path d="M18,-86 Q8,-118 -18,-122 Q6,-108 14,-84 Z"/>
<path d="M18,-86 Q-16,-104 -40,-98 Q-12,-92 14,-80 Z"/>
<path d="M18,-86 Q-6,-70 -22,-52 Q-2,-70 16,-80 Z"/>
<path d="M18,-86 Q40,-72 52,-54 Q34,-72 18,-80 Z"/>
</g>
</g>
<!-- 远景整层内容(周期 960) -->
<g id="farContent">
<path d="M400,385 Q470,358 550,385 Z" fill="#1d2b3a"/>
<use href="#palm" transform="translate(150,386) scale(.9)"/>
<use href="#palm" transform="translate(655,386) scale(-1.12,1.12)"/>
</g>
<!-- 近景素材:芦苇丛与礁石(原点在路面底) -->
<g id="reed">
<g fill="none" stroke="#16202c" stroke-width="4" stroke-linecap="round">
<path d="M0,0 Q-4,-26 -12,-46"/><path d="M6,0 Q6,-32 2,-58"/>
<path d="M12,0 Q16,-28 24,-50"/><path d="M-6,0 Q-12,-20 -22,-34"/>
</g>
<g fill="#16202c">
<ellipse cx="-12" cy="-50" rx="3.5" ry="9" transform="rotate(-15 -12 -50)"/>
<ellipse cx="2" cy="-62" rx="3.5" ry="10" transform="rotate(4 2 -62)"/>
<ellipse cx="24" cy="-54" rx="3.5" ry="9" transform="rotate(18 24 -54)"/>
</g>
</g>
<g id="nearContent">
<use href="#reed" transform="translate(70,600)"/>
<use href="#reed" transform="translate(310,600) scale(.7)"/>
<ellipse cx="205" cy="594" rx="30" ry="11" fill="#141d28"/>
</g>
</defs>
<!-- ======== 天空 · 落日 · 云 ======== -->
<rect x="0" y="0" width="960" height="382" fill="url(#skyGrad)"/>
<circle cx="690" cy="392" r="165" fill="url(#sunGlow)"/>
<circle cx="690" cy="392" r="86" fill="#ffd9a3"/>
<g id="clouds"></g>
<!-- ======== 海面 ======== -->
<rect x="0" y="380" width="960" height="90" fill="url(#seaGrad)"/>
<g id="refl" stroke-linecap="round">
<line x1="658" y1="396" x2="722" y2="396" stroke="#ffcf92" stroke-width="5" opacity=".55"/>
<line x1="664" y1="408" x2="716" y2="408" stroke="#ffcf92" stroke-width="5" opacity=".45"/>
<line x1="670" y1="421" x2="710" y2="421" stroke="#ffcf92" stroke-width="4.5" opacity=".35"/>
<line x1="675" y1="436" x2="705" y2="436" stroke="#ffcf92" stroke-width="4" opacity=".26"/>
<line x1="680" y1="452" x2="700" y2="452" stroke="#ffcf92" stroke-width="3.5" opacity=".16"/>
</g>
<g id="wave1" fill="none" stroke="#8fd0c2" stroke-width="2.5" opacity=".35"></g>
<g id="wave2" fill="none" stroke="#8fd0c2" stroke-width="2.5" opacity=".26"></g>
<g id="wave3" fill="none" stroke="#8fd0c2" stroke-width="2.5" opacity=".2"></g>
<!-- ======== 远景层(棕榈 · 小岛,视差慢速) ======== -->
<g id="farMove"><use href="#farContent"/><use href="#farContent" x="960"/></g>
<!-- ======== 沙滩 · 公路 ======== -->
<rect x="0" y="468" width="960" height="20" fill="#c9a87c"/>
<rect x="0" y="486" width="960" height="114" fill="#3b3733"/>
<rect x="0" y="486" width="960" height="3" fill="#57514a"/>
<line id="dash" x1="-20" y1="548" x2="980" y2="548" stroke="#e6b54c" stroke-width="6" stroke-dasharray="44 36"/>
<g id="specks" fill="#46413b"></g>
<!-- ======== 投影 ======== -->
<ellipse id="shadow" cx="472" cy="525" rx="215" ry="13" fill="#0d0f14" opacity=".22"/>
<!-- ======== 车轮 ======== -->
<g id="wheelR" transform="translate(302,452)">
<circle r="70" fill="none" stroke="#2a2724" stroke-width="11"/>
<circle r="63" fill="none" stroke="#cfc8b8" stroke-width="4"/>
<g stroke="#b9b2a2" stroke-width="2.5">
<line y1="-62" y2="-10"/><line y1="-62" y2="-10" transform="rotate(45)"/>
<line y1="-62" y2="-10" transform="rotate(90)"/><line y1="-62" y2="-10" transform="rotate(135)"/>
<line y1="-62" y2="-10" transform="rotate(180)"/><line y1="-62" y2="-10" transform="rotate(225)"/>
<line y1="-62" y2="-10" transform="rotate(270)"/><line y1="-62" y2="-10" transform="rotate(315)"/>
</g>
<circle r="7" fill="#8f887a"/><circle r="2.5" fill="#4a453f"/>
<circle cy="-64" r="2.2" fill="#8f887a"/>
</g>
<g id="wheelF" transform="translate(650,452)">
<circle r="70" fill="none" stroke="#2a2724" stroke-width="11"/>
<circle r="63" fill="none" stroke="#cfc8b8" stroke-width="4"/>
<g stroke="#b9b2a2" stroke-width="2.5">
<line y1="-62" y2="-10"/><line y1="-62" y2="-10" transform="rotate(45)"/>
<line y1="-62" y2="-10" transform="rotate(90)"/><line y1="-62" y2="-10" transform="rotate(135)"/>
<line y1="-62" y2="-10" transform="rotate(180)"/><line y1="-62" y2="-10" transform="rotate(225)"/>
<line y1="-62" y2="-10" transform="rotate(270)"/><line y1="-62" y2="-10" transform="rotate(315)"/>
</g>
<circle r="7" fill="#8f887a"/><circle r="2.5" fill="#4a453f"/>
<circle cy="-64" r="2.2" fill="#8f887a"/>
</g>
<!-- ======== 远侧腿(画在车架后面,制造内外层次) ======== -->
<g id="legFar">
<line id="legFarT" x1="416" y1="344" x2="440" y2="400" stroke="#c9792c" stroke-width="13" stroke-linecap="round"/>
<line id="legFarS" x1="440" y1="400" x2="448" y2="435" stroke="#d18a3a" stroke-width="10" stroke-linecap="round"/>
<path id="legFarF" d="M-8,-2 L20,-5 L24,0 L-6,4 Z" fill="#c9792c"/>
</g>
<!-- ======== 链条 · 车架 ======== -->
<line id="chainT" x1="448" y1="431" x2="305" y2="446" stroke="#3b3733" stroke-width="3.5" stroke-dasharray="5 4"/>
<line id="chainB" x1="448" y1="453" x2="305" y2="458" stroke="#3b3733" stroke-width="3.5" stroke-dasharray="5 4"/>
<g fill="none" stroke="#1f6f6b" stroke-width="9" stroke-linecap="round" stroke-linejoin="round">
<path d="M302,452 L448,442"/> <!-- 后下叉 -->
<path d="M302,452 L404,330"/> <!-- 后上叉 -->
<path d="M448,442 L400,322"/> <!-- 座管 -->
<path d="M448,442 L626,376"/> <!-- 下管 -->
<path d="M400,322 L620,336"/> <!-- 上管 -->
<path d="M614,296 L626,376"/> <!-- 头管 -->
<path d="M626,376 L650,452"/> <!-- 前叉 -->
</g>
<line x1="400" y1="322" x2="392" y2="302" stroke="#55504a" stroke-width="6" stroke-linecap="round"/>
<path d="M358,300 Q388,288 420,299 Q392,308 358,300 Z" fill="#2a2724"/>
<line x1="614" y1="296" x2="634" y2="274" stroke="#3b3733" stroke-width="7" stroke-linecap="round"/>
<line x1="634" y1="274" x2="652" y2="264" stroke="#211f1c" stroke-width="8" stroke-linecap="round"/>
<circle cx="448" cy="442" r="14" fill="none" stroke="#4a453f" stroke-width="5"/>
<!-- ======== 曲柄与踏板 ======== -->
<g id="crank" transform="translate(448,442)">
<line y2="-40" stroke="#33302c" stroke-width="7" stroke-linecap="round"/>
<line y2="40" stroke="#33302c" stroke-width="7" stroke-linecap="round"/>
<g id="pedalA" transform="translate(0,-40)"><rect x="-14" y="-3.5" width="28" height="7" rx="3" fill="#211f1c"/></g>
<g id="pedalB" transform="translate(0,40)"><rect x="-14" y="-3.5" width="28" height="7" rx="3" fill="#211f1c"/></g>
<circle r="6" fill="#55504a"/>
</g>
<!-- ======== 鹈鹕 ======== -->
<g id="upper">
<!-- 尾羽 -->
<path d="M386,318 Q348,296 322,304 Q352,312 384,328 Z" fill="#eadfc6"/>
<path d="M388,326 Q346,318 322,330 Q354,330 386,336 Z" fill="#e0d2b4"/>
<path d="M390,334 Q356,336 336,352 Q366,344 392,344 Z" fill="#eadfc6"/>
<!-- 身体 -->
<ellipse cx="436" cy="302" rx="63" ry="41" transform="rotate(-13 436 302)" fill="#f7efdf" stroke="#d9cdb4" stroke-width="1.5"/>
<!-- 脖子 -->
<path d="M466,290 C500,276 480,240 502,212 C510,202 518,194 526,188" fill="none" stroke="#f7efdf" stroke-width="23" stroke-linecap="round"/>
<!-- 头部组(会点头) -->
<g id="headG">
<circle cx="526" cy="184" r="23" fill="#f7efdf"/>
<path d="M516,163 q-2,-9 4,-14 M524,161 q1,-9 8,-12" fill="none" stroke="#e3d6bc" stroke-width="3" stroke-linecap="round"/>
<!-- 眼睛 -->
<circle cx="534" cy="176" r="6" fill="#eec95f"/>
<circle cx="535.5" cy="175.5" r="3" fill="#1d1a17"/>
<circle cx="536.8" cy="174.2" r="1.2" fill="#fff"/>
<circle id="lid" cx="0" cy="0" r="6.8" fill="#f7efdf" transform="translate(534,176) scale(1,0)"/>
<!-- 上喙(会张嘴) -->
<g id="beakTop">
<path d="M540,178 C580,170 622,178 648,196 C650,198 648,201 644,200 C616,192 580,188 542,190 Z"
fill="#f2a24b" stroke="#d9822f" stroke-width="1"/>
<line x1="548" y1="182" x2="560" y2="181" stroke="#d9822f" stroke-width="1.5"/>
</g>
<!-- 喉囊(动态鼓动) -->
<path id="pouch" d="" fill="#ee7f4a" opacity=".95"/>
</g>
<!-- 围巾 -->
<rect x="452" y="262" width="30" height="15" rx="7" fill="#cf4a38" transform="rotate(-16 467 269)"/>
<path id="scarf1" d="" fill="none" stroke="#cf4a38" stroke-width="9" stroke-linecap="round"/>
<path id="scarf2" d="" fill="none" stroke="#b83e2e" stroke-width="7" stroke-linecap="round"/>
<!-- 翅膀(搭在车把上) -->
<path d="M444,298 C470,272 530,258 592,260 C618,261 640,264 650,268 C654,271 652,276 646,276
C636,283 620,285 606,281 C560,297 505,307 466,309 C450,310 438,306 444,298 Z"
fill="#efe3cc" stroke="#d9cdb4" stroke-width="1.5"/>
<path d="M470,296 C520,286 570,278 615,272 M462,303 C515,295 570,287 618,279"
fill="none" stroke="#dccfb2" stroke-width="2"/>
<path d="M646,276 l10,4 M642,279 l8,6" stroke="#d9cdb4" stroke-width="2.5" stroke-linecap="round"/>
</g>
<!-- ======== 近侧腿 ======== -->
<g id="legNear">
<line id="legNearT" x1="416" y1="344" x2="440" y2="400" stroke="#ea9a44" stroke-width="13" stroke-linecap="round"/>
<line id="legNearS" x1="440" y1="400" x2="448" y2="435" stroke="#f0a851" stroke-width="10" stroke-linecap="round"/>
<path id="legNearF" d="M-8,-2 L20,-5 L24,0 L-6,4 Z" fill="#ea9a44"/>
</g>
<!-- ======== 车铃与冲刺特效 ======== -->
<g id="bellRing" transform="translate(628,267)" opacity="0" fill="none" stroke="#ffe6ba" stroke-width="2.5" stroke-linecap="round">
<path d="M-9,-9 A13,13 0 0 1 9,-9"/>
<path d="M-13,-13 A19,19 0 0 1 13,-13"/>
</g>
<g id="fx"></g>
<!-- ======== 近景层(芦苇掠过) ======== -->
<g id="nearMove"><use href="#nearContent"/><use href="#nearContent" x="480"/></g>
</svg>
<div class="vignette"></div>
<div class="ui title"><h1>落日公路 · 鹈鹕骑行</h1><p>A PELICAN ON WHEELS — GOLDEN HOUR RIDE</p></div>
<div class="ui speed"><div class="lab">时 速</div><span class="num" id="hudSpeed">21</span><span class="unit">km/h</span></div>
<div class="ui hint" id="hint">点击画面 / 按空格 —— 冲刺</div>
</div>
<script>
(function(){
'use strict';
const $ = id => document.getElementById(id);
const NS = 'http://www.w3.org/2000/svg';
const RM = matchMedia('(prefers-reduced-motion: reduce)').matches;
// ---------- 几何常量 ----------
const RW = {x:302,y:452}, FW = {x:650,y:452}, BB = {x:448,y:442};
const HIP = {x:416,y:344}, L1 = 72, L2 = 76, CRANK = 40, WHEEL_R = 70;
const BASE = RM ? 110 : 250;
// ---------- 状态 ----------
let last = performance.now(), T = 0;
let dist = 0, phase = 0, boost = 0;
let blinkStart = -10, nextBlink = 2.5, openT = -10, ringT = -10;
const lines = []; // 冲刺速度线
const clouds = [
{x:120,y:96, s:1.15,v:12,o:.5 }, {x:520,y:150,s:.8, v:18,o:.34},
{x:820,y:70, s:1, v:9, o:.42}, {x:300,y:200,s:.6, v:24,o:.25},
];
// ---------- 初始化:云 ----------
const cloudG = $('clouds');
clouds.forEach(c=>{
const g = document.createElementNS(NS,'g');
g.setAttribute('fill','#f6e7d2'); g.setAttribute('opacity',c.o);
g.innerHTML = '<ellipse rx="46" ry="14"/><ellipse cx="-26" cy="4" rx="26" ry="10"/><ellipse cx="28" cy="5" rx="30" ry="11"/>';
cloudG.appendChild(g); c.el = g;
});
// ---------- 初始化:波浪线(周期 60px 的重复波纹) ----------
function wavePath(width, amp){
let d = 'M0 0';
for(let x=0; x<width; x+=60) d += ` q15 ${-amp} 30 0 t30 0`;
return d;
}
[['wave1',402],['wave2',428],['wave3',452]].forEach(([id,y])=>{
const p = document.createElementNS(NS,'path');
p.setAttribute('d', wavePath(1560, 7));
p.setAttribute('transform', `translate(0,${y})`);
$(id).appendChild(p);
});
// ---------- 初始化:路面碎石 ----------
const specksG = $('specks');
[[120,515,9,3],[340,575,12,4],[560,530,7,2.5],[720,585,10,3.5],[860,520,8,3],[60,590,11,4]]
.forEach(([x,y,rx,ry])=>{
for(const off of [0,960]){
const e = document.createElementNS(NS,'ellipse');
e.setAttribute('cx',x+off); e.setAttribute('cy',y);
e.setAttribute('rx',rx); e.setAttribute('ry',ry); e.setAttribute('opacity','.7');
specksG.appendChild(e);
}
});
// ---------- 工具:Catmull-Rom 平滑曲线 ----------
function crPath(pts){
let d = `M${pts[0][0].toFixed(1)},${pts[0][1].toFixed(1)}`;
for(let i=0;i<pts.length-1;i++){
const p0=pts[Math.max(i-1,0)], p1=pts[i], p2=pts[i+1], p3=pts[Math.min(i+2,pts.length-1)];
d += `C${(p1[0]+(p2[0]-p0[0])/6).toFixed(1)},${(p1[1]+(p2[1]-p0[1])/6).toFixed(1)} `
+ `${(p2[0]-(p3[0]-p1[0])/6).toFixed(1)},${(p2[1]-(p3[1]-p1[1])/6).toFixed(1)} `
+ `${p2[0].toFixed(1)},${p2[1].toFixed(1)}`;
}
return d;
}
// ---------- 工具:两段式腿部 IK(膝盖朝前) ----------
function solveKnee(hx,hy,fx,fy){
let dx=fx-hx, dy=fy-hy, d=Math.hypot(dx,dy);
d = Math.max(Math.abs(L1-L2)+.5, Math.min(L1+L2-.5, d));
const a1 = Math.atan2(dy,dx);
const a2 = Math.acos(Math.min(1, Math.max(-1,(L1*L1+d*d-L2*L2)/(2*L1*d))));
const ka = a1 - a2;
return [hx+Math.cos(ka)*L1, hy+Math.sin(ka)*L1];
}
function setLeg(prefix, hx, hy, fx, fy){
const [kx,ky] = solveKnee(hx,hy,fx,fy);
const t=$(prefix+'T'), s=$(prefix+'S'), f=$(prefix+'F');
t.setAttribute('x1',hx); t.setAttribute('y1',hy); t.setAttribute('x2',kx); t.setAttribute('y2',ky);
s.setAttribute('x1',kx); s.setAttribute('y1',ky); s.setAttribute('x2',fx); s.setAttribute('y2',fy);
f.setAttribute('transform',`translate(${fx},${fy})`);
}
// ---------- 喉囊外形(squash 参数控制鼓动) ----------
function pouchD(s){
const k = 1 + 0.055*s, f = y => (192+(y-192)*k).toFixed(1);
return `M542,192 C552,${f(226)} 578,${f(246)} 604,${f(236)} C624,${f(228)} 636,${f(214)} 642,202 C644,199 645,197 644,196 C616,189 578,186 542,190 Z`;
}
// ---------- 围巾飘带 ----------
function ribbon(x0,y0,n,ph,amp,wf){
const pts=[];
for(let i=0;i<n;i++)
pts.push([x0-i*21, y0+i*4.5 + Math.sin(T*wf+ph+i*1.15)*amp*(i/(n-1))]);
return crPath(pts);
}
// ---------- 交互:冲刺 ----------
const hint = $('hint');
function kick(){
boost = Math.min(boost+0.9, 1.6);
openT = T; ringT = T;
hint.classList.add('on');
setTimeout(()=>hint.classList.remove('on'), 700);
}
$('stage').addEventListener('pointerdown', kick);
addEventListener('keydown', e=>{ if(e.code==='Space'){ e.preventDefault(); kick(); } });
// ---------- 主循环 ----------
const hud = $('hudSpeed');
let hudCache = -1;
function frame(now){
const dt = Math.min(0.05, (now-last)/1000); last = now; T += dt;
// 速度模型
boost *= Math.exp(-dt/1.4);
if(boost < .003) boost = 0;
const v = BASE*(1+boost*0.85);
dist += v*dt;
phase += 5.1*(1+boost*0.5)*dt;
const ph = phase, phd = ph*180/Math.PI;
// 车轮(转动角与里程一致)
const wd = (dist/WHEEL_R*57.29578)%360;
$('wheelR').setAttribute('transform',`translate(${RW.x},${RW.y}) rotate(${wd})`);
$('wheelF').setAttribute('transform',`translate(${FW.x},${FW.y}) rotate(${wd})`);
// 曲柄 + 踏板保持水平
$('crank').setAttribute('transform',`translate(${BB.x},${BB.y}) rotate(${phd})`);
$('pedalA').setAttribute('transform',`translate(0,-40) rotate(${-phd})`);
$('pedalB').setAttribute('transform',`translate(0,40) rotate(${-phd})`);
// 链条流动(上链向前、下链向后)
$('chainT').setAttribute('stroke-dashoffset', -((dist*.5)%9));
$('chainB').setAttribute('stroke-dashoffset', ((dist*.5)%9));
// 身体起伏 / 前倾 / 点头
const bob = 2.6*Math.sin(2*ph+.7);
const tilt = 1.5*Math.sin(2*ph+1.9) + boost*2.5;
const nod = 2.2*Math.sin(2*ph+2.6) - boost*3;
$('upper').setAttribute('transform',`translate(0 ${bob.toFixed(2)}) rotate(${tilt.toFixed(2)} 416 344)`);
$('headG').setAttribute('transform',`rotate(${nod.toFixed(2)} 505 212)`);
// 张嘴(点击后短促张开)
const age = T-openT;
const open = age>=0 ? Math.min(1,age*8)*Math.exp(-age*3) : 0;
$('beakTop').setAttribute('transform',`rotate(${(-7*open).toFixed(2)} 542 186)`);
// 喉囊鼓动
const sq = Math.sin(2*ph+1.2)*.6 + Math.sin(T*7.3)*.15 + open*1.6;
$('pouch').setAttribute('d', pouchD(sq));
// 双腿 IK(近/远踏板相位差 π)
const hx = HIP.x, hy = HIP.y + bob;
const nx = BB.x + CRANK*Math.cos(ph), ny = BB.y + CRANK*Math.sin(ph);
const fx2= BB.x - CRANK*Math.cos(ph), fy2= BB.y - CRANK*Math.sin(ph);
setLeg('legNear', hx,hy, nx, ny-7);
setLeg('legFar', hx,hy, fx2,fy2-7);
// 围巾
const amp = 3.2 + boost*7, wf = 6.5 + boost*5;
$('scarf1').setAttribute('d', ribbon(456,266,5,0, amp,wf));
$('scarf2').setAttribute('d', ribbon(454,272,4,.9, amp*.85,wf*1.1));
// 眨眼
if(T > nextBlink){ blinkStart = T; nextBlink = T + 2.4 + Math.random()*2.8; }
const bAge = T-blinkStart;
const lidS = (bAge>=0 && bAge<.16) ? Math.sin(Math.PI*bAge/.16) : 0;
$('lid').setAttribute('transform',`translate(534,176) scale(1,${lidS.toFixed(3)})`);
// 车铃涟漪
const rAge = T-ringT;
if(rAge>=0 && rAge<.5){
$('bellRing').setAttribute('opacity',(1-rAge/.5).toFixed(2));
$('bellRing').setAttribute('transform',`translate(628,267) scale(${(1+rAge*2.2).toFixed(2)})`);
} else $('bellRing').setAttribute('opacity','0');
// 冲刺速度线
if(boost>.2 && Math.random()<dt*22 && lines.length<14){
const el = document.createElementNS(NS,'line');
const ln = {x:260+Math.random()*170, y:240+Math.random()*190, len:30+Math.random()*40, life:.5+Math.random()*.3, el};
el.setAttribute('y1',ln.y); el.setAttribute('y2',ln.y);
el.setAttribute('stroke-width','2.5'); el.setAttribute('stroke-linecap','round');
$('fx').appendChild(el); lines.push(ln);
}
for(let i=lines.length-1;i>=0;i--){
const ln = lines[i];
ln.x -= (750+boost*400)*dt; ln.life -= dt*2.2;
if(ln.life<=0 || ln.x<-80){ ln.el.remove(); lines.splice(i,1); continue; }
ln.el.setAttribute('x1',ln.x); ln.el.setAttribute('x2',ln.x+ln.len);
ln.el.setAttribute('stroke',`rgba(255,240,220,${(ln.life*1.4).toFixed(2)})`);
}
// 背景:云
clouds.forEach(c=>{
c.x += (c.v + v*.06)*dt;
if(c.x>1100) c.x = -140;
c.el.setAttribute('transform',`translate(${c.x.toFixed(1)},${c.y}) scale(${c.s})`);
});
// 背景:波浪
const wOff = (T*v*.5)%120;
$('wave1').setAttribute('transform',`translate(${(-wOff).toFixed(1)},402)`);
$('wave2').setAttribute('transform',`translate(${(-wOff*.7).toFixed(1)},428)`);
$('wave3').setAttribute('transform',`translate(${(-wOff*1.3).toFixed(1)},452)`);
$('refl').setAttribute('opacity',(.75+.15*Math.sin(T*2.1)).toFixed(2));
// 背景:视差层
$('farMove').setAttribute('transform',`translate(${(-(dist*.22)%960).toFixed(1)},0)`);
$('nearMove').setAttribute('transform',`translate(${(-(dist*1.3)%480).toFixed(1)},0)`);
// 背景:路面
$('dash').setAttribute('stroke-dashoffset', (dist%80).toFixed(1));
specksG.setAttribute('transform',`translate(${(-(dist%960)).toFixed(1)},0)`);
// 投影微缩
$('shadow').setAttribute('transform',`translate(0,0) scale(${(1+.012*Math.sin(2*ph+.7)).toFixed(3)},1)`);
// HUD
const kmh = Math.round(v*.085);
if(kmh!==hudCache){ hud.textContent = kmh; hudCache = kmh; }
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>