qwen3.8-flash-next Q4实测震惊我一整天
觉浅
2026-08-27 16:48
1
“震惊得没话说”
创建一个HTML,内容是SVG绘制一个鹈鹕骑自行车的2D动画。

这也太离谱了,没有继续试的欲望了
最新回复 (8)
-
酷睿哥 08-27 16:491楼最低也得FP8的才能用,Q4的就只能当玩具
-
🔨 08-27 16:522楼qwen这是强到飞起,就问你强不强
-
ClaudeCode 08-27 16:543楼咋感觉还不如3.8 27b小模型,基本没啥bug画出来的东西
-
RS_HIT 08-27 16:564楼qwen不是问强不强,就问你飞没飞 ^-^
-
觉浅 楼主 08-27 17:155楼Q4质量本来就不差,其他模型特别是qwen3.8 27b体感上跟fp8几乎没差别,再者哪有那么多显存跑FP8
-
觉浅 楼主 08-27 17:166楼强强强,飞飞飞,这次是真真真飞了,啥飞了你别管
-
Grandy 08-27 17:187楼modelscope上的3.8-next,很强啊

-
Grandy 08-27 17:218楼不知道怎么发动图,发个代码吧。接的阉割版cherry-studio直出的。
HTML
<!DOCTYPE html>
<html lang="zh-CN" data-scene="day">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>鹈鹕骑车 · PELICAN CYCLE WORKS</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,400..700&family=IBM+Plex+Mono:wght@400;600&family=Karla:wght@400;600;700&family=Noto+Serif+SC:wght@400;700;900&display=swap" rel="stylesheet">
<style>
/* ============ 调色板 / 主题 ============ */
:root{
--cad:0.9s;
--paper:#f3ece0; --ink:#141b1a; --ink-soft:rgba(20,27,26,.62);
--line:rgba(20,27,26,.16);
--accent:#c8452f; --accent-2:#1a7483;
--sky-1:#b3d9e0; --sky-2:#dff0ea; --sky-3:#fdf1d4;
--sun:#ffc95c; --sun-glow:#ffe7ab; --sun-r:46px; --sun-band:0;
--cloud:#ffffff; --cloud-o:.92;
--sea:#2b808d; --foam:#e6f3ef;
--hill-far:#a2c1b5; --hill-mid:#7aa88d; --hill-near:#5c8c69;
--dune:#e8cd94; --dune-2:#d3b275;
--road:#4a5057; --road-2:#3b4046; --dash:#f0e2b8;
--pole:rgba(35,45,44,.62);
--frame:#c8452f; --frame-2:#8d2c1b; --metal:#5d666d; --tyre:#22292b;
--bird:#17201f;
--pelican:#fdf7ee; --pelican-sh:#e3d3bd; --beak:#f79f3f; --beak-2:#dc7c22;
--pouch:#ffc36a; --foot:#f6a13c; --cap:#e9b23c;
--shadow:rgba(18,30,28,.30);
--streak-o:0;
}
:root[data-scene="dusk"]{
--sky-1:#122740; --sky-2:#3d3350; --sky-3:#f5934f;
--sun:#ff9a45; --sun-glow:#ffb765; --sun-r:82px; --sun-band:.75;
--cloud:#6c7a97; --cloud-o:.55;
--sea:#0f3345; --foam:#4c7f8c;
--hill-far:#33465c; --hill-mid:#26364a; --hill-near:#1b2838;
--dune:#4d4a55; --dune-2:#3b3945;
--road:#242a33; --road-2:#191e26; --dash:#c9bd9a;
--pole:rgba(12,18,24,.8);
--frame:#a5372a; --frame-2:#6d1f18; --metal:#454e58; --tyre:#111619;
--bird:#0c1218;
--pelican:#ddd8cd; --pelican-sh:#a9a49b; --beak:#d97f2f; --beak-2:#a8551b;
--pouch:#e6a055; --foot:#d3873a; --cap:#bd8b33;
--shadow:rgba(4,8,14,.45);
--paper:#16202b; --ink:#f0e7d8; --ink-soft:rgba(240,231,216,.6);
--line:rgba(240,231,216,.18);
}
/* ============ 基础 ============ */
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;background:var(--paper);color:var(--ink);
font-family:"Karla","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
overflow-x:hidden;transition:background .9s ease,color .9s ease}
.mono{font-family:"IBM Plex Mono",ui-monospace,monospace}
.display{font-family:"Fraunces","Noto Serif SC",Georgia,serif}
h1,h2,h3{font-family:"Fraunces","Noto Serif SC",Georgia,serif;font-weight:900;
font-variation-settings:'SOFT' 40,'WONK' 1;margin:0}
svg{display:block;overflow:visible}
svg *{transition:fill .9s ease,stroke .9s ease,opacity .9s ease}
:root.nomo svg *, :root.nomo .panel, :root.nomo .anno, :root.nomo .stage{transition:none}
/* ============ 舞台 ============ */
.stage-wrap{position:relative}
.stage{position:relative;height:clamp(430px,86svh,780px);overflow:hidden;
background:var(--sky-1);border-bottom:1px solid var(--line)}
.scene{position:absolute;inset:0;width:100%;height:100%}
.vignette{position:absolute;inset:0;pointer-events:none;
background:radial-gradient(120% 90% at 50% 58%,transparent 40%,rgba(12,20,18,.34) 100%)}
.grain{position:absolute;inset:0;pointer-events:none;mix-blend-mode:overlay;opacity:.15}
.flash{position:absolute;inset:0;pointer-events:none;background:#fff;opacity:0}
.flash.go{animation:fl .45s ease-out}
@keyframes fl{0%{opacity:.42}100%{opacity:0}}
.mast{position:absolute;top:clamp(14px,2.4vw,26px);left:clamp(14px,2.6vw,30px);
display:flex;align-items:center;gap:12px;pointer-events:none}
.mono-tile{width:40px;height:40px;display:grid;place-items:center;background:var(--accent);
color:#fff8ec;font-family:"Fraunces",serif;font-weight:900;font-size:19px;letter-spacing:.02em;
box-shadow:3px 3px 0 rgba(20,27,26,.85);transition:transform .5s cubic-bezier(.2,.8,.2,1)}
.mast-txt b{display:block;font-size:.74rem;letter-spacing:.2em;font-weight:700}
.mast-txt span{display:block;font-size:.6rem;letter-spacing:.14em;color:var(--ink-soft)}
.stage:not(.scrim) .mast-t{opacity:0;transform:translateY(-6px)}
.mast-t{transition:opacity .5s,transform .5s}
.rail{position:absolute;left:clamp(10px,1.4vw,18px);top:50%;transform:translateY(-50%) rotate(180deg);
writing-mode:vertical-rl;font-size:.58rem;letter-spacing:.36em;color:var(--ink-soft);
pointer-events:none;opacity:.9}
.headline{position:absolute;left:clamp(52px,6vw,86px);top:clamp(66px,11vh,120px);
pointer-events:none;max-width:min(46vw,560px)}
.headline .kick{font-size:.62rem;letter-spacing:.34em;color:var(--accent);font-weight:700}
.headline h1{font-size:clamp(2.5rem,7.4vw,6.1rem);line-height:.86;letter-spacing:-.02em;
text-shadow:0 2px 0 rgba(255,255,255,.18)}
.headline .sub{display:block;font-size:clamp(.72rem,1.5vw,1rem);font-weight:400;font-style:italic;
letter-spacing:.02em;margin-top:.5em;color:var(--ink);opacity:.85}
.figcap{position:absolute;right:clamp(14px,2.6vw,30px);bottom:clamp(12px,2vw,22px);
text-align:right;font-size:.6rem;letter-spacing:.1em;color:var(--ink-soft);pointer-events:none}
.fine{position:absolute;left:clamp(14px,2.6vw,30px);bottom:clamp(12px,2vw,22px);
font-size:.58rem;letter-spacing:.1em;color:var(--ink-soft);pointer-events:none}
.anno{position:absolute;pointer-events:none;font-size:.6rem;letter-spacing:.05em;
color:var(--ink);opacity:0;transform:translateY(6px);transition:opacity .7s .1s,transform .7s .1s}
.anno.in{opacity:.92;transform:none}
.anno i{display:block;width:64px;height:1px;background:currentColor;opacity:.5;margin-bottom:5px}
.anno b{background:rgba(255,255,255,.55);padding:1px 5px;border-bottom:1px solid var(--accent)}
:root[data-scene="dusk"] .anno b{background:rgba(10,16,24,.45)}
.a1{left:22%;top:18%}.a2{left:57%;top:7%;text-align:right}.a2 i{margin-left:auto}
.a3{left:70%;top:64%}.a4{left:16%;top:66%}
/* ============ 控制台 ============ */
.deck{max-width:1320px;margin:-1px auto 0;padding:clamp(16px,2.6vw,30px) clamp(14px,3vw,34px) clamp(30px,5vw,58px);
display:grid;gap:clamp(12px,1.6vw,20px);
grid-template-columns:minmax(210px,.9fr) minmax(230px,1.15fr) minmax(240px,1fr)}
.panel{position:relative;background:var(--paper);border:1px solid var(--line);
padding:14px 16px 15px;box-shadow:4px 4px 0 rgba(20,27,26,.09);
transition:transform .35s cubic-bezier(.2,.8,.2,1),box-shadow .35s,background .9s,border-color .9s}
:root[data-scene="dusk"] .panel{box-shadow:4px 4px 0 rgba(0,0,0,.35)}
.panel:hover{transform:translateY(-3px);box-shadow:7px 8px 0 rgba(20,27,26,.14)}
.p-label{font-size:.58rem;letter-spacing:.28em;font-weight:700;color:var(--ink-soft);display:block}
.p-num{display:block;font-weight:600;font-size:clamp(1.9rem,3.4vw,2.8rem);line-height:1;
letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.p-sub{display:block;font-size:.6rem;letter-spacing:.14em;color:var(--ink-soft);margin-top:6px}
.gauge{height:7px;background:var(--line);margin-top:9px;overflow:hidden}
.gauge i{display:block;height:100%;width:0;background:var(--accent);transition:width .2s linear}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px 14px;margin-top:4px}
.grid2 .p-num{font-size:clamp(1.05rem,2vw,1.4rem)}
.spark{position:absolute;right:14px;top:14px;width:96px;height:34px}
.spark polyline{fill:none;stroke:var(--accent-2);stroke-width:1.6;opacity:.85}
.spark line{stroke:var(--line);stroke-width:1}
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:26px;background:none;cursor:ew-resize}
input[type=range]::-webkit-slider-runnable-track{height:4px;background:var(--line)}
input[type=range]::-moz-range-track{height:4px;background:var(--line)}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:24px;margin-top:-10px;
background:var(--accent);border:1px solid rgba(0,0,0,.35);transition:transform .2s}
input[type=range]::-moz-range-thumb{width:14px;height:24px;border-radius:0;background:var(--accent);border:1px solid rgba(0,0,0,.35)}
input[type=range]:hover::-webkit-slider-thumb{transform:scaleY(1.18)}
input[type=range]:focus-visible{outline:2px solid var(--accent-2);outline-offset:4px}
.hint{font-size:.62rem;letter-spacing:.08em;color:var(--ink-soft);margin:2px 0 8px}
.btns{display:flex;flex-wrap:wrap;gap:8px}
.btn{font:inherit;font-size:.72rem;letter-spacing:.12em;font-weight:700;padding:9px 13px;
border:1px solid var(--ink);background:transparent;color:var(--ink);cursor:pointer;
display:inline-flex;align-items:center;gap:7px;
transition:background .22s,color .22s,transform .22s,box-shadow .22s}
.btn:hover{background:var(--ink);color:var(--paper);transform:translate(-2px,-2px);box-shadow:3px 3px 0 var(--accent)}
.btn:active{transform:translate(0,0);box-shadow:none}
.btn[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:#fff8ec}
.btn .dot{width:7px;height:7px;border-radius:50%;background:#8ec96b;box-shadow:0 0 0 0 rgba(142,201,107,.7);
animation:pip 1.6s infinite}
.btn[aria-pressed=false] .dot{background:#9a938a;animation:none}
@keyframes pip{0%{box-shadow:0 0 0 0 rgba(142,201,107,.6)}70%{box-shadow:0 0 0 7px rgba(142,201,107,0)}100%{box-shadow:0 0 0 0 rgba(142,201,107,0)}}
.fishn{letter-spacing:.28em;color:var(--accent-2)}
.fishn.off{color:var(--ink-soft);opacity:.4}
.fine2{display:block;margin-top:10px;font-size:.56rem;letter-spacing:.1em;color:var(--ink-soft)}
/* ============ 说明区 ============ */
.notes{max-width:1320px;margin:0 auto;padding:clamp(30px,6vw,84px) clamp(14px,3vw,34px) 40px;
border-top:1px solid var(--line);display:grid;gap:clamp(20px,4vw,60px);
grid-template-columns:minmax(240px,.85fr) minmax(280px,1.15fr)}
.sec-head h2{font-size:clamp(2rem,4.6vw,3.5rem);line-height:.94}
.sec-head p{font-size:.94rem;line-height:1.65;color:var(--ink-soft);max-width:34ch;margin-top:14px}
.item{display:grid;grid-template-columns:64px 1fr;gap:16px;padding:20px 16px;border-top:1px solid var(--line);
transition:background .35s,transform .35s}
.item:hover{background:rgba(200,69,47,.06);transform:translateX(5px)}
.item .num{font-family:"Fraunces",serif;font-weight:900;font-size:2rem;line-height:1;
color:transparent;-webkit-text-stroke:1.2px var(--accent)}
.item h3{font-size:1.16rem;letter-spacing:.01em}
.item p{margin:8px 0 0;font-size:.92rem;line-height:1.7;color:var(--ink-soft)}
.item code{font-family:"IBM Plex Mono",monospace;font-size:.8em;background:rgba(20,27,26,.07);padding:1px 4px}
.reveal{opacity:0;transform:translateY(22px);transition:opacity .8s cubic-bezier(.2,.7,.2,1),transform .8s cubic-bezier(.2,.7,.2,1);
transition-delay:calc(var(--i,0)*100ms)}
.reveal.vis{opacity:1;transform:none}
footer{border-top:1px solid var(--line);padding:22px clamp(14px,3vw,34px) 60px;font-size:.6rem;
letter-spacing:.2em;color:var(--ink-soft);display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap}
@media (max-width:940px){
.deck{grid-template-columns:1fr 1fr}.panel.p-ctrl{grid-column:1/-1}
.rail,.anno{display:none}.headline{max-width:70vw}
.notes{grid-template-columns:1fr}
}
@media (max-width:560px){
.deck{grid-template-columns:1fr}.stage{height:clamp(400px,64svh,520px)}
.spark{display:none}.figcap{max-width:46%}
}
</style>
</head>
<body>
<div class="stage-wrap">
<div class="stage" id="stage">
<svg class="scene" id="scene" viewBox="0 0 1200 700" preserveAspectRatio="xMidYMid slice"
role="img" aria-label="一只戴骑行帽的鹈鹕骑着复古公路自行车,沿海边公路前进">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" style="stop-color:var(--sky-1)"/>
<stop offset=".55" style="stop-color:var(--sky-2)"/>
<stop offset="1" style="stop-color:var(--sky-3)"/>
</linearGradient>
<radialGradient id="sunG" cx=".5" cy=".5" r=".5">
<stop offset="0" style="stop-color:var(--sun-glow);stop-opacity:.85"/>
<stop offset="1" style="stop-color:var(--sun-glow);stop-opacity:0"/>
</radialGradient>
<filter id="soft" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="5"/>
</filter>
<filter id="soft2" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="2.2"/>
</filter>
<filter id="grainf"><feTurbulence type="fractalNoise" baseFrequency=".9" numOctaves="3"/></filter>
<clipPath id="clipScene"><rect x="0" y="0" width="1200" height="700"/></clipPath>
<!-- 可平铺的元素原型 -->
<g id="poleProto">
<rect x="-4" y="-252" width="8" height="252" style="fill:var(--pole)"/>
<path d="M-34,-246 H34 M-26,-230 H26" stroke-width="6" stroke-linecap="round" style="stroke:var(--pole)"/>
<circle cx="-34" cy="-250" r="3.5" style="fill:var(--pole)"/><circle cx="34" cy="-250" r="3.5" style="fill:var(--pole)"/>
<circle cx="-26" cy="-234" r="3" style="fill:var(--pole)"/><circle cx="26" cy="-234" r="3" style="fill:var(--pole)"/>
<path class="wire" d="M-34,-250 q120,22 240,0" fill="none" stroke-width="1.8" style="stroke:var(--pole)"/>
<path class="wire" d="M-26,-234 q120,26 240,0" fill="none" stroke-width="1.8" style="stroke:var(--pole)"/>
<path d="M-2,-252 q-6,-14 4,-18 q10,-4 8,8 q-2,10 -12,10 z" style="fill:var(--pole)"/>
</g>
<g id="turbineProto">
<path d="M-6,0 L-2,-140 L2,-140 L6,0 Z" style="fill:var(--hill-far)"/>
<g class="blades" style="transform-origin:0px -140px">
<path d="M0,-140 L7,-208 L-3,-208 Z" style="fill:var(--hill-far)"/>
<path d="M0,-140 L7,-208 L-3,-208 Z" transform="rotate(120)" style="fill:var(--hill-far)"/>
<path d="M0,-140 L7,-208 L-3,-208 Z" transform="rotate(240)" style="fill:var(--hill-far)"/>
</g>
<circle cx="0" cy="-140" r="5" style="fill:var(--hill-mid)"/>
</g>
<g id="bushProto">
<path d="M-30,0 C-34,-26 -14,-34 0,-30 C12,-40 34,-30 32,-6 C30,2 20,4 0,4 C-16,4 -28,4 -30,0 Z" style="fill:var(--hill-near)"/>
<path d="M-14,-16 C-6,-24 8,-22 12,-12" fill="none" stroke-width="2" style="stroke:var(--hill-mid);opacity:.7"/>
</g>
<g id="tuftProto">
<path d="M0,0 C-3,-16 -12,-24 -16,-26 C-9,-22 -4,-12 -2,0 Z" style="fill:var(--dune-2)"/>
<path d="M0,0 C1,-20 6,-30 10,-34 C6,-24 4,-12 4,0 Z" style="fill:var(--dune-2)"/>
<path d="M0,0 C-1,-13 -8,-20 -11,-22 C-6,-15 -4,-8 -3,0 Z" style="fill:var(--dune-2)"/>
</g>
<g id="cloudProto">
<path d="M0,0 C6,-22 34,-30 52,-16 C64,-32 96,-28 104,-8 C126,-10 136,8 118,14 L6,14 C-10,12 -8,4 0,0 Z"
style="fill:var(--cloud);opacity:var(--cloud-o)"/>
</g>
<g id="birdProto">
<path class="wb" d="M0,0 q9,-8 18,0 q-9,-4 -18,0 M20,0 q9,-8 18,0 q-9,-4 -18,0"
style="fill:var(--bird);opacity:.75"/>
</g>
</defs>
<g clip-path="url(#clipScene)">
<!-- 天空 -->
<rect x="0" y="0" width="1200" height="700" fill="url(#sky)"/>
<circle cx="900" cy="200" r="200" fill="url(#sunG)"/>
<circle class="sunDisc" cx="900" cy="200" r="46" style="fill:var(--sun);r:var(--sun-r)"/>
<rect class="sunBand" x="820" y="196" width="160" height="4" rx="2" style="fill:var(--sky-2);opacity:var(--sun-band)"/>
<g id="stars" style="opacity:0"></g>
<!-- 云 -->
<g class="layer" id="lay-cloud"><g class="tile" id="tile-cloud"></g></g>
<g id="lay-birds"></g>
<!-- 远山 / 海岸 -->
<polyline id="hillFar" style="fill:var(--hill-far)" stroke="none"/>
<polyline id="hillMid" style="fill:var(--hill-mid)" stroke="none"/>
<path d="M0,432 Q300,418 600,430 T1200,424 L1200,700 L0,700 Z" style="fill:var(--sea)"/>
<g class="layer" id="lay-wave"><g class="tile" id="tile-wave"></g></g>
<path d="M0,432 Q300,418 600,430 T1200,424" fill="none" stroke-width="2.5" style="stroke:var(--foam);opacity:.7"/>
<g class="layer" id="lay-turbine"><g class="tile" id="tile-turbine"></g></g>
<!-- 沙丘 -->
<polyline id="duneFar" style="fill:var(--dune)" stroke="none"/>
<polyline id="duneNear" style="fill:var(--dune-2)" stroke="none"/>
<g class="layer" id="lay-tuft"><g class="tile" id="tile-tuft"></g></g>
<g class="layer" id="lay-pole"><g class="tile" id="tile-pole"></g></g>
<g class="layer" id="lay-bush"><g class="tile" id="tile-bush"></g></g>
<!-- 路面 -->
<rect x="0" y="566" width="1200" height="134" style="fill:var(--road)"/>
<rect x="0" y="566" width="1200" height="5" style="fill:var(--road-2);opacity:.75"/>
<g class="layer" id="lay-road"><g class="tile" id="tile-road"></g></g>
<g class="layer" id="lay-streak"><g class="tile" id="tile-streak"></g></g>
<!-- 车影 -->
<ellipse id="shadow" cx="616" cy="572" rx="248" ry="12" style="fill:var(--shadow)"/>
<!-- ====== 骑行者 ====== -->
<g id="rig">
<g id="rigB">
<g id="bike">
<!-- 后轮 -->
<g id="wheelB">
<circle cx="470" cy="488" r="81" fill="none" stroke-width="9" style="stroke:var(--tyre)"/>
<circle cx="470" cy="488" r="71" fill="none" stroke-width="5" style="stroke:var(--frame-2)"/>
<circle cx="470" cy="488" r="66" style="fill:var(--metal);opacity:0" class="disc"/>
<g class="spokes" style="stroke:var(--metal);stroke-width:1.7;opacity:.9"></g>
<circle cx="470" cy="488" r="8" style="fill:var(--frame-2)"/>
<rect x="466" y="418" width="8" height="12" rx="2" style="fill:var(--tyre)"/>
</g>
<!-- 前轮 -->
<g id="wheelF">
<circle cx="762" cy="488" r="81" fill="none" stroke-width="9" style="stroke:var(--tyre)"/>
<circle cx="762" cy="488" r="71" fill="none" stroke-width="5" style="stroke:var(--frame-2)"/>
<circle cx="762" cy="488" r="66" style="fill:var(--metal);opacity:0" class="disc"/>
<g class="spokes" style="stroke:var(--metal);stroke-width:1.7;opacity:.9"></g>
<circle cx="762" cy="488" r="8" style="fill:var(--frame-2)"/>
<rect x="758" y="418" width="8" height="12" rx="2" style="fill:var(--tyre)"/>
</g>
<!-- 链条 / 牙盘 -->
<path d="M470,472 L616,455 A34,34 0 0 1 616,523 L470,504 A16,16 0 0 1 470,472 Z"
fill="none" stroke-width="4.5" style="stroke:var(--metal)"/>
<path id="chain" d="M470,472 L616,455 A34,34 0 0 1 616,523 L470,504 A16,16 0 0 1 470,472 Z"
fill="none" stroke-width="3" stroke-dasharray="7 5" style="stroke:var(--tyre)"/>
<circle cx="470" cy="488" r="16" style="fill:none;stroke:var(--frame-2);stroke-width:4"/>
<!-- 远侧曲柄+腿 -->
<g id="crankFar"><line id="crankFarL" x1="616" y1="489" x2="616" y2="445" stroke-width="7"
stroke-linecap="round" style="stroke:var(--frame-2)"/>
<rect id="pedFar" x="-13" y="-4" width="26" height="8" rx="2" style="fill:var(--tyre)"/></g>
<g id="legFar" style="stroke:var(--pelican-sh);stroke-linecap:round">
<polyline id="thighFar" fill="none" stroke-width="21"/>
<polyline id="shinFar" fill="none" stroke-width="16"/>
<path id="footFar" d="M-6,-3 L16,-6 L22,4 L-6,6 Z" style="fill:var(--foot);stroke:none"/>
</g>
<!-- 车架 -->
<path d="M470,488 L616,489 L572,382 L470,488 M572,382 L742,398 L762,488 M742,398 L616,489 M742,398 L752,374"
fill="none" stroke-width="13" stroke-linecap="round" style="stroke:var(--frame-2);opacity:.45"/>
<path d="M580,392 L470,488 M584,394 L470,488" fill="none" stroke-width="6" stroke-linecap="round" style="stroke:var(--frame-2);opacity:.45"/>
<path d="M470,488 L616,489 L572,382 L470,488 M572,382 L742,398 L762,488 M742,398 L616,489"
fill="none" stroke-width="9" stroke-linecap="round" style="stroke:var(--frame)"/>
<path d="M580,392 L470,488" fill="none" stroke-width="6" stroke-linecap="round" style="stroke:var(--frame)"/>
<path d="M616,489 L572,382" fill="none" stroke-width="8" stroke-linecap="round" style="stroke:var(--frame)"/>
<circle cx="616" cy="489" r="34" fill="none" stroke-width="4" style="stroke:var(--frame-2)"/>
<circle cx="616" cy="489" r="26" fill="none" stroke-width="2" stroke-dasharray="3 4" style="stroke:var(--metal)"/>
<circle cx="616" cy="489" r="7" style="fill:var(--metal)"/>
<!-- 货架 + 鱼篓 -->
<path d="M402,330 L468,330 L462,374 L410,374 Z" style="fill:var(--dune-2)"/>
<path d="M402,330 L468,330 L462,374 L410,374 Z" fill="none" stroke-width="3" style="stroke:var(--frame-2)"/>
<path d="M404,344 H466 M406,358 H464 M420,330 L419,374 M450,330 L447,374" fill="none" stroke-width="2"
style="stroke:var(--frame-2);opacity:.6"/>
<path id="fishA" d="M392,318 C404,306 424,308 432,320 C424,332 404,334 392,324 Z" style="fill:var(--metal)"/>
<path class="fin" id="finA" d="M392,321 l-14,-11 l1,22 z" style="fill:var(--beak)"/>
<circle cx="424" cy="319" r="2.2" style="fill:var(--bird)"/>
<g id="fishB">
<path d="M430,314 C442,300 464,302 472,314 C464,326 442,328 430,318 Z" style="fill:var(--sea)"/>
<path class="fin" id="finB" d="M472,314 l16,-12 l0,24 z" style="fill:var(--beak)"/>
<circle cx="438" cy="313" r="2.2" style="fill:var(--bird)"/>
</g>
<path d="M470,488 q-52,0 -60,-50 l-8,-46" fill="none" stroke-width="4" style="stroke:var(--frame-2);opacity:.5"/>
<!-- 座管+车座 -->
<path d="M572,382 L566,372" stroke-width="8" stroke-linecap="round" style="stroke:var(--frame-2)"/>
<path d="M528,372 C548,362 578,362 592,368 C584,378 550,382 528,378 Z" style="fill:var(--tyre)"/>
<!-- 车把+铃 -->
<path d="M742,398 C748,384 752,376 758,370" fill="none" stroke-width="8" stroke-linecap="round" style="stroke:var(--frame)"/>
<path d="M754,368 L776,374" stroke-width="10" stroke-linecap="round" style="stroke:var(--tyre)"/>
<g id="bell">
<circle cx="738" cy="378" r="9" style="fill:var(--cap)"/>
<circle cx="738" cy="378" r="9" fill="none" stroke-width="2" style="stroke:var(--frame-2)"/>
<circle cx="738" cy="369" r="2" style="fill:var(--frame-2)"/>
<g id="waves" style="opacity:0;stroke:var(--cap);fill:none;stroke-width:2.5">
<path d="M752,366 q10,-6 14,-16"/><path d="M758,376 q14,-2 24,-10"/>
</g>
</g>
<!-- 前灯(黄昏) -->
<g id="lamp">
<circle cx="770" cy="404" r="8" style="fill:var(--cap)"/>
<path id="cone" d="M776,404 L880,368 L888,438 Z" style="fill:var(--sun-glow);opacity:0"/>
</g>
</g>
<!-- 鹈鹕 -->
<g id="bird-body">
<path d="M478,286 C452,276 428,280 416,296 C434,300 452,308 464,320 C472,308 480,298 486,296 Z" style="fill:var(--pelican-sh)"/>
<g id="crest" style="transform-origin:620px 150px">
<path d="M620,146 C610,132 596,126 586,128 C598,136 606,146 610,156 Z" style="fill:var(--pelican-sh)"/>
</g>
<g id="neck">
<path d="M578,300 C584,250 602,212 630,192 L662,206 C640,232 622,268 618,314 Z" style="fill:var(--pelican)"/>
</g>
<ellipse cx="546" cy="300" rx="80" ry="66" transform="rotate(-12 546 300)" style="fill:var(--pelican)"/>
<path d="M512,344 C546,362 588,354 610,330 C604,352 572,372 536,368 C520,366 512,356 512,344 Z" style="fill:var(--pelican-sh);opacity:.55"/>
<path d="M500,268 C540,246 592,254 610,286 C596,320 548,334 512,318 C494,310 488,278 500,268 Z" style="fill:var(--pelican)"/>
<path d="M506,272 C536,256 574,262 596,284" fill="none" stroke-width="2.5" style="stroke:var(--pelican-sh);opacity:.8"/>
<path d="M512,290 C542,278 576,282 598,298" fill="none" stroke-width="2.5" style="stroke:var(--pelican-sh);opacity:.6"/>
<!-- 头 + 喙 -->
<g id="head">
<g id="beakG">
<g id="pouchSc">
<path id="pouch" d="M666,178 C660,224 688,254 730,252 C770,250 792,220 792,180 L666,178 Z" style="fill:var(--pouch)"/>
<path d="M686,204 C714,232 754,228 780,200" fill="none" stroke-width="3" style="stroke:var(--beak);opacity:.5"/>
<path id="pouchFish" d="M700,236 C712,226 728,228 736,238 C728,248 710,248 700,240 Z" style="fill:var(--metal);opacity:0"/>
</g>
<path d="M672,157 C702,148 746,155 792,170 L793,181 C750,172 702,172 672,175 Z" style="fill:var(--beak)"/>
<path d="M788,168 q10,4 6,14 q-6,-4 -8,-8 z" style="fill:var(--beak-2)"/>
<path d="M672,175 C710,182 750,181 791,178" fill="none" stroke-width="2.5" style="stroke:var(--beak-2)"/>
<path id="beakOpen" d="M674,180 C712,190 752,188 790,180 L790,184 C752,193 712,195 674,186 Z"
style="fill:var(--beak-2);opacity:0"/>
<path d="M688,164 C716,159 744,163 770,170" fill="none" stroke-width="1.8" style="stroke:var(--beak-2);opacity:.75"/>
</g>
<circle cx="648" cy="170" r="31" style="fill:var(--pelican)"/>
<path d="M626,186 C634,200 652,206 666,200" fill="none" stroke-width="2" style="stroke:var(--pelican-sh);opacity:.7"/>
<g id="glasses" style="transform-origin:656px 163px;transform:rotate(-34deg)">
<circle cx="654" cy="162" r="12" style="fill:var(--sea);opacity:.35"/>
<circle cx="654" cy="162" r="12" fill="none" stroke-width="2.5" style="stroke:var(--frame-2)"/>
<path d="M666,160 h8" stroke-width="2.5" style="stroke:var(--frame-2)"/>
<path d="M642,163 C632,164 626,170 626,178" fill="none" stroke-width="2.5" style="stroke:var(--frame-2)"/>
</g>
<circle id="eye" cx="656" cy="162" r="4.4" style="fill:var(--bird)"/>
<circle cx="657.6" cy="160.4" r="1.5" style="fill:#fff;opacity:.9"/>
<rect id="lid" x="644" y="146" width="26" height="0" style="fill:var(--pelican)"/>
<path d="M644,148 C654,140 668,142 674,150" fill="none" stroke-width="2.5" style="stroke:var(--pelican-sh)"/>
<!-- 骑行帽 -->
<path d="M620,152 C620,126 648,114 668,130 C674,136 675,146 672,152 C656,142 636,144 620,152 Z" style="fill:var(--cap)"/>
<path d="M640,124 C648,120 658,122 664,128 L660,150 C654,144 646,142 640,143 Z" style="fill:var(--frame);opacity:.85"/>
<path d="M666,142 C686,136 704,141 710,148 C696,152 678,150 666,148 Z" style="fill:var(--frame-2)"/>
<circle cx="644" cy="118" r="3.5" style="fill:var(--frame-2)"/>
</g>
<!-- 围巾 -->
<g id="scarf">
<path d="M604,228 C622,220 646,224 654,236 C640,246 616,246 604,240 Z" style="fill:var(--frame)"/>
<g class="sc1" style="transform-origin:606px 234px">
<path d="M608,228 C584,224 562,232 552,244 L556,262 C574,252 596,250 612,252 Z" style="fill:var(--frame)"/>
<g class="sc2" style="transform-origin:556px 252px">
<path d="M556,244 C532,244 512,256 504,270 L516,282 C530,268 548,264 562,266 Z" style="fill:var(--accent-2)"/>
<g class="sc3" style="transform-origin:512px 276px">
<path d="M512,268 C492,272 476,286 472,300 L488,306 C494,292 506,284 518,282 Z" style="fill:var(--frame)"/>
</g>
</g>
</g>
<path d="M604,238 C618,246 638,248 652,242" fill="none" stroke-width="4" style="stroke:var(--accent-2)"/>
</g>
<!-- 近侧腿 -->
<g id="legNear" style="stroke:var(--pelican);stroke-linecap:round">
<polyline id="thighNear" fill="none" stroke-width="24"/>
<polyline id="shinNear" fill="none" stroke-width="18"/>
<path id="footNear" d="M-6,-3 L18,-7 L25,4 L-6,7 Z" style="fill:var(--foot);stroke:none"/>
</g>
</g>
<!-- 近侧曲柄 + 翅膀 -->
<g id="crankNear"><line id="crankNearL" x1="616" y1="489" x2="616" y2="445" stroke-width="8"
stroke-linecap="round" style="stroke:var(--frame)"/>
<rect id="pedNear" x="-14" y="-4.5" width="28" height="9" rx="2" style="fill:var(--tyre)"/></g>
<g id="armNear" style="transform-origin:596px 268px">
<path d="M586,262 C624,272 672,306 716,358 C728,370 734,378 736,384 C720,382 694,372 668,354 C632,330 596,300 578,282 Z"
style="fill:var(--pelican)"/>
<path d="M700,352 C714,362 726,372 734,382 M692,342 C706,354 718,366 726,376" fill="none" stroke-width="2.5"
style="stroke:var(--pelican-sh)"/>
<path d="M712,358 C726,364 736,372 744,378 C736,384 724,382 714,374 Z" style="fill:var(--beak)"/>
</g>
<g id="armFar" style="transform-origin:576px 262px">
<path d="M566,258 C602,268 650,300 692,346 C680,352 654,340 626,318 C600,298 574,276 562,268 Z"
style="fill:var(--pelican-sh)"/>
</g>
</g>
</g>
<!-- 前景 -->
<g class="layer" id="lay-fg"><g class="tile" id="tile-fg" style="filter:url(#soft2)"></g></g>
</g>
</svg>
<div class="vignette"></div>
<svg class="grain" aria-hidden="true"><rect width="100%" height="100%" filter="url(#grainf)"/></svg>
<div class="flash" id="flash"></div>
<div class="mast">
<div class="mono-tile">PC</div>
<div class="mast-txt mast-t"><b>PELICAN CYCLE WORKS</b><span class="mono">标本 № 014 · 1893</span></div>
</div>
<div class="rail mono">SEA SIDE ROAD — 双腿驱动 · 双翼握把 · 一囊装鱼</div>
<div class="headline">
<span class="kick mono">SVG · 2D · 实时骨架</span>
<h1>鹈鹕<span class="display" style="font-style:italic;font-weight:400;font-size:.42em;letter-spacing:0">骑车</span><br>去<em class="display" style="font-style:italic;font-weight:700">海边</em></h1>
<span class="sub mono">A pelican, a road bike, and one very jiggle-prone throat pouch.</span>
</div>
<div class="anno a1 reveal"><i></i><b>a · 喉囊:临界阻尼弹簧</b></div>
<div class="anno a2 reveal"><i></i><b>b · 风挡:眼镜滑下</b></div>
<div class="anno a3 reveal"><i></i><b>c · 脚踏:双骨 IK</b></div>
<div class="anno a4 reveal"><i></i><b>d · 后架:三条鱼</b></div>
<div class="figcap mono">图 01 — 侧面机械研究<br>preserveAspectRatio: slice</div>
<div class="fine mono">点击舞台任意处 · 按铃</div>
</div>
<div class="deck">
<div class="panel p-read">
<span class="p-label mono">SPEED</span>
<strong class="p-num mono" id="v-sp">00.0</strong><span class="mono" style="font-size:.7rem">km/h</span>
<div class="gauge"><i id="bar"></i></div>
<div class="grid2">
<div><span class="p-num mono" id="v-rpm">0</span><span class="p-sub mono">RPM 踏频</span></div>
<div><span class="p-num mono" id="v-dist">0.00</span><span class="p-sub mono">KM 里程</span></div>
</div>
<svg class="spark" viewBox="0 0 100 36" preserveAspectRatio="none">
<line x1="0" y1="35" x2="100" y2="35"/><polyline id="sparkline" points=""/>
</svg>
</div>
<div class="panel p-thro">
<span class="p-label mono">THROTTLE 油门</span>
<input id="thr" type="range" min="0" max="100" value="42" aria-label="骑行速度">
<div class="p-sub mono">0 ——— 溜车 · 全力冲刺 ——— 100</div>
<div class="grid2" style="margin-top:14px">
<div><span class="p-num mono" id="v-fish">03</span><span class="p-sub mono">FISH 篓中</span></div>
<div><span class="p-num mono" id="v-time">--:--</span><span class="p-sub mono">TIME 在途</span></div>
</div>
</div>
<div class="panel p-ctrl">
<span class="p-label mono">CONTROLS</span>
<p class="hint mono">↑↓ 调速 · <span class="kbd">space</span> 铃 · <span class="kbd">P</span> 暂停 · <span class="kbd">L</span> 昼夜</p>
<div class="btns">
<button class="btn" id="b-bell" type="button">铃铛 <span class="mono">BELL</span></button>
<button class="btn" id="b-sprint" type="button">冲刺 <span class="mono">SPRINT</span></button>
<button class="btn" id="b-light" type="button" aria-pressed="false">黄昏 <span class="mono">DUSK</span></button>
<button class="btn" id="b-rhythm" type="button" aria-pressed="true"><span class="dot"></span> 节奏 <span class="mono">SYNC</span></button>
<button class="btn" id="b-pause" type="button" aria-pressed="false">暂停 <span class="mono">PAUSE</span></button>
</div>
<div class="p-sub mono" style="margin-top:12px">篓中鱼尾 <span class="fishn" id="fishn">● ● ●</span></div>
<span class="fine2 mono">键盘 · 指针 · 触屏均可操作;支持 prefers-reduced-motion。</span>
</div>
</div>
</div>
<section class="notes">
<div class="sec-head reveal">
<h2>画面之下<br>的四件事</h2>
<p class="mono" style="letter-spacing:.14em">HOW IT MOVES — 全部由原生 SVG + CSS + 一段 rAF 循环完成,没有引入任何库。</p>
</div>
<div class="spec">
<div class="item reveal" style="--i:1">
<span class="num mono">01</span>
<div><h3>腿不是动画,是解出来的</h3>
<p>髋部固定、脚必须钉在踏板上:用 <code>d = |F − H|</code> 与余弦定理解出膝盖,再取 <code>x</code> 更大的那个解作为前向膝。两条腿相差 180° 相位,所以上死点永远有一条腿在发力。</p></div>
</div>
<div class="item reveal" style="--i:2">
<span class="num mono">02</span>
<div><h3>喉囊是一根弹簧</h3>
<p>身体颠簸的加速度喂给一个阻尼弹簧,输出缩放与旋转:<code>a = (−k·x − c·v)·f(速度)</code>。速度越快,阻尼越低,那三条鱼就越不安分。</p></div>
</div>
<div class="item reveal" style="--i:3">
<span class="num mono">03</span>
<div><h3>无缝视差靠取模</h3>
<p>远山、沙丘是三条正弦叠加采样的 <code>polyline</code>;电线杆、灌木、沙草与路面虚线则是元素放进 1200 单位宽的组,用 <code><use></code> 平铺两份并令 <code>x = −(里程·视差比) mod 1200</code>——接缝在数学上不存在。</p></div>
</div>
<div class="item reveal" style="--i:4">
<span class="num mono">04</span>
<div><h3>光与声都是现算的</h3>
<p>黄昏切换只在 <code>:root</code> 上换一组 CSS 自定义属性,SVG 里所有 <code>fill: var(--…)</code> 靠 <code>transition</code> 自动补间;铃铛是两个失谐正弦加指数衰减包络,由 Web Audio 现场合成。</p></div>
</div>
</div>
</section>
<footer class="mono"><span>鹈鹕骑车 · 单文件 · 零依赖</span><span>SVG + CSS + rAF</span></footer>
<script>
(() => {
"use strict";
const NS = "http://www.w3.org/2000/svg";
const root = document.documentElement, $ = id => document.getElementById(id);
const clamp = (v,a,b) => v<a?a:v>b?b:v, lerp=(a,b,t)=>a+(b-a)*t;
const rnd = (a,b) => a + Math.random()*(b-a);
/* ---------- 几何常量 ---------- */
const BB = {x:616, y:489}, RA = {x:470,y:488}, FA = {x:762,y:488};
const CRANK = 44, HIP = {x:578,y:366}, L1 = 92, L2 = 92, WR = 78;
const SPAN = 1200, GY = 566;
/* ---------- 平铺层 ---------- */
const layers = [];
function addLayer(tileId, layerId, span, proto, items){
const tile = $(tileId);
items.forEach(it=>{
const u = document.createElementNS(NS,'use');
u.setAttribute('href', proto);
u.setAttribute('transform', `translate(${it.x},${it.y}) scale(${it.s ?? 1})`);
if(it.o!==undefined) u.style.opacity = it.o;
tile.appendChild(u);
});
const t2 = tile.cloneNode(true); t2.setAttribute('transform','translate('+span+',0)');
t2.removeAttribute('id'); tile.parentNode.appendChild(t2);
layers.push({el:$(layerId), span, f:it_f(items)});
}
function it_f(){ return arguments; } // placeholder
function layer(id, span, f, proto, items){
const tileHost = $(id);
const tile = tileHost.querySelector('.tile') || tileHost;
const frag = document.createDocumentFragment();
items.forEach(it=>{
const u = document.createElementNS(NS,'use');
u.setAttribute('href', proto);
u.setAttribute('transform', `translate(${it.x},${it.y}) scale(${it.s ?? 1})`);
if(it.o!==undefined) u.style.opacity = it.o;
frag.appendChild(u);
});
tile.appendChild(frag);
if(span){
const t2 = tile.cloneNode(true);
t2.setAttribute('transform','translate('+span+',0)'); t2.removeAttribute('id');
tile.parentNode.appendChild(t2);
layers.push({el:tile.parentNode, span, f});
}
}
/* 云朵 */
layer('tile-cloud', 1600, .06, '#cloudProto',
[1,2,3,4,5,6].map(i=>({x:rnd(-40,1560), y:rnd(70,230), s:rnd(.5,1.35), o:rnd(.5,.95)})));
/* 电线杆 */
layer('tile-pole', SPAN, .34, '#poleProto',
[0,1,2,3,4].map(i=>({x:i*240+rnd(-14,14), y:560, s:1})));
/* 涡轮发电机 */
layer('tile-turbine', SPAN, .12, '#turbineProto',
[{x:120,y:436,s:.72},{x:520,y:440,s:.62},{x:900,y:432,s:.8},{x:1120,y:442,s:.55}].map(o=>o));
/* 灌木 */
layer('tile-bush', SPAN, .58, '#bushProto',
Array.from({length:7},(_,i)=>({x:i*172+rnd(-30,30), y:rnd(524,544), s:rnd(.7,1.25)})));
/* 沙丘草丛 */
layer('tile-tuft', SPAN, .78, '#tuftProto',
Array.from({length:12},(_,i)=>({x:i*100+rnd(-24,24), y:rnd(546,558), s:rnd(.7,1.3)})));
/* 前景虚化草丛 */
layer('tile-fg', SPAN, 1.28, '#tuftProto',
Array.from({length:9},(_,i)=>({x:i*134+rnd(-30,30), y:672+rnd(-8,14), s:rnd(1.9,3.1)})));
/* 路面虚线 */
(() => {
const tile = $('tile-road');
for(let i=0;i<8;i++){
const r = document.createElementNS(NS,'rect');
r.setAttribute('x', i*150+20); r.setAttribute('y', 620);
r.setAttribute('width', 78); r.setAttribute('height', 7); r.setAttribute('rx',3);
r.style.fill = 'var(--dash)'; r.style.opacity = '.75';
tile.appendChild(r);
}
const t2 = tile.cloneNode(true); t2.setAttribute('transform','translate(1200,0)'); t2.removeAttribute('id');
tile.parentNode.appendChild(t2);
layers.push({el:$('lay-road'), span:1200, f:1});
})();
/* 速度线 */
(() => {
const tile = $('tile-streak');
for(let i=0;i<10;i++){
const r = document.createElementNS(NS,'rect');
r.setAttribute('x', i*120+rnd(0,40)); r.setAttribute('y', rnd(576,650));
r.setAttribute('width', rnd(50,140)); r.setAttribute('height', 2.4); r.setAttribute('rx',2);
r.style.fill = 'var(--foam)';
tile.appendChild(r);
}
const t2 = tile.cloneNode(true); t2.setAttribute('transform','translate(1200,0)'); t2.removeAttribute('id');
tile.parentNode.appendChild(t2);
const wrap = $('lay-streak'); layers.push({el:wrap, span:1200, f:1.6});
})();
/* 海面波光 */
(() => {
const tile = $('tile-wave');
for(let i=0;i<26;i++){
const r = document.createElementNS(NS,'rect');
r.setAttribute('x', rnd(0,1200)); r.setAttribute('y', rnd(442,528));
r.setAttribute('width', rnd(14,58)); r.setAttribute('height', 2.6); r.setAttribute('rx',2);
r.style.fill = 'var(--foam)'; r.style.opacity = rnd(.18,.6);
tile.appendChild(r);
}
const t2 = tile.cloneNode(true); t2.setAttribute('transform','translate(1200,0)'); t2.removeAttribute('id');
tile.parentNode.appendChild(t2);
layers.push({el:$('lay-wave'), span:1200, f:.16});
})();
/* 星星 */
(() => {
const g = $('stars');
for(let i=0;i<46;i++){
const c = document.createElementNS(NS,'circle');
c.setAttribute('cx', rnd(0,1200)); c.setAttribute('cy', rnd(20,300));
c.setAttribute('r', rnd(.7,1.9)); c.style.fill = '#fff';
c.style.opacity = rnd(.25,.9);
if(i%4===0){ c.style.animation = `tw ${rnd(2,5).toFixed(2)}s ease-in-out ${rnd(0,3).toFixed(2)}s infinite`; }
g.appendChild(c);
}
const st = document.createElement('style');
st.textContent = '@keyframes tw{0%,100%{opacity:.15}50%{opacity:1}}';
document.head.appendChild(st);
})();
/* 远处飞鸟 */
(() => {
const host = $('lay-birds');
window.__birds = [];
for(let i=0;i<3;i++){
const g = document.createElementNS(NS,'g');
const p = document.createElementNS(NS,'path');
p.setAttribute('d','M0,0 q10,-9 20,0 q-10,-5 -20,0 M22,0 q10,-9 20,0 q-10,-5 -20,0');
p.style.fill='var(--bird)'; p.style.opacity='.55';
g.appendChild(p); host.appendChild(g);
window.__birds.push({g, p, x:rnd(-200,1200), y:rnd(120,240), s:rnd(.6,1.1), v:rnd(18,34), ph:rnd(0,6)});
}
})();
/* 车轮辐条 */
function spokes(sel){
const g = document.querySelector(sel);
for(let i=0;i<18;i++){
const l = document.createElementNS(NS,'line');
const a = i/18*Math.PI*2;
const cx = sel.includes('F')?FA.x:RA.x;
l.setAttribute('x1', cx); l.setAttribute('y1', 488);
l.setAttribute('x2', (cx+Math.cos(a)*69).toFixed(1)); l.setAttribute('y2', (488+Math.sin(a)*69).toFixed(1));
g.appendChild(l);
}
}
spokes('#wheelF .spokes'); spokes('#wheelB .spokes');
/* ---------- 地形折线 ---------- */
function ridge(y0, comps, el){
const pts=[];
for(let x=0;x<=2400;x+=12){
let y=y0; for(const [a,f,p] of comps) y += a*Math.sin(x*f+p);
pts.push(x.toFixed(0)+','+y.toFixed(1));
}
pts.push('2400,700','0,700');
el.setAttribute('points', pts.join(' '));
}
ridge(392, [[22,.0062,0],[11,.0151,1.7],[5,.031,4.1]], $('hillFar'));
ridge(414, [[26,.0051,2.2],[13,.0126,.6],[6,.028,3.2]], $('hillMid'));
ridge(506, [[26,.0042,.4],[14,.0113,2.9],[6,.024,1.1]], $('duneFar'));
ridge(540, [[18,.0058,3.1],[9,.0132,.2]], $('duneNear'));
const hills = [
{el:$('hillFar'), f:.10, y0:0}, {el:$('hillMid'), f:.16, y0:0},
{el:$('duneFar'), f:.40, y0:0}, {el:$('duneNear'), f:.52, y0:0}
];
const hillWrap = document.createElementNS(NS,'g');
hills.forEach(h=>{ const w=document.createElementNS(NS,'g'); h.el.parentNode.insertBefore(w,h.el); w.appendChild(h.el); h.w=w; });
/* ---------- 骨架引用 ---------- */
const rig=$('rig'), rigB=$('rigB'), wheelF=$('wheelF'), wheelB=$('wheelB');
const chain=$('chain'), crankNL=$('crankNearL'), crankFL=$('crankFarL');
const pedN=$('pedNear'), pedF=$('pedFar');
const thN=$('thighNear'), shN=$('shinNear'), ftN=$('footNear');
const thF=$('thighFar'), shF=$('shinFar'), ftF=$('footFar');
const head=$('head'), neck=$('neck'), armN=$('armNear'), armF=$('armFar');
const beakG=$('beakG'), pouchSc=$('pouchSc'), beakOpen=$('beakOpen'), pouchFish=$('pouchFish');
const lid=$('lid'), eye=$('eye'), shadow=$('shadow'), scarf=$('scarf');
const discs=[...document.querySelectorAll('.disc')], spokesG=[...document.querySelectorAll('.spokes')];
const finB=$('finB'), cone=$('cone'), stars=$('stars'), waves=$('waves');
/* ---------- 状态 ---------- */
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
if(reduce) root.classList.add('nomo');
const S = {
v:0, target:62, theta:0, dist:0, t:0, paused:reduce, rhythm:true, dusk:0,
bob:0, pouch:{x:0,v:0}, shake:0, blinkT:rnd(2,5), sprintT:0, px:0
};
let ctx=null, gainMaster=null;
function audio(){
if(ctx) return ctx;
try{
ctx = new (window.AudioContext||window.webkitAudioContext)();
gainMaster = ctx.createGain(); gainMaster.gain.value=.5; gainMaster.connect(ctx.destination);
}catch(e){ ctx=false; }
return ctx;
}
function ding(){
const c=audio(); if(!c) return; if(c.state==='suspended') c.resume();
const t0=c.currentTime;
[[1245,.0,.42],[1860,.0,.16],[2490,0,.07]].forEach(([f,,amp],i)=>{
const o=c.createOscillator(), g=c.createGain();
o.type='sine'; o.frequency.setValueAtTime(f*(1+rnd(-.006,.006)),t0);
g.gain.setValueAtTime(0,t0); g.gain.linearRampToValueAtTime(amp/(i+1),t0+.004);
g.gain.exponentialRampToValueAtTime(.0008,t0+1.15);
o.connect(g); g.connect(gainMaster); o.start(t0); o.stop(t0+1.2);
});
waves.style.animation='none'; void waves.offsetWidth;
waves.style.animation='bellw .55s ease-out';
}
function gurgle(){
const c=audio(); if(!c) return;
const t0=c.currentTime, o=c.createOscillator(), g=c.createGain(), lfo=c.createOscillator(), lg=c.createGain();
o.type='triangle'; o.frequency.setValueAtTime(180,t0);
o.frequency.exponentialRampToValueAtTime(70,t0+.22);
lfo.frequency.value=22; lg.gain.value=28; lfo.connect(lg); lg.connect(o.frequency);
g.gain.setValueAtTime(.14,t0); g.gain.exponentialRampToValueAtTime(.001,t0+.28);
o.connect(g); g.connect(gainMaster); o.start(t0); lfo.start(t0); o.stop(t0+.3); lfo.stop(t0+.3);
}
const st=document.createElement('style');
st.textContent='@keyframes bellw{0%{opacity:0;transform:translate(0,0) scale(.5)}30%{opacity:1}100%{opacity:0;transform:translate(10px,-8px) scale(1.4)}}';
document.head.appendChild(st);
/* ---------- IK ---------- */
function ik(h, f, l1, l2){
const dx=f.x-h.x, dy=f.y-h.y, d=clamp(Math.hypot(dx,dy), Math.abs(l1-l2)+.5, l1+l2-.5);
const base=Math.atan2(dy,dx);
const ca=clamp((d*d+l1*l1-l2*l2)/(2*d*l1),-1,1);
const A=Math.acos(ca);
const k1={x:h.x+l1*Math.cos(base+A), y:h.y+l1*Math.sin(base+A)};
const k2={x:h.x+l1*Math.cos(base-A), y:h.y+l1*Math.sin(base-A)};
return (k1.x>=k2.x?k1:k2);
}
/* ---------- 读数控件 ---------- */
const thr=$('thr'); thr.value = 42;
function readTarget(){ S.target = +thr.value/100 * 190; }
readTarget();
thr.addEventListener('input', readTarget);
$('b-pause').addEventListener('click', e=>{
S.paused=!S.paused;
e.currentTarget.setAttribute('aria-pressed', S.paused);
e.currentTarget.innerHTML = (S.paused?'继续':'暂停')+' <span class="mono">'+(S.paused?'PLAY':'PAUSE')+'</span>';
root.style.setProperty('--play', S.paused?'paused':'running');
});
$('b-light').addEventListener('click', e=>{
const dusk = root.dataset.scene!=='dusk';
root.dataset.scene = dusk?'dusk':'day';
e.currentTarget.setAttribute('aria-pressed', dusk);
e.currentTarget.innerHTML = (dusk?'白昼':'黄昏')+' <span class="mono">'+(dusk?'DAY':'DUSK')+'</span>';
$('flash').classList.remove('go'); void $('flash').offsetWidth; $('flash').classList.add('go');
});
$('b-rhythm').addEventListener('click', e=>{
S.rhythm=!S.rhythm; e.currentTarget.setAttribute('aria-pressed',S.rhythm);
});
$('b-sprint').addEventListener('click', ()=>{ S.sprintT=1.15; ding(); });
$('b-bell').addEventListener('click', ding);
$('stage').addEventListener('pointerdown', e=>{
if(e.target.closest('button,input,a')) return;
$('stage').classList.add('scrim'); ding();
});
$('stage').addEventListener('pointerenter',()=>$('stage').classList.add('scrim'));
$('stage').addEventListener('pointerleave',()=>$('stage').classList.remove('scrim'));
addEventListener('keydown', e=>{
if(/input/i.test(e.target.tagName)) return;
if(e.key==='ArrowUp'||e.key==='ArrowRight'){ thr.value=clamp(+thr.value+6,0,100); readTarget(); e.preventDefault(); }
if(e.key==='ArrowDown'||e.key==='ArrowLeft'){ thr.value=clamp(+thr.value-6,0,100); readTarget(); e.preventDefault(); }
if(e.code==='Space'){ $('b-bell').click(); e.preventDefault(); }
if(e.key.toLowerCase()==='p'){ $('b-pause').click(); }
if(e.key.toLowerCase()==='l'){ $('b-light').click(); }
if(e.key.toLowerCase()==='s'){ $('b-sprint').click(); }
});
let pmx=0;
addEventListener('pointermove', e=>{ pmx = (e.clientX/innerWidth-.5)*2; },{passive:true});
/* 滚动视差 */
let scrollY0=0;
addEventListener('scroll',()=>{scrollY0=window.scrollY;},{passive:true});
/* reveal */
const io=new IntersectionObserver(es=>es.forEach(en=>{ if(en.isIntersecting){en.target.classList.add('vis'); io.unobserve(en.target);} }),{threshold:.2});
document.querySelectorAll('.reveal').forEach(el=>io.observe(el));
document.querySelectorAll('.anno').forEach((el,i)=>setTimeout(()=>el.classList.add('in'), 1200+i*260));
/* ---------- 主循环 ---------- */
let last=performance.now(), spark=[], frames=0;
function frame(now){
let dt=(now-last)/1000; last=now; dt=Math.min(dt,.05);
if(!S.paused) step(dt, now/1000);
else ambientOnly(dt, now/1000);
requestAnimationFrame(frame);
}
function ambientOnly(dt,t){
// 暂停时仅让围巾与鱼尾等 CSS 动画继续(它们由 CSS 驱动,pause 会一并冻结)
}
function step(dt, t){
S.t += dt;
/* 速度 */
let tgt = S.target;
if(S.sprintT>0){ S.sprintT-=dt; tgt = 250; }
S.v = lerp(S.v, tgt, 1-Math.exp(-dt*(tgt>S.v?1.6:2.6)));
const sp = clamp(S.v/250,0,1);
root.style.setProperty('--spd', sp.toFixed(3));
root.style.setProperty('--cad', (clamp(1.35-.85*sp,.30,1.5)).toFixed(3)+'s');
$('lay-streak').style.opacity = Math.max(0,(sp-.38)*1.25).toFixed(2);
stars.style.opacity = 0; // 由 dusk 覆盖
/* 里程 / 昼夜补间 */
S.dist += S.v*dt;
S.dusk = lerp(S.dusk, root.dataset.scene==='dusk'?1:0, 1-Math.exp(-dt*3));
stars.style.opacity = (S.dusk*.95).toFixed(2);
cone.style.opacity = (S.dusk*.35).toFixed(2);
discs.forEach(d=>d.style.opacity = (clamp((sp-.35)*1.5,0,.28)).toFixed(3));
spokesG.forEach(g=>g.style.opacity = (1-clamp((sp-.4)*1.1,0,.6)).toFixed(3));
/* 视差 */
layers.forEach(L=>{ const off=-((S.dist*L.f)%L.span); L.el.setAttribute('transform',`translate(${off.toFixed(2)},0)`); });
hills.forEach(h=>{ const off=-((S.dist*h.f)%2400) + pmx*(1-h.f)*10 - scrollY0*.02*(1-h.f); h.w.setAttribute('transform',`translate(${off.toFixed(2)},${(scrollY0*.03).toFixed(1)})`); });
if(turbineRefs.length){ const r=(S.v*.02+ (S.rhythm?0:.6))*dt; turbineRefs.forEach(b=>{b.a+=r*(b.s); b.el.setAttribute('transform',`rotate(${(b.a%360).toFixed(1)})`);}); }
/* 飞鸟 */
window.__birds.forEach(b=>{
b.x -= (b.v + S.v*.06)*dt;
if(b.x < -160) { b.x = 1320; b.y=rnd(110,250); }
const flap = Math.sin(t*7+b.ph)*4;
b.g.setAttribute('transform',`translate(${b.x.toFixed(1)},${b.y.toFixed(1)}) scale(${b.s})`);
b.p.setAttribute('d',`M0,0 q10,${-9+flap} 20,0 q-10,-5 -20,0 M22,0 q10,${-9+flap} 20,0 q-10,-5 -20,0`);
});
/* 曲柄 / 车轮 */
const omega = S.v/118*Math.PI*2; // rad/s
S.theta += omega*dt;
const wheelRot = (S.dist/WR)*180/Math.PI;
wheelF.setAttribute('transform',`rotate(${wheelRot.toFixed(2)} ${FA.x} ${FA.y})`);
wheelB.setAttribute('transform',`rotate(${wheelRot.toFixed(2)} ${RA.x} ${RA.y})`);
chain.setAttribute('stroke-dashoffset', (-(S.dist/34*34)%24).toFixed(1));
/* 身体颠簸 */
const effort = clamp(S.v/160,0,1);
S.bob = Math.sin(S.theta*2)*(.9+2.6*effort) - Math.cos(S.theta)*.5*effort;
S.shake = Math.max(0,(sp-.55))*Math.sin(t*46)*1.1;
const pitch = -sp*2.2 + Math.sin(S.theta)*.25*effort;
rig.setAttribute('transform',`translate(${(pmx*7+Math.sin(S.theta)*.6).toFixed(2)} ${S.bob.toFixed(2)}) rotate(${pitch.toFixed(2)} 620 520)`);
rigB.setAttribute('transform',`translate(0 ${(Math.abs(S.shake)).toFixed(2)})`);
/* 双腿 */
[[S.theta, thN, shN, ftN, crankNL, pedN], [S.theta+Math.PI, thF, shF, ftF, crankFL, pedF]].forEach(([ang,th,sh,ft,cr,ped],i)=>{
const f = {x: BB.x + Math.cos(ang)*CRANK, y: BB.y + Math.sin(ang)*CRANK};
const k = ik(HIP, f, L1, L2);
th.setAttribute('points', `${HIP.x},${HIP.y} ${k.x.toFixed(1)},${k.y.toFixed(1)}`);
sh.setAttribute('points', `${k.x.toFixed(1)},${k.y.toFixed(1)} ${f.x.toFixed(1)},${f.y.toFixed(1)}`);
cr.setAttribute('x2', f.x.toFixed(1)); cr.setAttribute('y2', f.y.toFixed(1));
ped.setAttribute('transform', `translate(${f.x.toFixed(1)} ${f.y.toFixed(1)})`);
const a2 = Math.atan2(f.y-k.y, f.x-k.x)*180/Math.PI;
ft.setAttribute('transform', `translate(${f.x.toFixed(1)} ${f.y.toFixed(1)}) rotate(${clamp(a2*.35,-22,26).toFixed(1)})`);
});
/* 头 / 颈 / 喙 */
const ndx = -S.bob*.55 - sp*3, nr = 2.2*Math.sin(S.theta*2) - sp*7;
neck.setAttribute('transform',`translate(${ndx.toFixed(2)} ${(ndx*.5).toFixed(2)}) rotate(${(nr*.25).toFixed(2)} 590 300)`);
head.setAttribute('transform',`translate(${ndx.toFixed(2)} ${(S.bob*.55+nr*.28).toFixed(2)}) rotate(${nr.toFixed(2)} 648 170)`);
/* 喉囊弹簧 */
const drive = -S.bob*1.15 + sp*.9 + Math.sin(S.theta*2)*.4;
const K = 88 - 34*sp, C = 9.5 - 4.6*sp;
const P=S.pouch;
P.v += ((-K*(P.x - drive*.16)) - C*P.v)*dt; P.x += P.v*dt;
const sx = 1 + P.x*.05, sy = 1 - P.x*.075, rot = P.x*.85;
pouchSc.setAttribute('transform',`translate(${(P.x*.6).toFixed(2)} ${(P.x*.3).toFixed(2)}) rotate(${rot.toFixed(2)} 668 178) scale(${sx.toFixed(3)} ${sy.toFixed(3)})`);
pouchSc.style.transformOrigin='668px 178px';
beakOpen.style.opacity = clamp((sp-.22)*1.6,0,.9).toFixed(2);
pouchFish.style.opacity = (clamp((sp-.5)*2,0,1)*.75).toFixed(2);
pouchFish.setAttribute('transform',`translate(${(-P.x*2).toFixed(1)} ${(P.x*1.2).toFixed(1)}) rotate(${(P.x*4).toFixed(1)} 716 238)`);
if(Math.abs(P.v)>26 && Math.random()<.06) gurgle();
/* 眼镜随颠簸 */
const gl = clamp((sp-.35)*1.4,0,1);
$('glasses').style.transform = `rotate(${(-34+gl*34+P.x*.9).toFixed(1)}deg)`;
/* 眨眼 */
S.blinkT -= dt;
if(S.blinkT<0){ S.blinkT = rnd(2.6,6.4); }
const bl = S.blinkT<.13 ? 1-Math.abs(S.blinkT/.13-.5)*2 : 0;
lid.setAttribute('height', (26*bl).toFixed(1));
lid.setAttribute('y', 146);
/* 翅膀 */
const flap = Math.sin(S.theta*2+.4)*1.4*(1-sp*.6);
armN.setAttribute('transform',`rotate(${(flap-sp*1.6).toFixed(2)} 596 268)`);
armF.setAttribute('transform',`rotate(${(-flap+sp*1.2).toFixed(2)} 576 262)`);
$('crest').setAttribute('transform',`rotate(${(-sp*16+Math.sin(t*3)*2).toFixed(1)} 620 150)`);
/* 围巾尾巴 CSS 时长 & 鱼篓 */
$('fishB').setAttribute('transform',`rotate(${(Math.sin(t*2.4)*4).toFixed(2)} 468 312)`);
finB.setAttribute('transform',`scale(1 ${(1+Math.sin(t*6)*.18).toFixed(3)})`);
finB.style.transformOrigin='472px 314px';
/* 影子 */
const sq = 1 + Math.abs(S.bob)*.006;
shadow.setAttribute('rx',(248*sq).toFixed(1)); shadow.setAttribute('ry',(12-S.bob*.4).toFixed(1));
shadow.setAttribute('cy', 572);
shadow.style.opacity = (.85-sp*.15).toFixed(2);
/* 读表 */
frames++;
if(frames%4===0){
const kmh = S.v*.135;
$('v-sp').textContent = kmh.toFixed(1).padStart(4,'0');
$('bar').style.width = (kmh/34*100).toFixed(1)+'%';
$('v-rpm').textContent = Math.round(S.v/118*Math.PI*2/ (Math.PI*2) *60).toString();
$('v-dist').textContent = (S.dist/1000*.135*3.6/3.6).toFixed(2);
const mins = Math.floor(S.t/60), secs = Math.floor(S.t%60);
$('v-time').textContent = String(mins).padStart(2,'0')+':'+String(secs).padStart(2,'0');
spark.push(clamp(kmh/34,0,1)); if(spark.length>34) spark.shift();
$('sparkline').setAttribute('points', spark.map((v,i)=>`${(i/(33)*100).toFixed(1)},${(34-v*31).toFixed(1)}`).join(' '));
const n = Math.round(clamp(sp*3,0,3));
$('v-fish').textContent = String(3).padStart(2,'0');
$('fishn').innerHTML = [0,1,2].map(i=>`<span class="${i<n?'':'off'}">●</span>`).join(' ');
}
}
/* 涡轮叶片引用(CSS 或手动) */
const turbineRefs = [];
(() => {
const host=$('tile-turbine').parentNode;
setTimeout(()=>{ host.querySelectorAll('.blades').forEach((b,i)=>turbineRefs.push({el:b, s:1+i*.13, a:i*70})); },0);
})();
const css = document.createElement('style');
css.textContent = `.blades{animation:tspin var(--turbine,4s) linear infinite;animation-play-state:var(--play,running)}
@keyframes tspin{to{transform:rotate(360deg)}}
.sc1,.sc2,.sc3{animation:scv calc(var(--cad)*1.1) ease-in-out infinite;animation-play-state:var(--play,running);
transform-box:view-box}
.sc2{animation-delay:calc(var(--cad)*-.22)} .sc3{animation-delay:calc(var(--cad)*-.44)}
@keyframes scv{0%,100%{transform:rotate(-3deg)}50%{transform:rotate(5deg)}}
.fin{animation:fsw calc(var(--cad)*.7) ease-in-out infinite;transform-box:fill-box;transform-origin:0% 50%;
animation-play-state:var(--play,running)}
.wb{animation:fsw calc(var(--cad)*1.3) ease-in-out infinite;transform-box:fill-box;transform-origin:center}
@keyframes fsw{0%,100%{transform:rotate(-6deg)}50%{transform:rotate(6deg)}}
.sunDisc{animation:sunp 7s ease-in-out infinite;animation-play-state:var(--play,running)}
@keyframes sunp{0%,100%{opacity:.95}50%{opacity:1}}`;
document.head.appendChild(css);
root.style.setProperty('--turbine','5s');
requestAnimationFrame(t=>{last=t;frame(t);});
if(reduce){ $('b-pause').setAttribute('aria-pressed','true'); S.paused=false; S.v=90; }
})();
</script>
</body>
</html>
* 帖子来源Linux.do
附近帖子
- ↑我的gpt邀请还没用,有需要的吗
- ↑每次到秋天都有一种悲凉的感觉...
- ↑关于我申请美国大学,获得教育邮箱,白嫖之路不知道怎么说。
- ↑淘宝闪购及美团外卖每周免单活动
- ↑kimi不太耐用啊。。。
- 📍 qwen3.8-flash-next Q4实测震惊我一整天
- ↓用了一下grok bot ,意外的丝滑好用
- ↓赛博医生上线,可以的话想定驻
- ↓注意!关于GPT自己使用重置卡的问题(已破案)!!!
- ↓跟着大模型错峰上班?
- ↓想要搭建一个投研智能体,有推荐的相关的开源项目或者教程学习嘛?