- 隔壁老陈2026-09-2328146
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <title>鹈鹕骑车 · 黄昏栈桥</title> <style> :root{ --sky-top:#2b2152; --sea-deep:#2b2c60; box-sizing:border-box; padding-top:env(safe-area-inset-top,0px); padding-bottom:env(safe-area-inset-bottom,0px); background:var(--sky-top); } html,body{height:100%;margin:0} html{scroll-padding-top:env(safe-area-inset-top,0px)} body{overflow:hidden;background:linear-gradient(var(--sky-top) 0 50%, var(--sea-deep) 50% 100%)} #scene{display:block;width:100%;height:100%} body::after{content:"";position:fixed;inset:0;pointer-events:none; background:radial-gradient(ellipse at 50% 50%, rgba(0,0,0,0) 55%, rgba(22,12,42,.5) 100%)} .tw{animation:tw 3s ease-in-out infinite} @keyframes tw{0%,100%{opacity:.15}50%{opacity:.9}} </style> </head> <body> <svg id="scene" viewBox="0 0 1200 700" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="黄昏的海边栈桥上,一只鹈鹕骑着自行车"> <defs> <linearGradient id="gSky" x1="0" y1="0" x2="0" y2="1"> <stop offset="0" stop-color="#2b2152"/> <stop offset=".32" stop-color="#5e3b78"/> <stop offset=".6" stop-color="#c9607e"/> <stop offset=".82" stop-color="#f7955f"/> <stop offset="1" stop-color="#ffd48e"/> </linearGradient> <radialGradient id="gGlow"> <stop offset="0" stop-color="#fff2c0" stop-opacity=".95"/> <stop offset=".22" stop-color="#ffd07a" stop-opacity=".6"/> <stop offset=".55" stop-color="#ff9a6a" stop-opacity=".18"/> <stop offset="1" stop-color="#ff8a6a" stop-opacity="0"/> </radialGradient> <linearGradient id="gSun" x1="0" y1="0" x2="0" y2="1"> <stop offset="0" stop-color="#fff6d2"/> <stop offset="1" stop-color="#ffb457"/> </linearGradient> <linearGradient id="gSea" x1="0" y1="0" x2="0" y2="1"> <stop offset="0" stop-color="#f7a47c"/> <stop offset=".15" stop-color="#dc7680"/> <stop offset=".5" stop-color="#8b5588"/> <stop offset="1" stop-color="#34366e"/> </linearGradient> <radialGradient id="gLamp"> <stop offset="0" stop-color="#ffe9a8" stop-opacity=".85"/> <stop offset=".4" stop-color="#ffc977" stop-opacity=".25"/> <stop offset="1" stop-color="#ffb060" stop-opacity="0"/> </radialGradient> <linearGradient id="gFeather" x1="1" y1="0" x2="0" y2="1"> <stop offset="0" stop-color="#fffaf0"/> <stop offset=".55" stop-color="#f4eadd"/> <stop offset="1" stop-color="#d6c3c2"/> </linearGradient> <linearGradient id="gBeak" x1="0" y1="0" x2="1" y2="0"> <stop offset="0" stop-color="#ffd65e"/> <stop offset="1" stop-color="#f59e35"/> </linearGradient> <linearGradient id="gPouch" x1="0" y1="0" x2="0" y2="1"> <stop offset="0" stop-color="#f7954a"/> <stop offset="1" stop-color="#dd6238"/> </linearGradient> <linearGradient id="gDeck" x1="0" y1="0" x2="0" y2="1"> <stop offset="0" stop-color="#e4ad72"/> <stop offset="1" stop-color="#b87a47"/> </linearGradient> </defs> <!-- 天空 --> <rect x="-1400" y="-1600" width="4000" height="1601" fill="#2b2152"/> <rect x="-1400" y="0" width="4000" height="422" fill="url(#gSky)"/> <g id="stars"></g> <circle cx="840" cy="400" r="320" fill="url(#gGlow)"/> <circle cx="840" cy="402" r="78" fill="url(#gSun)"/> <g id="clouds"></g> <g id="hillsFar" opacity=".9"></g> <g id="hillsNear"></g> <!-- 海 --> <rect x="-1400" y="420" width="4000" height="281" fill="url(#gSea)"/> <rect x="-1400" y="700" width="4000" height="1600" fill="#34366e"/> <ellipse cx="840" cy="424" rx="300" ry="42" fill="url(#gGlow)" opacity=".75"/> <rect x="-1400" y="419.4" width="4000" height="1.4" fill="#ffe6b0" opacity=".6"/> <g id="reflect"></g> <g id="boats"></g> <g id="wavesFar"></g> <g id="wavesNear"></g> <g id="gulls"></g> <!-- 栈桥 --> <g id="lamps"></g> <g id="rail"></g> <g id="pilings"></g> <rect x="-1400" y="662" width="4000" height="1640" fill="#2b2c60" opacity=".88"/> <rect x="-1400" y="661.5" width="4000" height="1.5" fill="#ffcdbf" opacity=".35"/> <g id="foam"></g> <rect x="-1400" y="562" width="4000" height="10" fill="url(#gDeck)"/> <rect x="-1400" y="572" width="4000" height="30" fill="#7a4c32"/> <rect x="-1400" y="572" width="4000" height="2.5" fill="#5a3525"/> <rect x="-1400" y="586" width="4000" height="1.2" fill="#5c3827" opacity=".8"/> <rect x="-1400" y="599" width="4000" height="3" fill="#4b2c1f"/> <g id="seams"></g> <ellipse cx="545" cy="565" rx="190" ry="6" fill="#3b1c2a" opacity=".3"/> <!-- 车轮 --> <g> <circle cx="430" cy="490" r="70" fill="none" stroke="#231c2c" stroke-width="9"/> <circle cx="430" cy="490" r="65.5" fill="none" stroke="#efe2cc" stroke-width="1.6" opacity=".75"/> <circle cx="430" cy="490" r="61" fill="none" stroke="#c7cfd6" stroke-width="3"/> <circle cx="430" cy="490" r="60" fill="#e6ebef" opacity=".07"/> <g id="spokesR"></g> <circle cx="430" cy="490" r="7" fill="#a7b0b8" stroke="#5b636c" stroke-width="2"/> </g> <g> <circle cx="650" cy="490" r="70" fill="none" stroke="#231c2c" stroke-width="9"/> <circle cx="650" cy="490" r="65.5" fill="none" stroke="#efe2cc" stroke-width="1.6" opacity=".75"/> <circle cx="650" cy="490" r="61" fill="none" stroke="#c7cfd6" stroke-width="3"/> <circle cx="650" cy="490" r="60" fill="#e6ebef" opacity=".07"/> <g id="spokesF"></g> <circle cx="650" cy="490" r="7" fill="#a7b0b8" stroke="#5b636c" stroke-width="2"/> </g> <path d="M353.2 503.5 A78 78 0 0 1 456.7 416.7" fill="none" stroke="#1f9e97" stroke-width="6" stroke-linecap="round"/> <path d="M590.3 439.9 A78 78 0 0 1 726.8 476.4" fill="none" stroke="#1f9e97" stroke-width="6" stroke-linecap="round"/> <!-- 远侧腿 --> <g stroke-linecap="round"> <line id="fShin" stroke="#bf672a" stroke-width="7"/> <line id="fThighO" stroke="#b9aaa3" stroke-width="16"/> <line id="fThigh" stroke="#d3c6bd" stroke-width="13"/> <rect id="fPedal" width="22" height="6" rx="2" fill="#241f2b"/> <g id="fFoot"> <path d="M-5 -2 C2 -5 12 -4 22 -2 C27 0 26 3 22 4 C26 6 25 9 21 9 C13 9 3 8 -5 6 C-8 4 -8 0 -5 -2 Z" fill="#b95f27"/> </g> <line id="fCrank" stroke="#7b848d" stroke-width="6"/> </g> <!-- 车架 --> <g fill="none" stroke-linecap="round" stroke-linejoin="round"> <path d="M430 490 L530 500 L500 392 Z" stroke="#1f9e97" stroke-width="7"/> <path d="M500 392 L622 392 M530 500 L632 425" stroke="#1f9e97" stroke-width="8"/> <path d="M621 388 L633 428" stroke="#177f7a" stroke-width="12"/> <path d="M632 426 C639 455 643 475 650 490" stroke="#1f9e97" stroke-width="6"/> <path d="M506 389 L618 389 M536 492 L626 426" stroke="#86ddd5" stroke-width="2" opacity=".7"/> <path d="M500 392 L497 377" stroke="#aeb7bf" stroke-width="5"/> <path d="M621 390 L617 366" stroke="#aeb7bf" stroke-width="5"/> <path d="M617 366 C611 355 600 353 588 359" stroke="#c9d0d6" stroke-width="5"/> <path d="M592 357 L583 362" stroke="#6a3a2a" stroke-width="8"/> <circle cx="430" cy="490" r="8" stroke="#8d969e" stroke-width="3"/> <path id="chain" d="M530 483 L430 482 A8 8 0 0 0 430 498 L530 517 A17 17 0 0 0 530 483" stroke="#4c4452" stroke-width="2.5" stroke-dasharray="3 2.5"/> </g> <g id="ring"> <circle cx="530" cy="500" r="18" fill="none" stroke="#b9c1c8" stroke-width="4" stroke-dasharray="2.2 1.6"/> <circle cx="530" cy="500" r="15" fill="none" stroke="#9aa3ab" stroke-width="2"/> <path d="M530 500 L530 486 M530 500 L543.3 495.7 M530 500 L538.2 511.3 M530 500 L521.8 511.3 M530 500 L516.7 495.7" stroke="#9aa3ab" stroke-width="3" stroke-linecap="round"/> <circle cx="530" cy="500" r="5" fill="#6f7880"/> </g> <path d="M476 373 C478 365 494 364 506 366 L522 369 C528 372 524 378 516 379 L486 380 C477 380 474 377 476 373 Z" fill="#6b3a26"/> <!-- 车篮与鱼 --> <g> <path d="M628 398 L640 398" stroke="#aeb7bf" stroke-width="3" stroke-linecap="round"/> <g id="fish1" transform="translate(652 360) rotate(-12)"> <path d="M0 -16 C7 -10 8 8 0 26 C-8 8 -7 -10 0 -16 Z" fill="#9cc3d6"/> <path d="M0 -14 L-9 -29 L0 -23 L9 -29 Z" fill="#86afc6"/> <path d="M0 -15 C3 -6 3 8 0 22" fill="none" stroke="#6f98b3" stroke-width="1.6"/> </g> <g id="fish2" transform="translate(678 359) rotate(15)"> <path d="M0 -16 C7 -10 8 8 0 26 C-8 8 -7 -10 0 -16 Z" fill="#b1d0de"/> <path d="M0 -14 L-9 -29 L0 -23 L9 -29 Z" fill="#93b8cc"/> <path d="M0 -15 C3 -6 3 8 0 22" fill="none" stroke="#7aa2bb" stroke-width="1.6"/> </g> <path d="M636 362 L694 362 L688 408 L642 408 Z" fill="#c98c4d" stroke="#8b5a2e" stroke-width="2" stroke-linejoin="round"/> <path d="M637 373 L693 373 M638.5 385 L691.5 385 M640 397 L690 397" stroke="#9a6534" stroke-width="1.6"/> <path d="M650 362 L652 408 M665 362 L665 408 M680 362 L678 408" stroke="#a86f3b" stroke-width="1.4"/> <rect x="633" y="357" width="64" height="7" rx="3.5" fill="#a86d38"/> </g> <!-- 鹈鹕:身体(后层) --> <g id="pBack"> <g id="wingF"> <path d="M505 276 C548 260 584 272 598 300 C606 318 606 338 604 356 C596 346 584 336 570 330 Z" fill="#d2c3bb"/> <path d="M586 304 C598 320 604 338 604 356 C596 348 588 340 578 334 C584 326 588 314 586 304 Z" fill="#1f1b26"/> </g> <path d="M428 300 L384 286 L394 301 L380 314 L396 316 L386 328 L432 326 Z" fill="#e3d6ca"/> <path d="M420 300 C430 255 520 245 565 265 C600 280 595 340 560 360 C520 382 460 372 435 345 C422 332 415 315 420 300 Z" fill="url(#gFeather)" stroke="#cdbab4" stroke-width="1.5"/> <path d="M566 270 C594 286 592 334 562 356 C578 332 582 298 562 274 Z" fill="#ffd6a0" opacity=".7"/> </g> <!-- 近侧腿 --> <g stroke-linecap="round"> <line id="nCrank" stroke="#b3bcc4" stroke-width="6"/> <circle cx="530" cy="500" r="4.5" fill="#8a939b"/> <rect id="nPedal" width="22" height="6" rx="2" fill="#3a3340"/> <line id="nShin" stroke="#f08d38" stroke-width="7.5"/> <g id="nFoot"> <path d="M-5 -2 C2 -5 12 -4 22 -2 C27 0 26 3 22 4 C26 6 25 9 21 9 C13 9 3 8 -5 6 C-8 4 -8 0 -5 -2 Z" fill="#f08632"/> <path d="M1 2 L20 1 M1 5 L19 7" fill="none" stroke="#c9621f" stroke-width="1"/> </g> <line id="nThighO" stroke="#cdbdb3" stroke-width="17"/> <line id="nThigh" stroke="#f3eadf" stroke-width="14"/> </g> <!-- 鹈鹕:翅膀、围巾、头(前层) --> <g id="pFront"> <g id="wingN"> <path d="M462 292 C500 262 552 266 578 296 C590 314 595 340 596 364 C584 354 568 342 548 336 C515 328 480 318 462 292 Z" fill="#f5ede3" stroke="#d6c7bd" stroke-width="1.5"/> <path d="M568 300 C586 318 594 342 596 364 C586 356 574 346 562 338 C570 326 572 312 568 300 Z" fill="#26212e"/> <path d="M486 298 C514 296 540 304 560 318 M496 310 C522 311 544 320 556 330" fill="none" stroke="#dccfc4" stroke-width="2" stroke-linecap="round"/> </g> <path id="scarfB" fill="#b02a43"/> <path id="scarfA" fill="#d83a4f"/> <g id="headG"> <path d="M548 292 C538 256 576 242 571 212 C568 196 574 184 588 180" fill="none" stroke="#f6eee4" stroke-width="30" stroke-linecap="round"/> <path d="M559 286 C553 256 587 244 582 214" fill="none" stroke="#ffd8a8" stroke-width="4.5" stroke-linecap="round" opacity=".55"/> <path d="M534 252 Q551 262 569 250 L570 262 Q551 274 533 264 Z" fill="#d83a4f"/> <circle cx="534" cy="259" r="6" fill="#b82e46"/> <path d="M574 164 C562 152 552 160 546 150 C552 166 562 172 572 172 Z" fill="#efe3d4"/> <ellipse cx="593" cy="176" rx="25" ry="21" fill="#f7efe5"/> <ellipse cx="588" cy="163" rx="15" ry="7" fill="#f7dc98" opacity=".55"/> <g id="pouch"> <path d="M611 192 C652 201 700 213 745 222 C724 246 684 264 650 256 C624 250 600 232 590 205 C596 198 604 194 611 192 Z" fill="url(#gPouch)"/> <path d="M628 214 C650 226 682 234 712 232" fill="none" stroke="#ffbf85" stroke-width="3" stroke-linecap="round" opacity=".6"/> </g> <path d="M606 163 C650 170 702 187 744 204 C755 209 755 221 745 223 C700 213 652 201 611 192 Z" fill="url(#gBeak)"/> <path d="M611 192 C652 201 700 213 745 223" fill="none" stroke="#d2702e" stroke-width="2"/> <path d="M613 172 C660 180 704 194 738 207" fill="none" stroke="#e38c30" stroke-width="1.4" opacity=".8"/> <path d="M742 204 C755 208 757 222 745 224 C749 216 748 209 742 204 Z" fill="#d6503b"/> <circle cx="600" cy="170" r="8" fill="#f2c39c"/> <g id="eye"> <circle cx="600" cy="170" r="4.3" fill="#2a1b1f"/> <circle cx="601.6" cy="168.4" r="1.4" fill="#fff"/> </g> </g> </g> </svg> <script> (() => { const NS = 'http://www.w3.org/2000/svg'; const $ = id => document.getElementById(id); const el = (tag, attrs, parent) => { const e = document.createElementNS(NS, tag); for (const k in attrs) e.setAttribute(k, attrs[k]); if (parent) parent.appendChild(e); return e; }; const TAU = Math.PI * 2, DEG = 180 / Math.PI; let seed = 20260922; const rnd = () => (seed = (seed * 16807) % 2147483647) / 2147483647; const X0 = -1400, X1 = 2600; // 运动参数:踏频、齿比 → 车轮角速度 → 地面速度 const CRANK_W = TAU / 1.25; const WHEEL_W = CRANK_W * 1.45; const V = WHEEL_W * 70; // 星星 const stars = $('stars'); for (let i = 0; i < 150; i++) { const x = X0 + rnd() * (X1 - X0); const y = i < 90 ? 6 + Math.pow(rnd(), 1.7) * 230 : -1500 + rnd() * 1500; const c = el('circle', {cx: x.toFixed(1), cy: y.toFixed(1), r: (0.5 + rnd() * 1.2).toFixed(2), fill: '#fff4e2', class: 'tw'}, stars); c.style.animationDuration = (2 + rnd() * 3).toFixed(2) + 's'; c.style.animationDelay = (-rnd() * 5).toFixed(2) + 's'; } // 可平铺滚动图层 const layers = []; function layer(id, speed, tile, build) { const g = el('g', {}, $(id)); const k0 = Math.floor(X0 / tile) - 1, k1 = Math.ceil(X1 / tile) + 1; for (let k = k0; k <= k1; k++) build(el('g', {transform: `translate(${k * tile} 0)`}, g)); layers.push({g, speed, tile}); } // 云 const cloudDefs = []; for (let i = 0; i < 6; i++) cloudDefs.push({x: i * 265 + rnd() * 60, y: 70 + rnd() * 240, w: 130 + rnd() * 170}); function cloud(p, d) { const high = d.y < 190; const body = high ? '#b0679a' : '#f2968e'; const lit = high ? '#f0a4ae' : '#ffd29f'; const h = 13 + d.w * 0.03; const g = el('g', {opacity: high ? 0.75 : 0.85}, p); el('rect', {x: d.x, y: d.y, width: d.w, height: h, rx: h / 2, fill: body}, g); el('ellipse', {cx: d.x + d.w * 0.3, cy: d.y + 2, rx: d.w * 0.16, ry: h * 0.95, fill: body}, g); el('ellipse', {cx: d.x + d.w * 0.55, cy: d.y - 1, rx: d.w * 0.19, ry: h * 1.3, fill: body}, g); el('ellipse', {cx: d.x + d.w * 0.77, cy: d.y + 3, rx: d.w * 0.11, ry: h * 0.8, fill: body}, g); el('rect', {x: d.x + h * 0.5, y: d.y + h - 3.5, width: d.w - h, height: 3, rx: 1.5, fill: lit}, g); } layer('clouds', 14, 1600, s => cloudDefs.forEach(d => cloud(s, d))); // 远山与岬角 function hillPath(T, f) { const N = Math.round(T / 8); let d = 'M-2 422'; for (let i = 0; i <= N; i++) { const x = -2 + i * (T + 4) / N; d += ` L${x.toFixed(1)} ${(420 - Math.max(0, f(x))).toFixed(1)}`; } return d + ` L${T + 2} 422 Z`; } const farF = x => 26 * Math.sin(TAU * 2 * x / 2400 + 1.3) + 10 * Math.sin(TAU * 5 * x / 2400 + 0.5) + 3; const nearF = x => 42 * Math.sin(TAU * 3 * x / 3000 + 0.4) + 14 * Math.sin(TAU * 8 * x / 3000 + 2) - 6; layer('hillsFar', 5, 2400, s => el('path', {d: hillPath(2400, farF), fill: '#c27b9c'}, s)); let xm = 0, hm = -1; for (let x = 0; x < 3000; x += 3) { const h = nearF(x); if (h > hm) { hm = h; xm = x; } } const beams = []; layer('hillsNear', 11, 3000, s => { el('path', {d: hillPath(3000, nearF), fill: '#6d4677'}, s); const x = xm, b = 420 - hm + 5, ly = b - 45; const beam = el('path', {d: `M${x} ${ly} L${x - 240} ${ly - 20} L${x - 240} ${ly + 20} Z`, fill: '#fff2c4', opacity: 0.25}, s); el('path', {d: `M${x + 11} ${b} L${x + 11} ${b - 9} L${x + 18} ${b - 15} L${x + 25} ${b - 9} L${x + 25} ${b} Z`, fill: '#553660'}, s); el('path', {d: `M${x - 8} ${b} L${x + 8} ${b} L${x + 5} ${b - 40} L${x - 5} ${b - 40} Z`, fill: '#efe2e8'}, s); el('path', {d: `M${x - 7.3} ${b - 9} L${x + 7.3} ${b - 9} L${x + 6.5} ${b - 17} L${x - 6.5} ${b - 17} Z M${x - 5.9} ${b - 25} L${x + 5.9} ${b - 25} L${x + 5.3} ${b - 32} L${x - 5.3} ${b - 32} Z`, fill: '#c9465d'}, s); el('rect', {x: x - 8, y: b - 42, width: 16, height: 3, fill: '#3b2847'}, s); el('rect', {x: x - 5, y: b - 50, width: 10, height: 9, fill: '#ffe9a6'}, s); el('path', {d: `M${x - 7.5} ${b - 50} L${x} ${b - 58} L${x + 7.5} ${b - 50} Z`, fill: '#3b2847'}, s); const flash = el('circle', {cx: x, cy: ly, r: 18, fill: 'url(#gLamp)', opacity: 0}, s); beams.push({beam, flash, x, y: ly}); }); // 海面倒影 const refl = $('reflect'), refLines = []; for (let i = 0; i < 16; i++) { const y = 425 + i * 6 + i * i * 0.18; const w = (36 + i * 7) * (0.55 + rnd() * 0.6); const x = 840 + (rnd() - 0.5) * (20 + i * 3); const e = el('line', {x1: (x - w / 2).toFixed(1), y1: y.toFixed(1), x2: (x + w / 2).toFixed(1), y2: y.toFixed(1), stroke: '#ffe3a6', 'stroke-width': (2 + i * 0.12).toFixed(2), 'stroke-linecap': 'round'}, refl); refLines.push({e, ph: rnd() * TAU, sp: 2 + rnd() * 3}); } // 帆船 function boat(p, x, y, k) { const g = el('g', {transform: `translate(${x} ${y}) scale(${k})`, fill: '#4b3161'}, p); el('path', {d: 'M-22 -5 L22 -5 L16 2 L-16 2 Z'}, g); el('path', {d: 'M-1 -7 L-1 -48 L-21 -9 Z'}, g); el('path', {d: 'M2 -9 L2 -42 L18 -9 Z'}, g); } layer('boats', 7, 1900, s => { boat(s, 320, 420, 0.9); boat(s, 1260, 421, 0.6); }); // 波纹 function waveLayer(id, speed, tile, n, y0, y1, col) { const defs = []; for (let i = 0; i < n; i++) { const y = y0 + rnd() * (y1 - y0); defs.push({x: rnd() * (tile - 40), y, w: 5 + (y - 420) * 0.12}); } layer(id, speed, tile, s => defs.forEach(d => el('path', { d: `M${d.x.toFixed(1)} ${d.y.toFixed(1)} q${(d.w / 2).toFixed(1)} ${(-d.w * 0.35).toFixed(1)} ${d.w.toFixed(1)} 0 q${(d.w / 2).toFixed(1)} ${(-d.w * 0.35).toFixed(1)} ${d.w.toFixed(1)} 0`, fill: 'none', stroke: col, 'stroke-width': 1.3, 'stroke-linecap': 'round', opacity: (0.18 + (d.y - 420) / 500).toFixed(2) }, s))); } waveLayer('wavesFar', 16, 480, 12, 428, 478, '#ffe0cc'); waveLayer('wavesNear', 55, 420, 12, 478, 552, '#ffd3c6'); // 海鸥 const gulls = []; [[160, 150, 1, 34], [520, 105, 0.72, 24], [980, 205, 0.9, 42], [760, 62, 0.58, 18], [1320, 170, 0.8, 30]].forEach(([x, y, k, sp], i) => { const g = el('g', {}, $('gulls')); const p = el('path', {fill: 'none', stroke: '#3a2650', 'stroke-width': 2.6, 'stroke-linecap': 'round', 'stroke-linejoin': 'round'}, g); gulls.push({g, p, x, y, k, sp, ph: i * 1.9, fl: 6.5 + i * 0.7}); }); // 栈桥:路灯、绳栏、桩、浪花、木板缝(与地面同速) layer('lamps', V, 760, s => { const x = 380; el('circle', {cx: x, cy: 330, r: 62, fill: 'url(#gLamp)'}, s); el('rect', {x: x - 3, y: 342, width: 6, height: 210, fill: '#30243c'}, s); el('rect', {x: x - 9, y: 538, width: 18, height: 24, rx: 3, fill: '#30243c'}, s); el('rect', {x: x - 7, y: 360, width: 14, height: 4, rx: 2, fill: '#30243c'}, s); el('path', {d: `M${x - 9} 318 L${x + 9} 318 L${x + 6} 342 L${x - 6} 342 Z`, fill: '#ffe7a2'}, s); el('path', {d: `M${x} 318 L${x} 342`, stroke: '#30243c', 'stroke-width': 1.6}, s); el('path', {d: `M${x - 13} 319 L${x} 304 L${x + 13} 319 Z`, fill: '#30243c'}, s); el('circle', {cx: x, cy: 301, r: 3, fill: '#30243c'}, s); el('rect', {x: x - 8, y: 341, width: 16, height: 4, rx: 2, fill: '#30243c'}, s); }); layer('rail', V, 175, s => { el('path', {d: 'M0 525 Q87.5 546 175 525', fill: 'none', stroke: '#e3c392', 'stroke-width': 2.6}, s); el('rect', {x: -5, y: 516, width: 10, height: 48, rx: 2, fill: '#6a422d'}, s); el('rect', {x: -6.5, y: 512, width: 13, height: 6, rx: 3, fill: '#8d5c3c'}, s); }); layer('pilings', V, 175, s => { el('path', {d: 'M12 610 L163 656 M12 656 L163 610', stroke: '#48291d', 'stroke-width': 6}, s); el('rect', {x: -12, y: 598, width: 24, height: 110, fill: '#5a3727'}, s); el('rect', {x: -12, y: 598, width: 7, height: 110, fill: '#6f4633'}, s); }); layer('foam', V, 175, s => { el('ellipse', {cx: 0, cy: 663, rx: 20, ry: 3, fill: '#ffd6cb', opacity: 0.5}, s); el('path', {d: 'M48 676 q10 -3 20 0 M112 688 q12 -3 24 0', fill: 'none', stroke: '#ffd6cb', 'stroke-width': 1.3, opacity: 0.3, 'stroke-linecap': 'round'}, s); }); layer('seams', V, 44, s => { el('rect', {x: -0.8, y: 574, width: 1.6, height: 25, fill: '#5a3625'}, s); el('rect', {x: 21.4, y: 563, width: 1.2, height: 8, fill: '#a96f40'}, s); el('circle', {cx: 5, cy: 580, r: 1.1, fill: '#4a2b1e'}, s); el('circle', {cx: 5, cy: 593, r: 1.1, fill: '#4a2b1e'}, s); }); // 辐条 function spokes(id, cx, cy) { const g = $(id); for (let i = 0; i < 18; i++) { const a = i / 18 * TAU; el('line', {x1: cx, y1: cy, x2: (cx + 60 * Math.cos(a)).toFixed(2), y2: (cy + 60 * Math.sin(a)).toFixed(2), stroke: '#dde3e8', 'stroke-width': 1.1}, g); } el('rect', {x: cx + 34, y: cy - 2.5, width: 10, height: 5, rx: 1.5, fill: '#ff8a3d'}, g); return g; } const spR = spokes('spokesR', 430, 490), spF = spokes('spokesF', 650, 490); // 腿:两段式反向运动学,脚始终踩在踏板上 const BB = {x: 530, y: 500}, CR = 28, LA = 84, LB = 94; const mk = (p, hx, hy, off) => ({ crank: $(p + 'Crank'), pedal: $(p + 'Pedal'), shin: $(p + 'Shin'), foot: $(p + 'Foot'), thighs: [$(p + 'ThighO'), $(p + 'Thigh')], hx, hy, off }); const legN = mk('n', 522, 348, 0), legF = mk('f', 512, 348, Math.PI); const setLine = (e, x1, y1, x2, y2) => { e.setAttribute('x1', x1.toFixed(2)); e.setAttribute('y1', y1.toFixed(2)); e.setAttribute('x2', x2.toFixed(2)); e.setAttribute('y2', y2.toFixed(2)); }; function leg(L, th, bob) { const a = th + L.off; const px = BB.x + CR * Math.cos(a), py = BB.y + CR * Math.sin(a); setLine(L.crank, BB.x, BB.y, px, py); L.pedal.setAttribute('x', (px - 11).toFixed(2)); L.pedal.setAttribute('y', (py - 3).toFixed(2)); const tx = px - 5, ty = py - 9, hx = L.hx, hy = L.hy + bob; let d = Math.hypot(tx - hx, ty - hy); d = Math.min(Math.max(d, Math.abs(LA - LB) + 1), (LA + LB) * 0.999); const phi = Math.atan2(ty - hy, tx - hx); const al = Math.acos((LA * LA + d * d - LB * LB) / (2 * LA * d)); const kx = hx + LA * Math.cos(phi - al), ky = hy + LA * Math.sin(phi - al); for (const e of L.thighs) setLine(e, hx, hy, kx, ky); setLine(L.shin, kx, ky, tx, ty); L.foot.setAttribute('transform', `translate(${tx.toFixed(2)} ${ty.toFixed(2)}) rotate(${(7 * Math.sin(a)).toFixed(2)})`); } // 围巾飘带 function ribbon(x0, y0, len, w0, droop, amp, freq, ph, t) { const N = 14, top = [], bot = []; for (let i = 0; i <= N; i++) { const s = i / N; const x = x0 - len * s + 4 * s * Math.sin(t * 9 + ph + s * 3); const y = y0 + droop * s * s + amp * s * Math.sin(s * freq - t * 13 + ph); const w = w0 * (1 - 0.55 * s); top.push(`${x.toFixed(1)} ${(y - w / 2).toFixed(1)}`); bot.push(`${x.toFixed(1)} ${(y + w / 2).toFixed(1)}`); } return 'M' + top.join(' L') + ' L' + bot.reverse().join(' L') + ' Z'; } const pBack = $('pBack'), pFront = $('pFront'), wingN = $('wingN'), wingF = $('wingF'); const headG = $('headG'), pouch = $('pouch'), eye = $('eye'); const scarfA = $('scarfA'), scarfB = $('scarfB'); const fish1 = $('fish1'), fish2 = $('fish2'); const ring = $('ring'), chain = $('chain'); let nextBlink = 2; function frame(now) { const t = now / 1000; const th = t * CRANK_W; // 身体随踩踏轻微起伏,翅膀反向微转保持握把 const bob = 1.6 * Math.sin(2 * th + 0.8); const pt = `translate(0 ${bob.toFixed(2)})`; pBack.setAttribute('transform', pt); pFront.setAttribute('transform', pt); const wr = (-bob / 96 * DEG).toFixed(2); wingN.setAttribute('transform', `rotate(${wr} 500 285)`); wingF.setAttribute('transform', `rotate(${wr} 505 280)`); leg(legF, th, bob); leg(legN, th, bob); ring.setAttribute('transform', `rotate(${(th * DEG % 360).toFixed(2)} 530 500)`); chain.setAttribute('stroke-dashoffset', ((th * 18) % 5.5).toFixed(2)); const wa = (t * WHEEL_W * DEG % 360).toFixed(2); spR.setAttribute('transform', `rotate(${wa} 430 490)`); spF.setAttribute('transform', `rotate(${wa} 650 490)`); headG.setAttribute('transform', `rotate(${(1.6 * Math.sin(2 * th + 2)).toFixed(2)} 550 288)`); const ps = 1 + 0.07 * Math.sin(2 * th + 2.6); pouch.setAttribute('transform', `translate(0 ${(195 * (1 - ps)).toFixed(2)}) scale(1 ${ps.toFixed(3)})`); let es = 1; const bt = t - nextBlink; if (bt > 0) { if (bt < 0.18) es = 1 - 0.92 * Math.sin(Math.PI * bt / 0.18); else nextBlink = t + 2.2 + Math.random() * 3.5; } eye.setAttribute('transform', `translate(0 ${(170 * (1 - es)).toFixed(2)}) scale(1 ${es.toFixed(3)})`); scarfB.setAttribute('d', ribbon(536, 262, 62, 9, 18, 6, 6, 1.8, t)); scarfA.setAttribute('d', ribbon(536, 257, 80, 11, 10, 7, 7, 0, t)); fish1.setAttribute('transform', `translate(652 360) rotate(${(-12 + 8 * Math.sin(t * 7)).toFixed(2)})`); fish2.setAttribute('transform', `translate(678 359) rotate(${(15 + 8 * Math.sin(t * 7 + 2.1)).toFixed(2)})`); for (const L of layers) { L.g.setAttribute('transform', `translate(${(-((t * L.speed) % L.tile)).toFixed(2)} 0)`); } const la = t * 1.3, c = Math.cos(la); for (const B of beams) { B.beam.setAttribute('transform', `translate(${B.x} ${B.y}) scale(${c.toFixed(3)} 1) translate(${-B.x} ${-B.y})`); B.beam.setAttribute('opacity', (0.1 + 0.22 * Math.abs(c)).toFixed(3)); B.flash.setAttribute('opacity', (Math.sin(la) > 0 ? Math.pow(1 - Math.abs(c), 3) : 0).toFixed(3)); } for (const R of refLines) R.e.setAttribute('opacity', (0.35 + 0.35 * Math.sin(t * R.sp + R.ph)).toFixed(3)); for (const G of gulls) { const span = 2000; const x = ((G.x - t * G.sp + 400) % span + span) % span - 400; const y = G.y + 7 * Math.sin(t * 0.7 + G.ph); const f = Math.sin(t * G.fl + G.ph); const ty = -2 + 7 * f, cy = -7 + 3 * f; G.p.setAttribute('d', `M-17 ${ty.toFixed(2)} Q-8 ${cy.toFixed(2)} 0 2 Q8 ${cy.toFixed(2)} 17 ${ty.toFixed(2)}`); G.g.setAttribute('transform', `translate(${x.toFixed(1)} ${y.toFixed(1)}) scale(${G.k})`); } requestAnimationFrame(frame); } frame(performance.now()); })(); </script> </body> </html> ```分别是网页版和code模式,效果差异挺大,但共同点是细节满满,截图看不出来的细节。 --- 车铃铛能点击这件事,真的想不到它会做,我的提示词很简单。 ---``` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>鹈鹕骑车记</title> <style> *{box-sizing:border-box;margin:0;padding:0} html,body{height:100%;overflow:hidden;background:#a7dcf5;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei",system-ui,sans-serif;color:#243044;-webkit-user-select:none;user-select:none} #scene{position:fixed;inset:0;width:100%;height:100%;display:block;cursor:pointer} .hud{position:fixed;left:28px;top:22px;pointer-events:none} .hud h1{font-size:clamp(28px,4.2vw,46px);font-weight:900;letter-spacing:3px;color:#fff;text-shadow:0 3px 0 #2b6f9e,0 8px 20px rgba(0,40,80,.25)} .hud p{margin-top:6px;font-size:14px;color:#1d4b6e;opacity:.85} .odo{position:fixed;right:28px;top:26px;background:rgba(255,255,255,.78);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);padding:8px 16px;border-radius:14px;font-weight:700;font-size:14px;box-shadow:0 4px 14px rgba(0,50,90,.15)} .odo b{font-variant-numeric:tabular-nums;font-size:22px;color:#e8483f;margin:0 2px} .ctrl{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);display:flex;align-items:center;gap:12px;background:rgba(255,255,255,.82);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);padding:10px 18px;border-radius:999px;box-shadow:0 6px 20px rgba(0,40,70,.18);white-space:nowrap} .ctrl input[type=range]{width:min(38vw,220px);accent-color:#e8483f;cursor:pointer} .ctrl .lbl{font-size:13px;font-weight:700;color:#40546b} .ctrl .val{font-size:13px;font-weight:800;color:#e8483f;min-width:40px;font-variant-numeric:tabular-nums} .ctrl button{border:0;background:#ffcf3f;color:#5a3b00;font-weight:800;padding:8px 16px;border-radius:999px;cursor:pointer;box-shadow:0 3px 0 #d69b00;font-size:14px;font-family:inherit} .ctrl button:active{transform:translateY(2px);box-shadow:0 1px 0 #d69b00} .spark{animation:twinkle 2.6s ease-in-out infinite;opacity:0} @keyframes twinkle{0%,100%{opacity:0}50%{opacity:.95}} .beam{animation:beam 4s linear infinite;opacity:0} @keyframes beam{0%,100%{opacity:0}12%,30%{opacity:.5}46%{opacity:0}} .bob{animation:bob 3.4s ease-in-out infinite;transform-box:fill-box;transform-origin:50% 100%} @keyframes bob{50%{transform:translateY(3px) rotate(2deg)}} .ol{fill:#263041;stroke:#263041;stroke-width:7;stroke-linejoin:round} .fl{fill:url(#gBody);stroke:none} .ink{stroke:#263041;stroke-width:3;stroke-linejoin:round;stroke-linecap:round} </style> </head> <body> <div class="hud"> <h1>鹈鹕骑车记</h1> <p>一只鹈鹕的海边兜风 · 点击画面按铃铛</p> </div> <div class="odo">已骑行 <b id="odo">0</b> 米</div> <div class="ctrl"> <span class="lbl">🐢</span> <input id="spd" type="range" min="0" max="250" value="100" aria-label="骑行速度"> <span class="lbl">🚀</span> <span class="val" id="spdVal">1.0×</span> <button id="bellBtn" type="button">🔔 按铃</button> </div> <svg id="scene" viewBox="0 0 1200 700" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg"> <defs> <linearGradient id="gSky" gradientUnits="userSpaceOnUse" x1="0" y1="-500" x2="0" y2="400"> <stop offset="0" stop-color="#3f9ee0"/><stop offset=".6" stop-color="#9dd7f3"/><stop offset="1" stop-color="#ffe8c6"/> </linearGradient> <radialGradient id="gGlow"><stop offset="0" stop-color="#fff7cf" stop-opacity=".95"/><stop offset=".45" stop-color="#fff2b8" stop-opacity=".35"/><stop offset="1" stop-color="#fff2b8" stop-opacity="0"/></radialGradient> <radialGradient id="gSun" cx=".4" cy=".35"><stop offset="0" stop-color="#fffbe0"/><stop offset="1" stop-color="#ffd35c"/></radialGradient> <linearGradient id="gSea" gradientUnits="userSpaceOnUse" x1="0" y1="395" x2="0" y2="482"> <stop offset="0" stop-color="#93d3e8"/><stop offset="1" stop-color="#3b90c2"/> </linearGradient> <linearGradient id="gSand" gradientUnits="userSpaceOnUse" x1="0" y1="478" x2="0" y2="552"> <stop offset="0" stop-color="#f8e4b6"/><stop offset="1" stop-color="#ecc98a"/> </linearGradient> <linearGradient id="gBoard" gradientUnits="userSpaceOnUse" x1="0" y1="550" x2="0" y2="636"> <stop offset="0" stop-color="#d9a26b"/><stop offset="1" stop-color="#b87a45"/> </linearGradient> <linearGradient id="gFg" gradientUnits="userSpaceOnUse" x1="0" y1="650" x2="0" y2="760"> <stop offset="0" stop-color="#f1d49b"/><stop offset="1" stop-color="#e2b872"/> </linearGradient> <radialGradient id="gBody" gradientUnits="userSpaceOnUse" cx="600" cy="225" r="190"> <stop offset="0" stop-color="#ffffff"/><stop offset=".55" stop-color="#f6f9fc"/><stop offset="1" stop-color="#d5dfea"/> </radialGradient> <linearGradient id="gBeak" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffd45e"/><stop offset="1" stop-color="#f3a42f"/></linearGradient> <linearGradient id="gPouch" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffbe72"/><stop offset="1" stop-color="#ee8636"/></linearGradient> <linearGradient id="gHelmet" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#5aa4ff"/><stop offset="1" stop-color="#2463c9"/></linearGradient> <!-- 鹈鹕白色部分:描边与填充共用同一形状,拼接处无缝 --> <g id="dBody"> <path d="M504,306 L452,294 Q444,304 456,311 L448,318 Q448,330 462,329 L506,330 Z"/> <path d="M488,316 C520,280 560,248 610,246 C650,244 674,272 662,305 C652,338 614,358 570,358 C535,358 505,340 488,316 Z"/> </g> <g id="dNeck"> <path d="M598,272 C594,236 630,222 648,182 L684,182 C686,214 660,232 664,284 Z"/> <ellipse cx="668" cy="170" rx="27" ry="23"/> </g> </defs> <!-- ===== 远景 ===== --> <rect x="-1200" y="-800" width="3600" height="1210" fill="url(#gSky)"/> <g id="sun"> <circle cx="985" cy="135" r="150" fill="url(#gGlow)"/> <circle cx="985" cy="135" r="50" fill="url(#gSun)"/> </g> <g id="lyClouds"></g> <g id="lyGulls"></g> <rect x="-1200" y="395" width="3600" height="90" fill="url(#gSea)"/> <rect x="-1200" y="394" width="3600" height="2.5" fill="#eef9ff" opacity=".85"/> <g id="lyIsland"></g> <g id="lyBoat"></g> <g id="lySpark"></g> <rect x="-1200" y="478" width="3600" height="80" fill="url(#gSand)"/> <g id="lyFoam"></g> <g id="lyBeach"></g> <!-- ===== 栈道 ===== --> <g id="lyRail"></g> <rect x="-1200" y="505" width="3600" height="5" rx="2" fill="#7a5234"/> <rect x="-1200" y="526" width="3600" height="4" rx="2" fill="#7a5234"/> <rect x="-1200" y="550" width="3600" height="88" fill="url(#gBoard)"/> <rect x="-1200" y="550" width="3600" height="5" fill="#e8b884"/> <g id="lyPlanks"></g> <rect x="-1200" y="636" width="3600" height="16" fill="#8e5b34"/> <rect x="-1200" y="652" width="3600" height="700" fill="url(#gFg)"/> <g id="lyPiles"></g> <g id="lyFg"></g> <!-- ===== 自行车 + 鹈鹕 ===== --> <g id="rig"> <ellipse id="shadow" cx="600" cy="585" rx="220" ry="9" fill="#5b3a1e" opacity=".22"/> <!-- 远侧:曲柄、脚蹬、腿 --> <line id="farCrank" x1="585" y1="510" x2="585" y2="542" stroke="#6f7680" stroke-width="7" stroke-linecap="round"/> <rect id="farPedal" x="573" y="539" width="24" height="7" rx="2" fill="#3a3e45"/> <path id="farLegO" fill="none" stroke="#263041" stroke-width="15" stroke-linecap="round" stroke-linejoin="round"/> <path id="farLegF" fill="none" stroke="#c9691c" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/> <g id="farFoot"><path d="M-3,-5 C6,-4 16,-1 25,2 Q30,5 25,8 Q20,8 21,11 Q24,14 19,16 Q15,15 14,16 Q14,20 9,19 L-2,9 Z" fill="#c9691c" class="ink"/></g> <!-- 车轮(辐条由脚本生成) --> <g id="wheelR"></g> <g id="wheelF"></g> <!-- 挡泥板 --> <path d="M365.3,492.2 A90,90 0 0 1 523.9,442.1" fill="none" stroke="#263041" stroke-width="11" stroke-linecap="round"/> <path d="M365.3,492.2 A90,90 0 0 1 523.9,442.1" fill="none" stroke="#f4ecd8" stroke-width="6" stroke-linecap="round"/> <path d="M658.1,476.7 A90,90 0 0 1 831.9,476.7" fill="none" stroke="#263041" stroke-width="11" stroke-linecap="round"/> <path d="M658.1,476.7 A90,90 0 0 1 831.9,476.7" fill="none" stroke="#f4ecd8" stroke-width="6" stroke-linecap="round"/> <!-- 车架 --> <path d="M730,425 Q744,468 745,500" fill="none" stroke="#263041" stroke-width="13" stroke-linecap="round"/> <path d="M730,425 Q744,468 745,500" fill="none" stroke="#e8483f" stroke-width="8" stroke-linecap="round"/> <path d="M455,500 L585,510 L555,395 Z M555,395 L720,390 M585,510 L730,425 M720,390 L730,425" fill="none" stroke="#263041" stroke-width="14" stroke-linejoin="round" stroke-linecap="round"/> <path d="M455,500 L585,510 L555,395 Z M555,395 L720,390 M585,510 L730,425 M720,390 L730,425" fill="none" stroke="#e8483f" stroke-width="9" stroke-linejoin="round" stroke-linecap="round"/> <path d="M562,392 L712,388 M592,504 L722,429" stroke="#ff8f86" stroke-width="2" stroke-linecap="round" opacity=".8"/> <circle cx="745" cy="500" r="5" fill="#9aa3ad"/> <!-- 链条与牙盘 --> <path id="chain" d="M455,489 L585,488 A22,22 0 0 1 585,532 L455,511 A11,11 0 0 1 455,489 Z" fill="none" stroke="#3a3b42" stroke-width="3.2" stroke-dasharray="3 2"/> <circle cx="455" cy="500" r="10" fill="#c3cad2" class="ink" style="stroke-width:2.5"/> <g id="ring"> <circle cx="585" cy="510" r="24" fill="none" stroke="#6d7580" stroke-width="3" stroke-dasharray="2.5 2.2"/> <circle cx="585" cy="510" r="21" fill="#d3d9e0" class="ink"/> <path d="M585,510 L585,493 M585,510 L601.2,504.7 M585,510 L595,523.8 M585,510 L575,523.8 M585,510 L568.8,504.7" stroke="#8c95a1" stroke-width="4" stroke-linecap="round"/> </g> <!-- 座杆、坐垫 --> <path d="M555,395 L548,364" stroke="#263041" stroke-width="10" stroke-linecap="round"/> <path d="M555,395 L548,364" stroke="#b4bcc5" stroke-width="5.5" stroke-linecap="round"/> <path d="M516,361 Q546,350 578,357 Q581,364 571,367 Q546,371 520,368 Q510,366 516,361 Z" fill="#6b3e26" class="ink"/> <path d="M526,358 Q546,354 566,357" fill="none" stroke="#9a6444" stroke-width="2" stroke-linecap="round"/> <!-- 车把 --> <path d="M720,390 L716,364 C712,352 694,351 672,356" fill="none" stroke="#263041" stroke-width="11" stroke-linecap="round" stroke-linejoin="round"/> <path d="M720,390 L716,364 C712,352 694,351 672,356" fill="none" stroke="#c3cad3" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path d="M677,356 L658,360" stroke="#263041" stroke-width="14" stroke-linecap="round"/> <path d="M677,356 L658,360" stroke="#5a3a28" stroke-width="9" stroke-linecap="round"/> <g id="bell"> <path d="M692,352 A9.5,9.5 0 0 1 711,352 Z" fill="#e6ebf0" class="ink" style="stroke-width:2.2"/> <circle cx="701.5" cy="340" r="2.4" fill="#e6ebf0" class="ink" style="stroke-width:2"/> <path d="M697,347 Q700,344 704,345" fill="none" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/> </g> <!-- 车筐 + 鱼 --> <g id="basket"> <path d="M728,380 L718,377 M738,408 L732,428" stroke="#263041" stroke-width="6" stroke-linecap="round"/> <path d="M728,380 L718,377 M738,408 L732,428" stroke="#b4bcc5" stroke-width="3" stroke-linecap="round"/> <g id="fishTail"><path d="M748,372 C744,352 750,338 758,331 L750,321 Q760,322 768,325 L763,334 C767,348 764,362 762,372 Z" fill="#7fa7c4" class="ink"/></g> <path d="M770,372 C768,352 776,338 790,333 C799,338 800,354 793,372 Z" fill="#9cbad1" class="ink"/> <circle cx="789" cy="342" r="2.4" fill="#263041"/> <path d="M782,351 Q788,355 795,351" fill="none" stroke="#6b8ca6" stroke-width="2" stroke-linecap="round"/> <path d="M728,364 L798,364 L790,408 L736,408 Z" fill="#cf9552" class="ink"/> <path d="M731,379 L795,379 M733,394 L793,394 M747,366 L749,407 M763,366 L763,407 M779,366 L777,407" stroke="#9a6432" stroke-width="2" stroke-linecap="round"/> <rect x="724" y="360" width="78" height="8" rx="4" fill="#b77a3c" class="ink" style="stroke-width:2.5"/> </g> <!-- 近侧:曲柄、脚蹬、腿 --> <line id="nearCrankO" x1="585" y1="510" x2="585" y2="478" stroke="#263041" stroke-width="11" stroke-linecap="round"/> <line id="nearCrank" x1="585" y1="510" x2="585" y2="478" stroke="#c3cad3" stroke-width="6.5" stroke-linecap="round"/> <circle cx="585" cy="510" r="5" fill="#8c95a1" class="ink" style="stroke-width:2"/> <rect id="nearPedal" x="573" y="475" width="24" height="7" rx="2" fill="#3e434b" class="ink" style="stroke-width:2"/> <path id="nearLegO" fill="none" stroke="#263041" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/> <path id="nearLegF" fill="none" stroke="#f28c28" stroke-width="11" stroke-linecap="round" stroke-linejoin="round"/> <g id="nearFoot"><path d="M-3,-5 C6,-4 16,-1 25,2 Q30,5 25,8 Q20,8 21,11 Q24,14 19,16 Q15,15 14,16 Q14,20 9,19 L-2,9 Z" fill="#f28c28" class="ink"/></g> <g id="pel"> <!-- 围巾飘带(路径由脚本实时计算) --> <path id="scarfB" fill="#0f8a7f" class="ink" style="stroke-width:2.5"/> <path id="scarfA" fill="#19b3a3" class="ink" style="stroke-width:2.5"/> <!-- 白色身体:先统一描边,再统一填充 --> <g class="ol"><use href="#dBody"/><use href="#dNeck"/></g> <g class="fl"><use href="#dBody"/><use href="#dNeck"/></g> <path d="M470,306 L500,313 M466,320 L500,321" stroke="#c9d4df" stroke-width="2" stroke-linecap="round"/> <!-- 围巾 --> <path d="M600,244 C620,254 648,254 670,240 L668,260 C646,272 620,270 601,262 Z" fill="#19b3a3" class="ink" style="stroke-width:2.5"/> <path d="M616,250 L614,266 M636,253 L635,269 M655,250 L655,265" stroke="#fff" stroke-width="3" stroke-linecap="round" opacity=".85"/> <circle cx="603" cy="254" r="7" fill="#19b3a3" class="ink" style="stroke-width:2.5"/> <!-- 头部零件:嘴、喉囊、眼、头盔 --> <g id="headParts"> <path d="M690,186 L826,202 L812,213 L700,201 Z" fill="#a8423c"/> <g id="pouch"> <path d="M688,186 C735,188 790,195 826,202 C812,214 790,228 760,236 C725,244 690,236 668,210 Z" fill="url(#gPouch)" class="ink"/> <path d="M702,215 C727,227 754,227 782,216" fill="none" stroke="#d9722a" stroke-width="2" stroke-linecap="round" opacity=".7"/> </g> <path d="M684,158 C730,160 790,172 836,190 Q847,196 840,204 Q834,207 828,200 C790,192 735,187 688,187 Z" fill="url(#gBeak)" class="ink"/> <path d="M693,167 C740,171 792,181 829,194" fill="none" stroke="#e39a22" stroke-width="2" stroke-linecap="round"/> <path d="M834,191 Q846,196 840,203 Q835,206 830,200 Z" fill="#e8643c"/> <g id="eye"> <ellipse cx="675" cy="168" rx="10" ry="8.5" fill="#ffd9a0"/> <circle cx="675" cy="168" r="5.8" fill="#fff4bf" class="ink" style="stroke-width:2"/> <circle cx="676.5" cy="168" r="3" fill="#1d1d1f"/> <circle cx="677.6" cy="166.8" r="1.1" fill="#fff"/> </g> <path d="M646,166 Q656,184 668,196" fill="none" stroke="#263041" stroke-width="2"/> <path d="M638,168 C633,140 660,127 685,133 C699,137 705,149 701,160 L688,157 C670,154 652,158 638,168 Z" fill="url(#gHelmet)" class="ink"/> <path d="M652,145 C662,138 676,136 688,140 M646,156 C658,148 676,146 694,151" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" opacity=".85"/> </g> <!-- 近侧翅膀:握住车把 --> <g id="wing"> <path d="M652,346 Q646,357 634,350 Q626,361 614,350 Q604,359 594,346 Q582,353 574,338 Q561,341 556,322 L560,300 L650,320 Z" fill="#2e2f38" class="ink"/> <path d="M560,275 C590,258 630,256 650,280 C662,296 668,318 670,340 L652,346 C640,330 622,320 600,322 C585,324 570,316 556,306 C548,298 548,285 560,275 Z" fill="#fbfcfe" class="ink"/> <path d="M580,290 Q606,280 630,292 M592,306 Q616,298 640,312 M606,318 Q626,314 648,328" fill="none" stroke="#c8d3de" stroke-width="2" stroke-linecap="round"/> <path d="M648,338 C662,333 681,341 685,354 C687,364 677,369 668,365 C661,362 655,357 649,352 Z" fill="#2e2f38" class="ink"/> <path d="M662,342 Q672,349 674,360 M654,346 Q662,352 663,360" fill="none" stroke="#5b5d6b" stroke-width="1.6" stroke-linecap="round"/> </g> </g> </g> <g id="fx"></g> <g id="bubble" opacity="0"> <path d="M14,0 H120 A14,14 0 0 1 134,14 V42 A14,14 0 0 1 120,56 H42 L26,72 L28,56 H14 A14,14 0 0 1 0,42 V14 A14,14 0 0 1 14,0 Z" fill="#fff" class="ink"/> <text x="67" y="36" text-anchor="middle" font-size="22" font-weight="800" fill="#e8483f" font-family="PingFang SC,Microsoft YaHei,sans-serif">叮铃铃~</text> </g> </svg> <script> (() => { const NS = 'http://www.w3.org/2000/svg'; const $ = id => document.getElementById(id); function el(tag, attrs, parent) { const n = document.createElementNS(NS, tag); for (const k in attrs) n.setAttribute(k, attrs[k]); if (parent) parent.appendChild(n); return n; } // 固定种子的随机数:每个平铺块内容完全一致,循环滚动无缝 function rng(seed) { return () => (seed = (seed * 16807) % 2147483647) / 2147483647; } const INK = '#263041'; // ---------- 几何常量 ---------- const WHEEL_R = 80; const RH = { x: 455, y: 500 }, FH = { x: 745, y: 500 }, BB = { x: 585, y: 510 }; const CRANK = 32; // ---------- 车轮 ---------- function buildWheel(g, c) { el('circle', { cx: c.x, cy: c.y, r: 80, fill: 'none', stroke: INK, 'stroke-width': 15 }, g); el('circle', { cx: c.x, cy: c.y, r: 80, fill: 'none', stroke: '#34343a', 'stroke-width': 10 }, g); el('circle', { cx: c.x, cy: c.y, r: 74.5, fill: 'none', stroke: '#56565f', 'stroke-width': 1.5 }, g); el('circle', { cx: c.x, cy: c.y, r: 71, fill: 'none', stroke: '#c9d0d8', 'stroke-width': 3.5 }, g); const spin = el('g', {}, g); for (let i = 0; i < 20; i++) { const a = i / 20 * Math.PI * 2, b = a + (i % 2 ? 0.32 : -0.32); el('line', { x1: c.x + Math.cos(a) * 8, y1: c.y + Math.sin(a) * 8, x2: c.x + Math.cos(b) * 70, y2: c.y + Math.sin(b) * 70, stroke: '#aeb7c1', 'stroke-width': 1.4 }, spin); } el('rect', { x: c.x + 38, y: c.y - 4, width: 13, height: 8, rx: 2, fill: '#ff9f1c', stroke: INK, 'stroke-width': 1.5 }, spin); el('rect', { x: c.x - 3, y: c.y - 79, width: 6, height: 8, rx: 1.5, fill: '#9aa3ad' }, spin); el('circle', { cx: c.x, cy: c.y, r: 9, fill: '#dfe4ea', stroke: INK, 'stroke-width': 3 }, g); return spin; } const spinR = buildWheel($('wheelR'), RH); const spinF = buildWheel($('wheelF'), FH); // ---------- 视差平铺层 ---------- const layers = []; function layer(id, period, factor, build, drift = 0) { const g = $(id); const n = Math.ceil(3600 / period) + 1; for (let k = 0; k < n; k++) { const tile = el('g', { transform: `translate(${-1200 + k * period},0)` }, g); build(tile, rng(7919 + period)); } layers.push({ g, period, factor, drift }); } function cloud(p, x, y, s) { const g = el('g', { transform: `translate(${x},${y}) scale(${s})` }, p); el('ellipse', { cx: -4, cy: 20, rx: 72, ry: 11, fill: '#d3e9f7' }, g); [[-44, 2, 26], [-12, -16, 36], [24, -6, 30], [52, 6, 20], [-64, 10, 16]] .forEach(([cx, cy, r]) => el('circle', { cx, cy, r, fill: '#fff' }, g)); el('rect', { x: -72, y: 0, width: 132, height: 20, rx: 10, fill: '#fff' }, g); } layer('lyClouds', 1800, 0.05, (p, r) => { for (let i = 0; i < 5; i++) cloud(p, r() * 1800, 40 + r() * 230, 0.55 + r() * 0.7); }, 9); layer('lyIsland', 3000, 0.02, p => { const x = 380, lx = 392, b = 371; el('path', { d: `M${x - 160},397 Q${x - 95},364 ${x - 20},370 Q${x + 55},358 ${x + 140},397 Z`, fill: '#8fbab3' }, p); el('path', { d: `M${x - 110},397 Q${x - 60},381 ${x},385 Q${x + 42},379 ${x + 95},397 Z`, fill: '#77a69f' }, p); el('path', { d: `M${lx},${b - 57} L${lx + 190},${b - 86} L${lx + 190},${b - 30} Z`, fill: '#fff7c2', class: 'beam' }, p); el('path', { d: `M${lx - 9},${b} L${lx - 6},${b - 50} L${lx + 6},${b - 50} L${lx + 9},${b} Z`, fill: '#fff', stroke: '#5d6b7a', 'stroke-width': 1.2 }, p); el('path', { d: `M${lx - 8.2},${b - 12} L${lx - 7.5},${b - 23} L${lx + 7.5},${b - 23} L${lx + 8.2},${b - 12} Z M${lx - 6.9},${b - 34} L${lx - 6.4},${b - 44} L${lx + 6.4},${b - 44} L${lx + 6.9},${b - 34} Z`, fill: '#e8483f' }, p); el('rect', { x: lx - 6.5, y: b - 60, width: 13, height: 10, fill: '#ffe89a', stroke: '#5d6b7a', 'stroke-width': 1.2 }, p); el('path', { d: `M${lx - 9},${b - 60} L${lx},${b - 68} L${lx + 9},${b - 60} Z`, fill: '#e8483f' }, p); }); layer('lyBoat', 2200, 0.035, p => { const x = 1350, y = 432; const g = el('g', { class: 'bob' }, p); el('path', { d: `M${x - 24},${y} L${x + 24},${y} L${x + 17},${y + 9} L${x - 17},${y + 9} Z`, fill: '#e8483f', stroke: INK, 'stroke-width': 1.8 }, g); el('path', { d: `M${x - 2},${y - 4} L${x - 2},${y - 44} L${x - 22},${y - 5} Z`, fill: '#fff', stroke: INK, 'stroke-width': 1.8 }, g); el('path', { d: `M${x + 2},${y - 4} L${x + 2},${y - 36} L${x + 19},${y - 5} Z`, fill: '#ffd35c', stroke: INK, 'stroke-width': 1.8 }, g); }, -4); layer('lySpark', 700, 0.08, (p, r) => { for (let i = 0; i < 16; i++) { el('rect', { x: r() * 700, y: 402 + r() * 70, width: 5 + r() * 12, height: 2, rx: 1, fill: '#fff', class: 'spark', style: `animation-delay:${(-r() * 2.6).toFixed(2)}s` }, p); } for (let i = 0; i < 8; i++) { const x = r() * 680, y = 415 + r() * 58; el('path', { d: `M${x},${y} q5,-3 10,0 q5,-3 10,0`, fill: 'none', stroke: '#e6f6ff', 'stroke-width': 1.6, opacity: 0.55 }, p); } }); layer('lyFoam', 120, 0.3, p => { el('path', { d: 'M0,481 q15,-7 30,0 t30,0 t30,0 t30,0 V489 H0 Z', fill: '#fff', opacity: 0.85 }, p); }); function umbrella(p, x, y, c1, c2, tilt) { const g = el('g', { transform: `translate(${x},${y}) rotate(${tilt})` }, p); el('line', { x1: 0, y1: 0, x2: 0, y2: -84, stroke: '#6b4a2f', 'stroke-width': 3 }, g); el('path', { d: 'M-46,-60 Q-42,-94 0,-96 Q42,-94 46,-60 Z', fill: c1, stroke: INK, 'stroke-width': 2 }, g); el('path', { d: 'M-31,-60 L0,-96 L-11,-60 Z M11,-60 L0,-96 L31,-60 Z', fill: c2 }, g); el('path', { d: 'M-46,-60 Q-42,-94 0,-96 Q42,-94 46,-60 Z', fill: 'none', stroke: INK, 'stroke-width': 2 }, g); } layer('lyBeach', 1400, 0.4, p => { el('path', { d: 'M150,512 L232,512 L246,526 L164,526 Z', fill: '#ff7eb6' }, p); el('path', { d: 'M170,512 L184,526 M196,512 L210,526', stroke: '#fff', 'stroke-width': 4 }, p); umbrella(p, 200, 512, '#ff5a5f', '#fff', -6); el('path', { d: 'M520,515 L520,494 L528,494 L528,500 L536,500 L536,490 L544,490 L544,500 L552,500 L552,494 L560,494 L560,515 Z', fill: '#e2bb78', stroke: '#b98d49', 'stroke-width': 1.5 }, p); el('path', { d: 'M540,490 L540,476 L550,480 L540,484', fill: '#ff5a5f', stroke: '#b98d49', 'stroke-width': 1 }, p); umbrella(p, 860, 510, '#2f7de1', '#ffd35c', 5); const bx = 1080, by = 504; el('circle', { cx: bx, cy: by, r: 11, fill: '#fff', stroke: INK, 'stroke-width': 1.8 }, p); el('path', { d: `M${bx - 11},${by} A11,11 0 0 1 ${bx},${by - 11} L${bx},${by} Z M${bx + 11},${by} A11,11 0 0 1 ${bx},${by + 11} L${bx},${by} Z`, fill: '#ff5a5f' }, p); }); layer('lyRail', 110, 0.85, p => { el('rect', { x: 0, y: 497, width: 10, height: 56, rx: 2, fill: '#8a5c38', stroke: INK, 'stroke-width': 1.2 }, p); el('rect', { x: -2, y: 493, width: 14, height: 6, rx: 2, fill: '#a8764b', stroke: INK, 'stroke-width': 1.2 }, p); }); layer('lyPlanks', 36, 1, p => { el('line', { x1: 0, y1: 556, x2: -12, y2: 636, stroke: '#9f6d42', 'stroke-width': 2 }, p); el('line', { x1: 2.5, y1: 556, x2: -9.5, y2: 636, stroke: '#e6b37e', 'stroke-width': 1, opacity: 0.6 }, p); }); layer('lyPiles', 150, 1.02, p => { el('rect', { x: 0, y: 650, width: 16, height: 24, fill: '#6e4526' }, p); el('ellipse', { cx: 8, cy: 675, rx: 14, ry: 3, fill: '#5b3a1e', opacity: 0.2 }, p); }); layer('lyFg', 900, 1.35, (p, r) => { for (let i = 0; i < 7; i++) { const x = r() * 900, y = 668 + r() * 36, s = 0.7 + r() * 0.6; el('path', { d: `M${x},${y} q${-6 * s},${-16 * s} ${-15 * s},${-24 * s} M${x},${y} q0,${-20 * s} ${3 * s},${-34 * s} M${x},${y} q${7 * s},${-14 * s} ${16 * s},${-20 * s}`, fill: 'none', stroke: i % 2 ? '#7fae5a' : '#6c9a49', 'stroke-width': 3.2, 'stroke-linecap': 'round' }, p); } for (let i = 0; i < 4; i++) { const x = r() * 900, y = 690 + r() * 30; el('path', { d: `M${x},${y} l-9,-8 q9,-9 18,0 Z`, fill: '#ffd6c4', stroke: '#d59a86', 'stroke-width': 1.3 }, p); } }); // ---------- 海鸥 ---------- const gulls = [0, 1, 2].map(i => ({ el: el('path', { fill: 'none', stroke: '#3b4f63', 'stroke-width': 2.4, 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }, $('lyGulls')), x: 150 + i * 480, y0: 90 + i * 55, s: 1 - i * 0.18, ph: i * 2.1 })); // ---------- 特效:风线、扬沙 ---------- const fxG = $('fx'); const streaks = Array.from({ length: 9 }, () => ({ el: el('line', { stroke: '#fff', 'stroke-width': 2.2, 'stroke-linecap': 'round', opacity: 0 }, fxG), x: -999, y: 0, len: 0 })); const dust = Array.from({ length: 16 }, () => ({ el: el('circle', { r: 0, fill: '#e9c890', opacity: 0 }, fxG), x: 0, y: 0, vx: 0, vy: 0, life: 0, max: 1 })); let dustTimer = 0; // ---------- 状态 ---------- const BASE_V = 250, RATIO = 0.62, LEG_A = 95, LEG_B = 102; let speed = 1, targetSpeed = 1, dist = 0, wheelAng = 0, crankAng = -Math.PI / 2; let chainOff = 0, time = 0, last = performance.now(); let nextBlink = 2, blinkT = -1, ringT = -1, audioCtx = null, shownOdo = -1; const pel = $('pel'), dNeck = $('dNeck'), headParts = $('headParts'); const pouch = $('pouch'), eye = $('eye'), fishTail = $('fishTail'); const ringEl = $('ring'), chain = $('chain'), bell = $('bell'), bubble = $('bubble'); const scarfA = $('scarfA'), scarfB = $('scarfB'); const f1 = n => n.toFixed(1); // 两段腿的反向运动学:已知髋和踝,求膝盖(膝盖朝前) function knee(h, p) { const dx = p.x - h.x, dy = p.y - h.y; const d = Math.min(Math.max(Math.hypot(dx, dy), Math.abs(LEG_A - LEG_B) + 1), LEG_A + LEG_B - 0.5); const k = Math.atan2(dy, dx) - Math.acos((LEG_A * LEG_A + d * d - LEG_B * LEG_B) / (2 * LEG_A * d)); return { x: h.x + LEG_A * Math.cos(k), y: h.y + LEG_A * Math.sin(k) }; } function pose(side, hip, ang) { const px = BB.x + CRANK * Math.cos(ang), py = BB.y + CRANK * Math.sin(ang); const ank = { x: px - 5, y: py - 9 }, kn = knee(hip, ank); const d = `M${f1(hip.x)},${f1(hip.y)} L${f1(kn.x)},${f1(kn.y)} L${f1(ank.x)},${f1(ank.y)}`; $(side + 'LegO').setAttribute('d', d); $(side + 'LegF').setAttribute('d', d); $(side + 'Foot').setAttribute('transform', `translate(${f1(ank.x)},${f1(ank.y)}) rotate(${f1(6 + 14 * Math.sin(ang))})`); const cranks = side === 'near' ? ['nearCrank', 'nearCrankO'] : ['farCrank']; cranks.forEach(id => { $(id).setAttribute('x2', f1(px)); $(id).setAttribute('y2', f1(py)); }); const ped = $(side + 'Pedal'); ped.setAttribute('x', f1(px - 12)); ped.setAttribute('y', f1(py - 3.5)); } // 围巾飘带:沿中心线叠加行进波 function ribbon(x0, y0, len, w, ph) { const N = 14, top = [], bot = []; const droop = 34 / (0.6 + speed), amp = 3 + 4 * Math.min(speed, 2.5), fr = 5 + 7 * speed; for (let i = 0; i <= N; i++) { const u = i / N, x = x0 - u * len * (0.75 + 0.1 * Math.min(speed, 2.5)); const y = y0 + droop * u * u + amp * u * Math.sin(u * 5.5 - time * fr + ph); const hw = w * (1 - 0.3 * u); top.push(`${f1(x)},${f1(y - hw)}`); bot.unshift(`${f1(x)},${f1(y + hw)}`); } const [ex, ey] = top[N].split(',').map(Number); return `M${top.join(' L')} L${f1(ex + 7)},${f1(ey + w * 0.7)} L${bot.join(' L')} Z`; } // ---------- 铃铛 ---------- function ding() { try { audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)(); const t0 = audioCtx.currentTime; [0, 0.17].forEach((delay, i) => { [[2093, 0.2], [2637, 0.08], [4186, 0.04]].forEach(([f, vol]) => { const o = audioCtx.createOscillator(), g = audioCtx.createGain(); o.type = 'sine'; o.frequency.value = f * (i ? 1.03 : 1); g.gain.setValueAtTime(0.0001, t0 + delay); g.gain.exponentialRampToValueAtTime(vol, t0 + delay + 0.005); g.gain.exponentialRampToValueAtTime(0.0001, t0 + delay + 0.9); o.connect(g).connect(audioCtx.destination); o.start(t0 + delay); o.stop(t0 + delay + 0.95); }); }); } catch (e) { /* 无音频也照样动画 */ } } function ringBell() { ringT = 0; ding(); } // ---------- 交互 ---------- const spd = $('spd'), spdVal = $('spdVal'); function setSpeed(v) { targetSpeed = Math.max(0, Math.min(2.5, v)); spd.value = Math.round(targetSpeed * 100); spdVal.textContent = targetSpeed.toFixed(1) + '×'; } spd.addEventListener('input', () => setSpeed(spd.value / 100)); $('bellBtn').addEventListener('click', ringBell); $('scene').addEventListener('click', ringBell); addEventListener('keydown', e => { if (e.code === 'Space') { e.preventDefault(); ringBell(); } else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') setSpeed(targetSpeed + 0.2); else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') setSpeed(targetSpeed - 0.2); }); if (matchMedia('(prefers-reduced-motion: reduce)').matches) { speed = 0.6; setSpeed(0.6); } // 风线放在鹈鹕后面,避免划过角色 const backFx = el('g', {}); $('rig').before(backFx); streaks.forEach(s => backFx.appendChild(s.el)); const odo = $('odo'); const easeBack = x => 1 + 2.70158 * Math.pow(x - 1, 3) + 1.70158 * Math.pow(x - 1, 2); // ---------- 主循环 ---------- function frame(now) { const dt = Math.min(0.05, (now - last) / 1000); last = now; time += dt; speed += (targetSpeed - speed) * Math.min(1, dt * 3); const v = BASE_V * speed, dW = v / WHEEL_R * dt, dC = dW * RATIO; dist += v * dt; wheelAng += dW; crankAng += dC; chainOff -= dC * 22; // 视差背景 for (const L of layers) { const off = ((dist * L.factor + time * L.drift) % L.period + L.period) % L.period; L.g.setAttribute('transform', `translate(${(-off).toFixed(2)},0)`); } // 车轮、牙盘、链条 const wd = f1(wheelAng * 180 / Math.PI % 360); spinR.setAttribute('transform', `rotate(${wd} ${RH.x} ${RH.y})`); spinF.setAttribute('transform', `rotate(${wd} ${FH.x} ${FH.y})`); ringEl.setAttribute('transform', `rotate(${f1(crankAng * 180 / Math.PI % 360)} ${BB.x} ${BB.y})`); chain.setAttribute('stroke-dashoffset', (chainOff % 5).toFixed(2)); // 身体随踩踏起伏,两条腿跟着脚蹬走 const act = Math.min(1, speed); const bob = 1.4 * Math.sin(2 * crankAng) * act; pel.setAttribute('transform', `translate(0,${bob.toFixed(2)})`); pose('far', { x: 556, y: 341 + bob }, crankAng + Math.PI); pose('near', { x: 565, y: 345 + bob }, crankAng); // 头颈:踩一下点一下头,骑得越快越往前探 const lean = Math.max(-3, Math.min(8, (speed - 1) * 5)); const nod = 2 * Math.sin(2 * crankAng + 0.9) * act + lean + 1.2 * Math.sin(time * 0.7); const ht = `rotate(${nod.toFixed(2)} 630 275)`; dNeck.setAttribute('transform', ht); headParts.setAttribute('transform', ht); // 按铃:铃铛摇晃、张嘴、冒气泡 let open = 0; if (ringT >= 0) { ringT += dt; const w = Math.sin(ringT * 55) * 16 * Math.exp(-ringT * 4); bell.setAttribute('transform', `rotate(${w.toFixed(2)} 701.5 352)`); open = ringT < 0.8 ? Math.sin(Math.PI * ringT / 0.8) : 0; const s = ringT < 0.22 ? easeBack(ringT / 0.22) : 1; const op = ringT < 1.3 ? 1 : Math.max(0, 1 - (ringT - 1.3) / 0.3); bubble.setAttribute('opacity', op.toFixed(2)); bubble.setAttribute('transform', `translate(716,${f1(342 - 8 * ringT)}) scale(${Math.max(0.01, s).toFixed(3)}) translate(-26,-72)`); if (ringT > 1.6) { ringT = -1; bubble.setAttribute('opacity', 0); bell.removeAttribute('transform'); } } const jig = 1 + 0.07 * Math.sin(2 * crankAng + 1.8) * act + 0.03 * Math.sin(time * 3.1); pouch.setAttribute('transform', `rotate(${f1(open * 10)} 690 188) translate(750 196) scale(1 ${jig.toFixed(3)}) translate(-750 -196)`); // 眨眼 nextBlink -= dt; if (nextBlink <= 0) { blinkT = 0; nextBlink = 2.5 + Math.random() * 3; } let ey = 1; if (blinkT >= 0) { blinkT += dt; ey = Math.max(0.08, Math.abs(1 - blinkT / 0.07)); if (blinkT > 0.14) { blinkT = -1; ey = 1; } } eye.setAttribute('transform', `translate(675 168) scale(1 ${ey.toFixed(2)}) translate(-675 -168)`); // 车筐里的鱼甩尾巴、围巾飘动 fishTail.setAttribute('transform', `rotate(${f1(Math.sin(time * 7) * 7)} 755 368)`); scarfA.setAttribute('d', ribbon(601, 252, 96, 7, 0)); scarfB.setAttribute('d', ribbon(601, 257, 76, 6, 1.9)); // 海鸥 for (const g of gulls) { g.x -= (v * 0.1 - 22) * dt; if (g.x < -420) g.x += 2000; else if (g.x > 1580) g.x -= 2000; const fl = Math.sin(time * 7 + g.ph), s = g.s, y = g.y0 + 8 * Math.sin(time * 0.9 + g.ph); const wy = (-5 - 7 * fl) * s; g.el.setAttribute('d', `M${f1(g.x - 14 * s)},${f1(y + 2 * s)} Q${f1(g.x - 7 * s)},${f1(y + wy)} ${f1(g.x)},${f1(y)} Q${f1(g.x + 7 * s)},${f1(y + wy)} ${f1(g.x + 14 * s)},${f1(y + 2 * s)}`); } // 风线:速度越快越明显 const sOp = Math.max(0, Math.min(0.75, (speed - 0.9) / 1.2)).toFixed(2); for (const s of streaks) { s.x -= v * 2.4 * dt; if (s.x + s.len < -300) { s.x = 900 + Math.random() * 700; s.y = 110 + Math.random() * 380; s.len = 40 + Math.random() * 90; } s.el.setAttribute('x1', f1(s.x)); s.el.setAttribute('x2', f1(s.x + s.len)); s.el.setAttribute('y1', f1(s.y)); s.el.setAttribute('y2', f1(s.y)); s.el.setAttribute('opacity', sOp); } // 后轮扬起的沙粒 dustTimer -= dt * speed; if (speed > 0.15 && dustTimer <= 0) { dustTimer = 0.09; const p = dust.find(d => d.life <= 0); if (p) { p.x = RH.x - 32 + Math.random() * 12; p.y = 578; p.vx = -(30 + Math.random() * 50) - v * 0.3; p.vy = -(12 + Math.random() * 22); p.max = p.life = 0.6 + Math.random() * 0.4; } } for (const p of dust) { if (p.life <= 0) continue; p.life -= dt; p.x += p.vx * dt; p.y += p.vy * dt; p.vy += 25 * dt; const k = Math.min(1, 1 - p.life / p.max); p.el.setAttribute('cx', f1(p.x)); p.el.setAttribute('cy', f1(p.y)); p.el.setAttribute('r', f1(3 + 7 * k)); p.el.setAttribute('opacity', p.life > 0 ? (0.5 * (1 - k)).toFixed(2) : 0); } // 里程 const m = Math.floor(dist / 100); if (m !== shownOdo) { shownOdo = m; odo.textContent = m; } requestAnimationFrame(frame); } requestAnimationFrame(frame); })(); </script> </body> </html>主楼 - coolb2026-09-2320530
铁汁,能不能直接上图。。。。
- 隔壁老陈2026-09-2328146
emmmm不是说了吗,效果真的不是截图能感受到的,懒得做动图了。
- Myth2026-09-2311226
neo啥时候支持个html站内渲染
- 隔壁老陈2026-09-2328146
别说了,我本来想直接把html文件发上来,结果,还不让上传。
- 隔壁老陈2026-09-2328146
也可以直接去看哈雷佬打包的html附件,震惊围坐 Opus 5.5
- 哈雷彗星2026-09-234777
- Myth2026-09-2311226