qwen3.8-flash-next Q4实测震惊我一整天

觉浅 2026-08-27 16:48 1

“震惊得没话说”



创建一个HTML,内容是SVG绘制一个鹈鹕骑自行车的2D动画。





这也太离谱了,没有继续试的欲望了

最新回复 (8)
  • 酷睿哥 08-27 16:49
    1

    最低也得FP8的才能用,Q4的就只能当玩具

  • 🔨 08-27 16:52
    2

    qwen这是强到飞起,就问你强不强

  • ClaudeCode 08-27 16:54
    3

    咋感觉还不如3.8 27b小模型,基本没啥bug画出来的东西

  • RS_HIT 08-27 16:56
    4

    qwen不是问强不强,就问你飞没飞 ^-^

  • 觉浅 楼主 08-27 17:15
    5

    Q4质量本来就不差,其他模型特别是qwen3.8 27b体感上跟fp8几乎没差别,再者哪有那么多显存跑FP8

  • 觉浅 楼主 08-27 17:16
    6

    强强强,飞飞飞,这次是真真真飞了,啥飞了你别管

  • Grandy 08-27 17:18
    7

    modelscope上的3.8-next,很强啊

  • Grandy 08-27 17:21
    8

    不知道怎么发动图,发个代码吧。接的阉割版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&nbsp;SIDE&nbsp;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>&lt;use&gt;</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
返回