Arena ai抽卡抽出来了带交互的骑车鹈鹕太逆天了!佬友们看看是什么模型?
wenjiu19
2026-08-14 17:57
1

源代码
此文本将被隐藏
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>鹈鹕骑自行车 2D 动画 - Pelican On A Bicycle</title>
<style>
:root {
--bg-gradient: linear-gradient(180deg, #38bdf8 0%, #bae6fd 55%, #fef08a 85%, #fde047 100%);
--ground-color: #4ade80;
--road-color: #334155;
--road-line: #facc15;
--ui-bg: rgba(255, 255, 255, 0.88);
--ui-border: rgba(255, 255, 255, 0.6);
--ui-text: #1e293b;
--ui-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.15), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
--accent: #f97316;
--accent-hover: #ea580c;
}
/* Night theme */
body.theme-night {
--bg-gradient: linear-gradient(180deg, #0f172a 0%, #1e1b4b 50%, #312e81 80%, #4338ca 100%);
--ground-color: #1e293b;
--road-color: #18181b;
--road-line: #e4e4e7;
--ui-bg: rgba(15, 23, 42, 0.88);
--ui-border: rgba(255, 255, 255, 0.15);
--ui-text: #f8fafc;
--ui-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
}
/* Sunset theme */
body.theme-sunset {
--bg-gradient: linear-gradient(180deg, #701a75 0%, #c026d3 30%, #f97316 65%, #fde047 100%);
--ground-color: #166534;
--road-color: #292524;
--road-line: #fdba74;
--ui-bg: rgba(255, 247, 237, 0.9);
--ui-border: rgba(251, 146, 60, 0.3);
--ui-text: #431407;
--ui-shadow: 0 10px 25px -5px rgba(194, 65, 12, 0.25);
}
/* Synthwave theme */
body.theme-synthwave {
--bg-gradient: linear-gradient(180deg, #090014 0%, #2e0854 45%, #7e127a 75%, #ff2a85 100%);
--ground-color: #110022;
--road-color: #0d021a;
--road-line: #00ffff;
--ui-bg: rgba(26, 5, 48, 0.88);
--ui-border: rgba(255, 42, 133, 0.4);
--ui-text: #00ffff;
--ui-shadow: 0 10px 25px -5px rgba(255, 42, 133, 0.4);
--accent: #ff007f;
--accent-hover: #ff3399;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #0f172a;
color: var(--ui-text);
overflow-x: hidden;
padding: 16px;
}
.main-container {
width: 100%;
max-width: 1200px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
/* Title header */
.header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 24px;
background: var(--ui-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--ui-border);
border-radius: 18px;
box-shadow: var(--ui-shadow);
transition: all 0.4s ease;
}
.title-group {
display: flex;
align-items: center;
gap: 12px;
}
.title-icon {
font-size: 32px;
animation: bounce 2s infinite ease-in-out;
}
h1 {
font-size: 22px;
font-weight: 800;
background: linear-gradient(135deg, #ea580c 0%, #f59e0b 50%, #10b981 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.5px;
}
.sub-title {
font-size: 13px;
opacity: 0.75;
font-weight: 500;
}
.stats-badge {
display: flex;
gap: 16px;
font-size: 14px;
font-weight: 600;
}
.stat-item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: rgba(0, 0, 0, 0.05);
border-radius: 20px;
}
body.theme-night .stat-item,
body.theme-synthwave .stat-item {
background: rgba(255, 255, 255, 0.08);
}
/* Screen / Canvas Area */
.canvas-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
max-height: 640px;
background: var(--bg-gradient);
border-radius: 24px;
overflow: hidden;
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.3), 0 0 0 1px var(--ui-border);
transition: background 0.6s ease;
}
svg#scene {
width: 100%;
height: 100%;
display: block;
cursor: pointer;
}
/* Clickable ripple / action bubble */
.bubble-fx {
position: absolute;
pointer-events: none;
font-size: 20px;
font-weight: 800;
color: #ea580c;
animation: floatUpFade 1.2s cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
text-shadow: 0 2px 8px rgba(255, 255, 255, 0.8);
z-index: 20;
}
@keyframes floatUpFade {
0% { opacity: 0; transform: translate(-50%, 0) scale(0.5); }
20% { opacity: 1; transform: translate(-50%, -20px) scale(1.2); }
100% { opacity: 0; transform: translate(-50%, -80px) scale(0.9); }
}
/* Speed lines overlay for turbo */
.speed-lines-overlay {
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0;
background: repeating-linear-gradient(90deg, transparent, transparent 50px, rgba(255, 255, 255, 0.15) 50px, rgba(255, 255, 255, 0.15) 52px);
transition: opacity 0.3s ease;
}
.speed-lines-overlay.active {
opacity: 1;
animation: speedStreak 0.1s linear infinite;
}
@keyframes speedStreak {
0% { transform: translateX(0); }
100% { transform: translateX(-50px); }
}
/* Controls container */
.controls-panel {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
.card {
background: var(--ui-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--ui-border);
border-radius: 18px;
padding: 16px 20px;
box-shadow: var(--ui-shadow);
display: flex;
flex-direction: column;
gap: 12px;
transition: all 0.3s ease;
}
.card-title {
font-size: 14px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
opacity: 0.7;
display: flex;
align-items: center;
gap: 8px;
}
.btn-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
button {
background: rgba(0, 0, 0, 0.06);
color: var(--ui-text);
border: 1px solid transparent;
padding: 9px 14px;
border-radius: 12px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
body.theme-night button,
body.theme-synthwave button {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.08);
}
button:hover {
background: var(--accent);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(234, 88, 12, 0.3);
border-color: transparent;
}
button:active {
transform: translateY(0);
}
button.active {
background: var(--accent);
color: white;
box-shadow: 0 4px 12px rgba(234, 88, 12, 0.35);
}
/* Big Action Buttons */
.action-btn {
flex: 1;
padding: 12px 16px;
font-size: 14px;
background: linear-gradient(135deg, #f97316, #ea580c);
color: white;
box-shadow: 0 4px 14px rgba(234, 88, 12, 0.35);
border: none;
}
.action-btn:hover {
background: linear-gradient(135deg, #ea580c, #c2410c);
}
.action-btn.secondary {
background: linear-gradient(135deg, #0284c7, #0369a1);
box-shadow: 0 4px 14px rgba(2, 132, 199, 0.35);
}
.action-btn.secondary:hover {
background: linear-gradient(135deg, #0369a1, #075985);
}
.action-btn.green {
background: linear-gradient(135deg, #10b981, #059669);
box-shadow: 0 4px 14px rgba(16, 185, 129, 0.35);
}
.action-btn.green:hover {
background: linear-gradient(135deg, #059669, #047857);
}
/* Slider style */
.slider-container {
display: flex;
align-items: center;
gap: 12px;
}
input[type="range"] {
flex: 1;
height: 8px;
border-radius: 4px;
background: rgba(0, 0, 0, 0.15);
outline: none;
-webkit-appearance: none;
cursor: pointer;
}
body.theme-night input[type="range"],
body.theme-synthwave input[type="range"] {
background: rgba(255, 255, 255, 0.2);
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: transform 0.1s ease;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.15);
}
/* Tips & Instructions */
.instructions {
font-size: 12px;
opacity: 0.7;
text-align: center;
line-height: 1.5;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
/* Responsive adjustments */
@media (max-width: 768px) {
.header {
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
.stats-badge {
width: 100%;
justify-content: space-between;
}
.controls-panel {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="main-container">
<!-- Header -->
<header class="header">
<div class="title-group">
<div class="title-icon">🦆🚲</div>
<div>
<h1>鹈鹕骑行日记 - Pelican Grand Tour</h1>
<div class="sub-title">纯矢量 SVG 动态骨骼绑定与物理摆动系统</div>
</div>
</div>
<div class="stats-badge">
<div class="stat-item">
<span>⚡ 速度:</span>
<span id="speedVal" style="color: #ea580c">28 km/h</span>
</div>
<div class="stat-item">
<span>🚩 骑行里程:</span>
<span id="distVal">0.0 km</span>
</div>
<div class="stat-item">
<span>🐟 鱼干收获:</span>
<span id="fishVal" style="color: #0284c7">0 条</span>
</div>
</div>
</header>
<!-- Canvas SVG Scene -->
<div class="canvas-wrapper" id="canvasWrapper">
<div class="speed-lines-overlay" id="speedLines"></div>
<svg id="scene" viewBox="0 0 1200 675" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- Sun Glow -->
<radialGradient id="sunGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fffbeb" stop-opacity="1" />
<stop offset="30%" stop-color="#fde047" stop-opacity="0.9" />
<stop offset="70%" stop-color="#f59e0b" stop-opacity="0.4" />
<stop offset="100%" stop-color="#ea580c" stop-opacity="0" />
</radialGradient>
<!-- Moon Glow -->
<radialGradient id="moonGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff" stop-opacity="1" />
<stop offset="40%" stop-color="#93c5fd" stop-opacity="0.7" />
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0" />
</radialGradient>
<!-- Mountain Far Gradient -->
<linearGradient id="mountFarGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#93c5fd" stop-opacity="0.5" />
<stop offset="100%" stop-color="#bae6fd" stop-opacity="0.9" />
</linearGradient>
<!-- Mountain Mid Gradient -->
<linearGradient id="mountMidGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#60a5fa" stop-opacity="0.8" />
<stop offset="100%" stop-color="#38bdf8" stop-opacity="0.95" />
</linearGradient>
<!-- Hills Foreground Gradient -->
<linearGradient id="hillGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#4ade80" />
<stop offset="100%" stop-color="#16a34a" />
</linearGradient>
<!-- Bike Frame Chrome / Mint Metallic Gradient -->
<linearGradient id="bikeFrameGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#2dd4bf" />
<stop offset="50%" stop-color="#14b8a6" />
<stop offset="100%" stop-color="#0f766e" />
</linearGradient>
<!-- Chrome Tube Highlights -->
<linearGradient id="chromeGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#ffffff" />
<stop offset="30%" stop-color="#e2e8f0" />
<stop offset="70%" stop-color="#94a3b8" />
<stop offset="100%" stop-color="#475569" />
</linearGradient>
<!-- Pelican Beak Top Gradient -->
<linearGradient id="beakGrad" x1="0%" y1="0%" x2="100%" y2="50%">
<stop offset="0%" stop-color="#fbbf24" />
<stop offset="70%" stop-color="#f97316" />
<stop offset="100%" stop-color="#ea580c" />
</linearGradient>
<!-- Pelican Gular Pouch Gradient -->
<linearGradient id="pouchGrad" x1="10%" y1="0%" x2="90%" y2="100%">
<stop offset="0%" stop-color="#fed7aa" stop-opacity="0.95" />
<stop offset="50%" stop-color="#fca5a5" stop-opacity="0.92" />
<stop offset="100%" stop-color="#fb7185" stop-opacity="0.98" />
</linearGradient>
<!-- Pelican Body Feather Gradient -->
<linearGradient id="pelicanFeather" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#ffffff" />
<stop offset="65%" stop-color="#f8fafc" />
<stop offset="100%" stop-color="#cbd5e1" />
</linearGradient>
<!-- Pelican Wing Gradient -->
<linearGradient id="pelicanWing" x1="0%" y1="0%" x2="100%" y2="80%">
<stop offset="0%" stop-color="#f1f5f9" />
<stop offset="50%" stop-color="#e2e8f0" />
<stop offset="100%" stop-color="#94a3b8" />
</linearGradient>
<!-- Pelican Leg Front Gradient -->
<linearGradient id="legFront" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#fb923c" />
<stop offset="100%" stop-color="#ea580c" />
</linearGradient>
<!-- Pelican Leg Back Gradient (darker for depth) -->
<linearGradient id="legBack" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#c2410c" />
<stop offset="100%" stop-color="#9a3412" />
</linearGradient>
<!-- Tire Rubber Texture Gradient -->
<radialGradient id="tireGrad" cx="50%" cy="50%" r="50%">
<stop offset="70%" stop-color="#1e293b" />
<stop offset="85%" stop-color="#334155" />
<stop offset="95%" stop-color="#0f172a" />
<stop offset="100%" stop-color="#020617" />
</radialGradient>
<!-- Headlight Beam Cone -->
<linearGradient id="headlightBeam" x1="0%" y1="0%" x2="100%" y2="30%">
<stop offset="0%" stop-color="#fef08a" stop-opacity="0.8" />
<stop offset="50%" stop-color="#fef08a" stop-opacity="0.3" />
<stop offset="100%" stop-color="#fef08a" stop-opacity="0" />
</linearGradient>
<!-- Turbo Rocket Flame -->
<linearGradient id="turboFlame" x1="0%" y1="50%" x2="100%" y2="50%">
<stop offset="0%" stop-color="#ef4444" />
<stop offset="30%" stop-color="#f97316" />
<stop offset="70%" stop-color="#facc15" />
<stop offset="100%" stop-color="#ffffff" />
</linearGradient>
<!-- Drop Shadows -->
<filter id="shadow" x="-10%" y="-10%" width="130%" height="130%">
<feDropShadow dx="2" dy="5" stdDeviation="4" flood-opacity="0.25" />
</filter>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- Ground Contact Shadow -->
<radialGradient id="groundShadow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="rgba(0,0,0,0.4)" />
<stop offset="60%" stop-color="rgba(0,0,0,0.15)" />
<stop offset="100%" stop-color="rgba(0,0,0,0)" />
</radialGradient>
</defs>
<!-- ================= BACKGROUND LAYERS (PARALLAX) ================= -->
<!-- Celestial Body: Sun & Moon -->
<g id="celestialGroup" transform="translate(1000, 140)">
<!-- Sun -->
<g id="sunElement">
<circle cx="0" cy="0" r="110" fill="url(#sunGlow)" />
<circle cx="0" cy="0" r="50" fill="#fde047" />
<!-- Sun Rays -->
<g id="sunRays" stroke="#fef08a" stroke-width="4" stroke-linecap="round" opacity="0.6">
<line x1="0" y1="-65" x2="0" y2="-80" />
<line x1="46" y1="-46" x2="56" y2="-56" />
<line x1="65" y1="0" x2="80" y2="0" />
<line x1="46" y1="46" x2="56" y2="56" />
<line x1="0" y1="65" x2="0" y2="80" />
<line x1="-46" y1="46" x2="-56" y2="56" />
<line x1="-65" y1="0" x2="-80" y2="0" />
<line x1="-46" y1="-46" x2="-56" y2="-56" />
</g>
</g>
<!-- Moon (hidden in day, shown in night) -->
<g id="moonElement" opacity="0" style="transition: opacity 0.5s;">
<circle cx="0" cy="0" r="80" fill="url(#moonGlow)" />
<circle cx="0" cy="0" r="42" fill="#f8fafc" />
<circle cx="14" cy="-8" r="36" fill="#1e1b4b" opacity="0.9" />
</g>
</g>
<!-- Stars (Night Mode) -->
<g id="starsGroup" opacity="0" style="transition: opacity 0.5s;"></g>
<!-- Clouds Layer 1 (Far, Slower) -->
<g id="cloudsFar"></g>
<!-- Distant Mountains -->
<g id="mountainsFar"></g>
<!-- Clouds Layer 2 (Mid) -->
<g id="cloudsMid"></g>
<!-- Midground Hills with Windmills & Trees -->
<g id="hillsMid"></g>
<!-- Scenery Objects (Lighthouses, Palm Trees, Signposts) -->
<g id="sceneryProps"></g>
<!-- Foreground Rolling Ground & Flowers -->
<g id="groundForeground">
<path id="groundPath" d="M0,520 Q300,515 600,520 T1200,520 L1200,675 L0,675 Z" fill="var(--ground-color)" />
</g>
<!-- Road Surface -->
<g id="roadGroup">
<!-- Asphalt -->
<path d="M-50,540 L1250,540 L1250,650 L-50,650 Z" fill="var(--road-color)" />
<!-- Road Curbs -->
<line x1="-50" y1="540" x2="1250" y2="540" stroke="#94a3b8" stroke-width="4" />
<line x1="-50" y1="650" x2="1250" y2="650" stroke="#64748b" stroke-width="4" />
<!-- Road Center Dashed Stripes (Animated) -->
<g id="roadDashes"></g>
<!-- Roadside Posts / Cat-eyes / Wildflowers -->
<g id="roadsideFlora"></g>
</g>
<!-- ================= BICYCLE & PELICAN RIG ================= -->
<g id="rigRoot" transform="translate(0, 0)">
<!-- Ground Shadows under wheels and pedals -->
<ellipse id="shadowRear" cx="440" cy="542" rx="65" ry="14" fill="url(#groundShadow)" />
<ellipse id="shadowFront" cx="740" cy="542" rx="65" ry="14" fill="url(#groundShadow)" />
<ellipse id="shadowCenter" cx="590" cy="543" rx="100" ry="18" fill="url(#groundShadow)" />
<!-- Speed Dust Particles -->
<g id="dustParticles"></g>
<!-- Headlight Light Cone (Projected onto Road) -->
<polygon id="lightBeam" points="765,370 1200,430 1200,580 770,390" fill="url(#headlightBeam)" opacity="0" />
<!-- Turbo Flames (Emitted from rear rack) -->
<g id="turboFlamesGroup" transform="translate(365, 395)" opacity="0">
<path id="flameMain" d="M0,-10 C-40,-8 -70,0 -110,0 C-70,5 -40,8 0,10 Z" fill="url(#turboFlame)" filter="url(#glow)" />
<path id="flameInner" d="M0,-5 C-25,-4 -45,0 -70,0 C-45,4 -25,5 0,5 Z" fill="#ffffff" />
</g>
<!-- ================= LAYER 1: BACK LEG & BACK PEDAL ================= -->
<g id="backLegGroup">
<!-- Back Crank Arm -->
<line id="crankBack" x1="580" y1="465" x2="555" y2="445" stroke="#475569" stroke-width="9" stroke-linecap="round" />
<!-- Back Pedal -->
<rect id="pedalBack" x="543" y="440" width="24" height="10" rx="3" fill="#1e293b" stroke="#64748b" stroke-width="2" />
<!-- Back Leg (Darker orange for 3D depth) -->
<!-- Thigh, Knee, Shin, Webbed Foot -->
<path id="backLegPath" d="" fill="url(#legBack)" stroke="#7c2d12" stroke-width="2" stroke-linejoin="round" />
<path id="backFootPath" d="" fill="#ea580c" stroke="#7c2d12" stroke-width="2" />
</g>
<!-- ================= LAYER 2: BICYCLE REAR WHEEL ================= -->
<g id="rearWheelGroup" transform="translate(440, 465)">
<!-- Outer Tire -->
<circle cx="0" cy="0" r="76" fill="url(#tireGrad)" />
<!-- Rim -->
<circle cx="0" cy="0" r="66" fill="none" stroke="#e2e8f0" stroke-width="7" />
<circle cx="0" cy="0" r="62" fill="none" stroke="#64748b" stroke-width="1.5" />
<!-- Spokes (Will rotate dynamically) -->
<g id="rearSpokes" stroke="#cbd5e1" stroke-width="1.5" opacity="0.85"></g>
<!-- Orange Wheel Reflector -->
<rect x="22" y="-6" width="22" height="12" rx="4" fill="#f97316" stroke="#c2410c" stroke-width="1.5" />
<!-- Hub -->
<circle cx="0" cy="0" r="13" fill="#475569" />
<circle cx="0" cy="0" r="7" fill="#f8fafc" />
</g>
<!-- ================= LAYER 3: BICYCLE MAIN FRAME ================= -->
<g id="bikeFrameGroup">
<!-- Rear Rack -->
<path d="M435,465 L410,400 L470,400 L440,465 Z" fill="none" stroke="#0f766e" stroke-width="5" stroke-linejoin="round" />
<path d="M400,397 L480,397" stroke="#14b8a6" stroke-width="6" stroke-linecap="round" />
<!-- Rear Mudguard (Fender) -->
<path d="M375,475 A78,78 0 0,1 475,395" fill="none" stroke="#f8fafc" stroke-width="9" stroke-linecap="round" />
<path d="M375,475 A78,78 0 0,1 475,395" fill="none" stroke="#0f766e" stroke-width="3" stroke-linecap="round" />
<line x1="440" y1="465" x2="385" y2="485" stroke="#94a3b8" stroke-width="3" stroke-linecap="round" />
<!-- Chainstays & Seatstays -->
<line x1="440" y1="465" x2="580" y2="465" stroke="url(#bikeFrameGrad)" stroke-width="9" stroke-linecap="round" />
<line x1="440" y1="465" x2="505" y2="350" stroke="url(#bikeFrameGrad)" stroke-width="9" stroke-linecap="round" />
<!-- Seat Tube -->
<line x1="580" y1="465" x2="505" y2="350" stroke="url(#bikeFrameGrad)" stroke-width="10" stroke-linecap="round" />
<!-- Seat Post (Chrome) -->
<line x1="505" y1="350" x2="495" y2="315" stroke="url(#chromeGrad)" stroke-width="7" stroke-linecap="round" />
<!-- Down Tube -->
<path d="M580,465 C620,440 680,370 710,320" fill="none" stroke="url(#bikeFrameGrad)" stroke-width="12" stroke-linecap="round" />
<!-- Vintage Cruiser Curved Top Tube -->
<path d="M505,350 C570,330 650,325 710,320" fill="none" stroke="url(#bikeFrameGrad)" stroke-width="11" stroke-linecap="round" />
<path d="M505,370 C570,355 640,345 710,320" fill="none" stroke="url(#bikeFrameGrad)" stroke-width="7" stroke-linecap="round" />
<!-- Head Tube -->
<line x1="710" y1="315" x2="715" y2="355" stroke="url(#bikeFrameGrad)" stroke-width="12" stroke-linecap="round" />
<!-- Front Mudguard -->
<path d="M690,405 A78,78 0 0,1 785,420" fill="none" stroke="#f8fafc" stroke-width="9" stroke-linecap="round" />
<path d="M690,405 A78,78 0 0,1 785,420" fill="none" stroke="#0f766e" stroke-width="3" stroke-linecap="round" />
<line x1="740" y1="465" x2="780" y2="435" stroke="#94a3b8" stroke-width="3" stroke-linecap="round" />
<!-- Front Fork -->
<line x1="715" y1="355" x2="740" y2="465" stroke="url(#bikeFrameGrad)" stroke-width="9" stroke-linecap="round" />
<!-- Drive Chain & Chainring -->
<circle cx="580" cy="465" r="24" fill="#334155" stroke="#64748b" stroke-width="3" />
<path d="M580,443 L440,455 A12,12 0 0,0 440,475 L580,487 Z" fill="none" stroke="#cbd5e1" stroke-width="4" stroke-dasharray="7,4" id="chainPath" />
<circle cx="580" cy="465" r="16" fill="#1e293b" />
<!-- Leather Spring Saddle / Seat -->
<g id="saddleGroup" transform="translate(490, 310)">
<!-- Springs under seat -->
<path d="M-10,12 Q-5,2 -10,-4 M10,12 Q15,2 10,-4" fill="none" stroke="#94a3b8" stroke-width="4" />
<!-- Saddle Body -->
<path d="M-30,-2 C-20,-12 15,-12 35,-4 C30,6 20,8 -5,8 C-25,8 -32,4 -30,-2 Z" fill="#78350f" stroke="#451a03" stroke-width="2.5" />
<path d="M-26,-2 C-15,-8 10,-8 28,-3" fill="none" stroke="#b45309" stroke-width="2" />
<!-- Saddle Rivets -->
<circle cx="-20" cy="2" r="1.5" fill="#fde047" />
<circle cx="-10" cy="4" r="1.5" fill="#fde047" />
<circle cx="0" cy="4" r="1.5" fill="#fde047" />
</g>
<!-- Handlebar Stem & Bar -->
<line x1="710" y1="315" x2="705" y2="265" stroke="url(#chromeGrad)" stroke-width="8" stroke-linecap="round" />
<!-- Swept-back Cruiser Handlebars -->
<path d="M705,265 C705,245 680,240 660,250" fill="none" stroke="url(#chromeGrad)" stroke-width="8" stroke-linecap="round" />
<!-- Rubber Handgrip -->
<path d="M668,247 L650,255" stroke="#78350f" stroke-width="11" stroke-linecap="round" />
<!-- Front Basket (Wicker) -->
<g id="basketGroup" transform="translate(718, 260)">
<!-- Basket Support Bracket -->
<line x1="0" y1="40" x2="-10" y2="80" stroke="#64748b" stroke-width="3" />
<line x1="20" y1="40" x2="15" y2="80" stroke="#64748b" stroke-width="3" />
<!-- Basket Body -->
<path d="M-5,10 L38,10 L30,48 L-2,48 Z" fill="#d97706" stroke="#92400e" stroke-width="2.5" />
<!-- Wicker Weave Pattern -->
<path d="M2,10 L-1,48 M10,10 L8,48 M20,10 L17,48 M30,10 L25,48 M-4,22 L36,22 M-3,34 L33,34" fill="none" stroke="#b45309" stroke-width="1.8" />
<!-- Basket Companion: Bubbles the Happy Little Fish! -->
<g id="basketFish" transform="translate(14, 8)">
<!-- Fish Body -->
<path d="M0,0 C-12,-15 -25,-5 -30,-15 C-25,0 -30,12 -18,10 C-8,14 2,10 5,0 Z" fill="#38bdf8" stroke="#0284c7" stroke-width="1.8" />
<!-- Fish Tail -->
<path d="M-25,-5 L-36,-15 L-30,-5 L-38,5 Z" fill="#0284c7" />
<!-- Fish Eye -->
<circle cx="-5" cy="-3" r="3.5" fill="#ffffff" />
<circle cx="-4.5" cy="-3" r="1.8" fill="#0f172a" />
<circle cx="-4" cy="-4" r="0.8" fill="#ffffff" />
<!-- Cute Swim Goggles / Party Sunglasses on fish -->
<rect x="-8" y="-6" width="8" height="5" rx="2" fill="rgba(234, 88, 12, 0.7)" stroke="#c2410c" stroke-width="1" />
<!-- Fish Fin -->
<path d="M-10,3 C-14,8 -8,12 -6,5 Z" fill="#0284c7" />
</g>
</g>
<!-- Brass Vintage Bicycle Bell (Clickable!) -->
<g id="bellGroup" transform="translate(685, 240)" style="cursor: pointer;">
<circle cx="0" cy="0" r="10" fill="#facc15" stroke="#ca8a04" stroke-width="2" />
<circle cx="-2" cy="-2" r="7" fill="#fef08a" opacity="0.6" />
<circle cx="5" cy="5" r="3" fill="#a16207" />
<!-- Bell vibration soundwaves (triggered on click) -->
<g id="bellRings" opacity="0">
<path d="M-12,-12 A18,18 0 0,1 12,-12" fill="none" stroke="#facc15" stroke-width="2" stroke-linecap="round" />
<path d="M-16,-16 A24,24 0 0,1 16,-16" fill="none" stroke="#f59e0b" stroke-width="2" stroke-linecap="round" />
</g>
</g>
<!-- Streamers / Ribbons on Handlebar -->
<g id="streamersGroup" transform="translate(650, 255)">
<path id="ribbon1" d="M0,0 C-15,5 -30,-5 -45,2" fill="none" stroke="#ec4899" stroke-width="3" stroke-linecap="round" />
<path id="ribbon2" d="M0,0 C-12,12 -28,8 -42,16" fill="none" stroke="#38bdf8" stroke-width="3" stroke-linecap="round" />
<path id="ribbon3" d="M0,0 C-18,2 -32,15 -48,10" fill="none" stroke="#facc15" stroke-width="3" stroke-linecap="round" />
</g>
<!-- Vintage Headlight Lamp -->
<g id="headlightLamp" transform="translate(745, 335)">
<!-- Chrome Bracket -->
<path d="M-25,-15 L-5,0" stroke="#94a3b8" stroke-width="4" stroke-linecap="round" />
<!-- Lamp Shell -->
<path d="M-10,-14 C5,-14 16,-10 18,0 C16,10 5,14 -10,14 C-16,10 -18,-10 -10,-14 Z" fill="#e2e8f0" stroke="#475569" stroke-width="2" />
<!-- Glass Lens -->
<path d="M16,-10 C20,-5 20,5 16,10 Z" fill="#fef08a" stroke="#ca8a04" stroke-width="1.5" />
</g>
</g>
<!-- ================= LAYER 4: BICYCLE FRONT WHEEL ================= -->
<g id="frontWheelGroup" transform="translate(740, 465)">
<!-- Outer Tire -->
<circle cx="0" cy="0" r="76" fill="url(#tireGrad)" />
<!-- Rim -->
<circle cx="0" cy="0" r="66" fill="none" stroke="#e2e8f0" stroke-width="7" />
<circle cx="0" cy="0" r="62" fill="none" stroke="#64748b" stroke-width="1.5" />
<!-- Spokes -->
<g id="frontSpokes" stroke="#cbd5e1" stroke-width="1.5" opacity="0.85"></g>
<!-- Orange Wheel Reflector -->
<rect x="22" y="-6" width="22" height="12" rx="4" fill="#f97316" stroke="#c2410c" stroke-width="1.5" />
<!-- Hub -->
<circle cx="0" cy="0" r="13" fill="#475569" />
<circle cx="0" cy="0" r="7" fill="#f8fafc" />
</g>
<!-- ================= LAYER 5: PELICAN CHARACTER BODY ================= -->
<g id="pelicanRig" transform="translate(0, 0)">
<!-- Tail Feathers (Wiggling in wind) -->
<g id="pelicanTail" transform="translate(450, 310)">
<path d="M0,0 C-25,-5 -45,5 -60,-8 C-45,15 -25,18 0,15 Z" fill="#cbd5e1" stroke="#94a3b8" stroke-width="2" />
<path d="M-5,3 C-28,0 -48,15 -65,5 C-48,25 -25,25 -5,12 Z" fill="#f8fafc" stroke="#94a3b8" stroke-width="2" />
<path d="M-10,8 C-30,10 -45,28 -58,22 C-42,32 -22,28 -10,18 Z" fill="#e2e8f0" stroke="#94a3b8" stroke-width="2" />
</g>
<!-- Main Torso / Plump Body -->
<!-- Smooth pear-like avian body with soft feather shading -->
<g id="pelicanTorso">
<path id="torsoBody" d="M465,310 C465,245 525,210 575,230 C615,245 625,300 605,345 C580,395 495,385 465,310 Z" fill="url(#pelicanFeather)" stroke="#cbd5e1" stroke-width="3" filter="url(#shadow)" />
<!-- Soft White Chest Fluff Highlights -->
<path d="M535,235 C585,255 600,295 590,340 C565,375 515,370 495,335 C520,335 545,310 535,235 Z" fill="#ffffff" opacity="0.75" />
</g>
<!-- Red Polka-Dot Scarf Neck-wrap -->
<g id="scarfNeck">
<path d="M565,220 C585,215 615,225 615,245 C605,255 570,250 560,235 Z" fill="#ef4444" stroke="#b91c1c" stroke-width="2" />
<circle cx="580" cy="227" r="2.5" fill="#ffffff" />
<circle cx="598" cy="232" r="2.5" fill="#ffffff" />
<circle cx="572" cy="242" r="2" fill="#ffffff" />
</g>
<!-- Scarf Tails Fluttering in the Slipstream -->
<g id="scarfTailGroup" transform="translate(562, 230)">
<path id="scarfRibbon1" d="M0,0 C-25,-10 -55,5 -85,-12 C-60,10 -30,5 0,10 Z" fill="#ef4444" stroke="#b91c1c" stroke-width="2" />
<circle cx="-30" cy="-2" r="3" fill="#ffffff" />
<circle cx="-60" cy="-6" r="3" fill="#ffffff" />
<path id="scarfRibbon2" d="M2,8 C-20,5 -45,25 -75,15 C-50,30 -25,20 2,16 Z" fill="#dc2626" stroke="#b91c1c" stroke-width="2" />
<circle cx="-25" cy="14" r="2.5" fill="#ffffff" />
<circle cx="-52" cy="20" r="2.5" fill="#ffffff" />
</g>
<!-- Pelican Neck & Head Sub-Assembly -->
<g id="headNeckGroup" transform="translate(0, 0)">
<!-- S-curved Graceful Pelican Neck -->
<path id="neckPath" d="M575,225 C580,180 595,150 620,135 C640,125 650,140 645,165 C635,210 610,240 590,245 Z" fill="url(#pelicanFeather)" stroke="#cbd5e1" stroke-width="3" />
<!-- Head Crest Feathers (Wind sway) -->
<g id="headCrest" transform="translate(615, 128)">
<path d="M0,0 C-15,-12 -35,-8 -50,-20 C-35,-2 -20,2 0,6 Z" fill="#ffffff" stroke="#cbd5e1" stroke-width="2" />
<path d="M-2,6 C-18,-2 -38,5 -52,-5 C-38,10 -18,10 -2,12 Z" fill="#f1f5f9" stroke="#cbd5e1" stroke-width="2" />
</g>
<!-- Head Base -->
<circle cx="630" cy="142" r="26" fill="url(#pelicanFeather)" stroke="#cbd5e1" stroke-width="2.5" />
<!-- Giant Throat Pouch (Gular Sac) - Stretchy & Wobbly! -->
<!-- Starts from beak tip, bows deep down, reconnects to lower neck -->
<g id="pouchContainer">
<path id="pouchPath" d="M640,162 Q715,178 775,185 C750,230 680,265 620,225 C615,200 625,175 640,162 Z" fill="url(#pouchGrad)" stroke="#f43f5e" stroke-width="2.5" filter="url(#shadow)" />
<!-- Pouch Elastic Texture Lines -->
<path id="pouchLines" d="M660,175 Q685,215 645,228 M690,180 Q715,220 675,238 M725,184 Q745,210 710,235" fill="none" stroke="#fb7185" stroke-width="1.5" stroke-linecap="round" opacity="0.6" />
<!-- Secret Wiggling Fish Tail poking out of pouch occasionally! -->
<g id="pouchSecretFish" transform="translate(670, 185) rotate(-15)" opacity="0.85">
<!-- Translucent belly glow inside pouch -->
<ellipse cx="10" cy="15" rx="18" ry="10" fill="rgba(56, 189, 248, 0.4)" />
</g>
</g>
<!-- Top Beak (Massive, Hooked & Powerful) -->
<g id="topBeak">
<!-- Upper Bill Bone -->
<path d="M632,138 C675,142 735,160 782,180 C788,183 790,188 785,190 C780,192 770,187 755,183 C700,170 650,160 635,160 Z" fill="url(#beakGrad)" stroke="#c2410c" stroke-width="2.5" />
<!-- Beak Hook Tip -->
<path d="M782,180 Q792,185 788,193 Q781,195 780,187 Z" fill="#ea580c" stroke="#9a3412" stroke-width="1.5" />
<!-- Beak Ridge Highlight -->
<path d="M642,142 C685,147 740,165 778,181" fill="none" stroke="#fef08a" stroke-width="2" stroke-linecap="round" />
<!-- Nostril slit -->
<line x1="655" y1="147" x2="668" y2="150" stroke="#9a3412" stroke-width="2" stroke-linecap="round" />
</g>
<!-- Cute Pelican Eye with Blinking Animation -->
<g id="eyeGroup" transform="translate(635, 140)">
<!-- Eye Socket -->
<ellipse cx="0" cy="0" rx="9" ry="11" fill="#fef08a" stroke="#ca8a04" stroke-width="1.5" />
<!-- Eye Pupil -->
<ellipse id="eyePupil" cx="2" cy="0" rx="5.5" ry="7" fill="#0f172a" />
<!-- Cute Specular Catchlights -->
<circle cx="4" cy="-3" r="2.2" fill="#ffffff" />
<circle cx="1" cy="2" r="1.2" fill="#ffffff" />
<!-- Eyelid for Blinking -->
<path id="eyelid" d="M-9,-11 Q0,-11 9,-11 L9,-11 Q0,-11 -9,-11 Z" fill="#cbd5e1" stroke="#94a3b8" stroke-width="1.5" />
</g>
<!-- Stylish Accessory: Aviator Sunglasses (Toggleable) -->
<g id="sunglasses" transform="translate(642, 138)" opacity="0" style="transition: opacity 0.3s;">
<path d="M-18,-6 L12,-4 L14,6 C12,14 0,16 -4,8 C-7,16 -18,14 -19,4 Z" fill="#1e1b4b" stroke="#f59e0b" stroke-width="2" />
<path d="M-15,-2 L-6,6" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" opacity="0.8" />
<path d="M-2,-1 L8,7" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" opacity="0.8" />
</g>
<!-- Stylish Accessory: Vintage French Cycling Cap (Toggleable) -->
<g id="cyclingCap" transform="translate(628, 124)" opacity="0" style="transition: opacity 0.3s;">
<path d="M-22,6 C-20,-18 10,-22 25,-4 C15,0 -5,4 -22,6 Z" fill="#2563eb" stroke="#1d4ed8" stroke-width="2" />
<path d="M-2,-16 L5,-16 L5,0 L-2,0 Z" fill="#ffffff" />
<path d="M5,-16 L12,-16 L12,0 L5,0 Z" fill="#dc2626" />
<!-- Cap Visor flipped up -->
<path d="M18,-5 C28,-14 38,-12 36,-3 C30,2 22,0 18,-5 Z" fill="#1e40af" stroke="#1d4ed8" stroke-width="1.5" />
</g>
</g>
<!-- Left Wing (Reaching forward & holding handlebar grip!) -->
<g id="pelicanWingGroup">
<!-- Shoulder joint at (545, 275), Elbow bobs, Wingtips wrap on handlebar at (660, 250) -->
<path id="wingPath" d="M530,265 C540,295 570,335 605,325 C635,315 655,275 665,252 C652,256 638,270 622,280 C600,295 575,290 545,260 Z" fill="url(#pelicanWing)" stroke="#cbd5e1" stroke-width="3" filter="url(#shadow)" />
<!-- Feather Layer Details on Wing -->
<path d="M560,285 C585,310 615,305 640,270" fill="none" stroke="#94a3b8" stroke-width="2" stroke-linecap="round" />
<path d="M575,298 C595,318 620,312 638,285" fill="none" stroke="#94a3b8" stroke-width="2" stroke-linecap="round" />
<!-- Wingtip "fingers" grasping handlebar -->
<path d="M660,248 C668,245 672,253 664,258 C660,260 655,255 660,248 Z" fill="#f8fafc" stroke="#94a3b8" stroke-width="1.5" />
</g>
</g>
<!-- ================= LAYER 6: FRONT CRANK & FRONT LEG ================= -->
<g id="frontLegGroup">
<!-- Front Crank Arm -->
<line id="crankFront" x1="580" y1="465" x2="605" y2="485" stroke="#94a3b8" stroke-width="10" stroke-linecap="round" />
<circle cx="580" cy="465" r="9" fill="#e2e8f0" stroke="#475569" stroke-width="2" />
<!-- Front Pedal -->
<rect id="pedalFront" x="593" y="480" width="26" height="12" rx="3" fill="#334155" stroke="#f8fafc" stroke-width="2" />
<!-- Pedal Reflector -->
<rect id="pedalReflector" x="598" y="483" width="16" height="4" rx="1" fill="#facc15" />
<!-- Front Leg (Vibrant Orange, 2-Segment IK: Hip -> Knee -> Pedal) -->
<path id="frontLegPath" d="" fill="url(#legFront)" stroke="#9a3412" stroke-width="2.5" stroke-linejoin="round" filter="url(#shadow)" />
<!-- Front Webbed Foot wrapping pedal -->
<path id="frontFootPath" d="" fill="#f97316" stroke="#9a3412" stroke-width="2" />
</g>
</g>
</svg>
</div>
<!-- Interactive Controls Bar -->
<div class="controls-panel">
<!-- Speed & Motion Card -->
<div class="card">
<div class="card-title">
<span>🚀</span> 速度与骑行节奏 (Speed Mode)
</div>
<div class="slider-container">
<span style="font-size: 16px;">🐢</span>
<input type="range" id="speedSlider" min="0" max="80" value="28" step="1">
<span style="font-size: 16px;">⚡</span>
</div>
<div class="btn-group">
<button id="btnSlow">漫步 (15km/h)</button>
<button id="btnNormal" class="active">巡航 (28km/h)</button>
<button id="btnFast">冲刺 (50km/h)</button>
<button id="btnTurbo" style="background: linear-gradient(135deg, #ef4444, #f97316); color: white;">🔥 氮气狂飙!</button>
</div>
</div>
<!-- Action & Sound Card -->
<div class="card">
<div class="card-title">
<span>🎮</span> 互动与特效 (Actions & Sounds)
</div>
<div class="btn-group">
<button id="btnBell" class="action-btn">
<span>🔔</span> 车铃叮咚 (Ding!)
</button>
<button id="btnSquawk" class="action-btn secondary">
<span>🦆</span> 鹈鹕大叫 (Squawk!)
</button>
</div>
<div class="btn-group">
<button id="btnJump" class="action-btn green">
<span>🐇</span> 兔跳飞跃 (Bunny Hop)
</button>
<button id="btnFeedFish">
<span>🐟</span> 投喂小鱼 (+10)
</button>
</div>
</div>
<!-- Theme & Accessories Card -->
<div class="card">
<div class="card-title">
<span>🎨</span> 场景与服饰 (Themes & Style)
</div>
<div class="btn-group">
<button id="themeDay" class="active">☀️ 晴空万里</button>
<button id="themeSunset">🌅 绝美落日</button>
<button id="themeNight">🌙 星光之夜</button>
<button id="themeSynth">🌆 赛博霓虹</button>
</div>
<div class="btn-group" style="margin-top: 4px;">
<button id="btnToggleShades">🕶️ 酷炫墨镜</button>
<button id="btnToggleCap">🧢 骑行小帽</button>
<button id="btnToggleSound">🔊 音效开启</button>
<button id="btnPause">⏸️ 暂停/继续</button>
</div>
</div>
</div>
<!-- Instructions / Tips -->
<div class="instructions">
💡 提示:点击画面中任意位置可投喂小鱼,点击车铃 <b>🔔</b> 可触发清脆铃声,点击鹈鹕大嘴 <b>🦆</b> 可触发物理弹性晃动!
</div>
</div>
<!-- ================= JAVASCRIPT ANIMATION & PHYSICS ENGINE ================= -->
<script>
(function() {
'use strict';
// ================= STATE & CONFIG =================
const state = {
speed: 28, // km/h
targetSpeed: 28,
distance: 0,
fishCount: 0,
isPaused: false,
soundEnabled: true,
isTurbo: false,
theme: 'day',
hasShades: false,
hasCap: false,
// Jump / Physics state
jumpY: 0,
jumpVel: 0,
isJumping: false,
// Pouch physics (mass-spring-damper)
pouchWobble: 0,
pouchVel: 0,
// Cadence angle (radians)
pedalAngle: 0,
wheelAngle: 0,
// Scenery positions
roadOffset: 0,
hillsOffset: 0,
mountOffset: 0,
cloudsFarOffset: 0,
cloudsMidOffset: 0,
propsOffset: 0,
// Head / eye blink
blinkProgress: 0,
isBlinking: false
};
// Rig Anchors & Geometry
const RIG = {
// Crank center
cx: 580,
cy: 465,
crankRadius: 32,
// Pelvis / Hip anchor
hipX: 520,
hipY: 340,
// Leg segment lengths
thighLen: 86,
shinLen: 84,
// Wheels
rearWheelX: 440,
frontWheelX: 740,
wheelY: 465,
wheelRadius: 76
};
// ================= DOM ELEMENTS =================
const scene = document.getElementById('scene');
const canvasWrapper = document.getElementById('canvasWrapper');
const speedVal = document.getElementById('speedVal');
const distVal = document.getElementById('distVal');
const fishVal = document.getElementById('fishVal');
const speedSlider = document.getElementById('speedSlider');
const speedLines = document.getElementById('speedLines');
const turboFlamesGroup = document.getElementById('turboFlamesGroup');
const lightBeam = document.getElementById('lightBeam');
// Rig Elements
const rigRoot = document.getElementById('rigRoot');
const pelicanRig = document.getElementById('pelicanRig');
const frontLegPath = document.getElementById('frontLegPath');
const frontFootPath = document.getElementById('frontFootPath');
const backLegPath = document.getElementById('backLegPath');
const backFootPath = document.getElementById('backFootPath');
const crankFront = document.getElementById('crankFront');
const crankBack = document.getElementById('crankBack');
const pedalFront = document.getElementById('pedalFront');
const pedalBack = document.getElementById('pedalBack');
const pedalReflector = document.getElementById('pedalReflector');
const rearSpokes = document.getElementById('rearSpokes');
const frontSpokes = document.getElementById('frontSpokes');
const pouchPath = document.getElementById('pouchPath');
const pouchLines = document.getElementById('pouchLines');
const eyelid = document.getElementById('eyelid');
const headNeckGroup = document.getElementById('headNeckGroup');
const pelicanTail = document.getElementById('pelicanTail');
const wingPath = document.getElementById('wingPath');
const scarfRibbon1 = document.getElementById('scarfRibbon1');
const scarfRibbon2 = document.getElementById('scarfRibbon2');
const ribbon1 = document.getElementById('ribbon1');
const ribbon2 = document.getElementById('ribbon2');
const ribbon3 = document.getElementById('ribbon3');
const basketFish = document.getElementById('basketFish');
const bellRings = document.getElementById('bellRings');
const chainPath = document.getElementById('chainPath');
// Background Groups
const cloudsFar = document.getElementById('cloudsFar');
const cloudsMid = document.getElementById('cloudsMid');
const mountainsFar = document.getElementById('mountainsFar');
const hillsMid = document.getElementById('hillsMid');
const sceneryProps = document.getElementById('sceneryProps');
const roadDashes = document.getElementById('roadDashes');
const roadsideFlora = document.getElementById('roadsideFlora');
const starsGroup = document.getElementById('starsGroup');
const sunElement = document.getElementById('sunElement');
const moonElement = document.getElementById('moonElement');
const sunRays = document.getElementById('sunRays');
const dustParticles = document.getElementById('dustParticles');
// ================= PROCEDURAL ASSET GENERATION =================
// 1. Build Wheel Spokes
function createSpokes(group) {
group.innerHTML = '';
const numSpokes = 16;
for (let i = 0; i < numSpokes; i++) {
const angle = (i * 360) / numSpokes;
const rad = (angle * Math.PI) / 180;
const x2 = Math.cos(rad) * 62;
const y2 = Math.sin(rad) * 62;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', '0');
line.setAttribute('y1', '0');
line.setAttribute('x2', x2.toFixed(1));
line.setAttribute('y2', y2.toFixed(1));
group.appendChild(line);
}
}
createSpokes(rearSpokes);
createSpokes(frontSpokes);
// 2. Build Road Dashes
function initRoad() {
roadDashes.innerHTML = '';
for (let i = -100; i < 1400; i += 120) {
const dash = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
dash.setAttribute('x', i);
dash.setAttribute('y', '590');
dash.setAttribute('width', '60');
dash.setAttribute('height', '8');
dash.setAttribute('rx', '4');
dash.setAttribute('fill', 'var(--road-line)');
dash.setAttribute('opacity', '0.9');
roadDashes.appendChild(dash);
}
}
initRoad();
// 3. Build Far Mountains
function initMountains() {
mountainsFar.innerHTML = '';
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
let d = 'M-100,520 ';
for (let x = -100; x <= 2600; x += 150) {
const peakY = 220 + Math.sin(x * 0.008) * 80 + Math.cos(x * 0.02) * 40;
d += `L${x},${peakY} `;
}
d += 'L2600,520 Z';
path.setAttribute('d', d);
path.setAttribute('fill', 'url(#mountFarGrad)');
mountainsFar.appendChild(path);
}
initMountains();
// 4. Build Mid Hills & Windmills
function initHills() {
hillsMid.innerHTML = '';
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
let d = 'M-100,520 ';
for (let x = -100; x <= 2600; x += 100) {
const hillY = 360 + Math.sin(x * 0.006) * 50 + Math.sin(x * 0.015) * 30;
d += `Q${x + 50},${hillY - 30} ${x + 100},${hillY} `;
}
d += 'L2600,520 Z';
path.setAttribute('d', d);
path.setAttribute('fill', 'url(#mountMidGrad)');
hillsMid.appendChild(path);
// Add some distant pine trees & beach houses on hills
for (let x = 50; x < 2500; x += 220) {
const y = 370 + Math.sin(x * 0.006) * 40;
const tree = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
tree.setAttribute('points', `${x},${y} ${x - 14},${y + 40} ${x + 14},${y + 40}`);
tree.setAttribute('fill', '#047857');
tree.setAttribute('opacity', '0.85');
hillsMid.appendChild(tree);
}
}
initHills();
// 5. Build Procedural Clouds
function createCloud(x, y, scale, opacity) {
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
g.setAttribute('transform', `translate(${x}, ${y}) scale(${scale})`);
g.setAttribute('opacity', opacity);
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M20,40 Q0,40 0,25 Q0,10 15,10 Q25,-5 45,0 Q60,-10 75,5 Q90,5 90,25 Q90,40 70,40 Z');
path.setAttribute('fill', '#ffffff');
g.appendChild(path);
return g;
}
function initClouds() {
cloudsFar.innerHTML = '';
cloudsMid.innerHTML = '';
for (let i = 0; i < 8; i++) {
const x = i * 320;
const y = 50 + (i % 3) * 35;
cloudsFar.appendChild(createCloud(x, y, 1.2 + (i % 2) * 0.4, 0.45));
}
for (let i = 0; i < 6; i++) {
const x = i * 420 + 80;
const y = 90 + (i % 2) * 45;
cloudsMid.appendChild(createCloud(x, y, 1.6 + (i % 3) * 0.3, 0.75));
}
}
initClouds();
// 6. Build Stars for Night Mode
function initStars() {
starsGroup.innerHTML = '';
for (let i = 0; i < 65; i++) {
const star = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
star.setAttribute('cx', (Math.random() * 1200).toFixed(0));
star.setAttribute('cy', (Math.random() * 320).toFixed(0));
star.setAttribute('r', (Math.random() * 1.8 + 0.6).toFixed(1));
star.setAttribute('fill', '#ffffff');
star.setAttribute('opacity', (Math.random() * 0.8 + 0.2).toFixed(2));
starsGroup.appendChild(star);
}
}
initStars();
// 7. Build Coastal Scenery Props (Lighthouse, Palm Trees, Signs)
function initProps() {
sceneryProps.innerHTML = '';
// Lighthouse
const lh = document.createElementNS('http://www.w3.org/2000/svg', 'g');
lh.setAttribute('transform', 'translate(850, 360)');
lh.innerHTML = `
<polygon points="-15,160 15,160 10,20 -10,20" fill="#f8fafc" stroke="#e2e8f0" stroke-width="2"/>
<polygon points="-13,115 13,115 11,80 -11,80" fill="#ef4444"/>
<rect x="-14" y="10" width="28" height="12" fill="#334155" rx="2"/>
<circle cx="0" cy="5" r="10" fill="#fef08a" opacity="0.8"/>
<polygon points="-12,0 12,0 0,-15" fill="#ef4444"/>
`;
sceneryProps.appendChild(lh);
// Palm Trees
for (let i = 0; i < 4; i++) {
const pt = document.createElementNS('http://www.w3.org/2000/svg', 'g');
const px = 200 + i * 550;
pt.setAttribute('transform', `translate(${px}, 420)`);
pt.innerHTML = `
<path d="M0,100 Q15,40 5,0" fill="none" stroke="#78350f" stroke-width="8" stroke-linecap="round"/>
<path d="M5,0 Q-30,-20 -50,-5 M5,0 Q30,-25 55,-10 M5,0 Q-20,-45 -40,-35 M5,0 Q25,-45 45,-30 M5,0 Q0,-55 0,-40" fill="none" stroke="#15803d" stroke-width="5" stroke-linecap="round"/>
`;
sceneryProps.appendChild(pt);
}
}
initProps();
// 8. Build Roadside Flora & Grass Tufts
function initRoadside() {
roadsideFlora.innerHTML = '';
for (let x = -50; x < 1300; x += 70) {
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
g.setAttribute('transform', `translate(${x}, 538)`);
// Grass tuft
g.innerHTML = `
<path d="M0,0 Q-4,-12 -8,-15 M0,0 Q2,-16 3,-18 M0,0 Q6,-10 10,-12" fill="none" stroke="#22c55e" stroke-width="2.5" stroke-linecap="round"/>
${(x % 140 === 0) ? `<circle cx="3" cy="-19" r="3" fill="#f43f5e" /><circle cx="3" cy="-19" r="1.2" fill="#fef08a" />` : ''}
`;
roadsideFlora.appendChild(g);
}
}
initRoadside();
// ================= INVERSE KINEMATICS (IK) SOLVER =================
/**
* Solves 2-joint planar Inverse Kinematics for bird leg pedaling
* @param {number} hx - Hip X
* @param {number} hy - Hip Y
* @param {number} px - Pedal X
* @param {number} py - Pedal Y
* @param {number} L1 - Thigh length
* @param {number} L2 - Shin length
* @param {boolean} kneeForward - Knee bending orientation
* @returns {{kx: number, ky: number, angle1: number, angle2: number}}
*/
function solveIK(hx, hy, px, py, L1, L2, kneeForward = true) {
const dx = px - hx;
const dy = py - hy;
let d = Math.sqrt(dx * dx + dy * dy);
// Clamp distance to avoid triangle inequality breakdown
const maxReach = L1 + L2 - 0.5;
const minReach = Math.abs(L1 - L2) + 0.5;
d = Math.max(minReach, Math.min(maxReach, d));
const baseAngle = Math.atan2(dy, dx);
// Law of cosines for angle at hip
const cosHip = (L1 * L1 + d * d - L2 * L2) / (2 * L1 * d);
const hipOffset = Math.acos(Math.max(-1, Math.min(1, cosHip)));
// For cyclist pedaling forward, knee pushes out toward the front of the bike
const thighAngle = kneeForward ? (baseAngle - hipOffset) : (baseAngle + hipOffset);
const kx = hx + Math.cos(thighAngle) * L1;
const ky = hy + Math.sin(thighAngle) * L1;
const shinAngle = Math.atan2(py - ky, px - kx);
return { kx, ky, thighAngle, shinAngle };
}
// ================= AUDIO SYNTHESIS ENGINE (Web Audio API) =================
let audioCtx = null;
function getAudioContext() {
if (!audioCtx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) {
audioCtx = new AudioContext();
}
}
if (audioCtx && audioCtx.state === 'suspended') {
audioCtx.resume();
}
return audioCtx;
}
// 1. Bicycle Bell ("Ding-Ding!")
function playBellSound() {
if (!state.soundEnabled) return;
const ctx = getAudioContext();
if (!ctx) return;
const now = ctx.currentTime;
[0, 0.12].forEach((offsetDelay, idx) => {
const strikeTime = now + offsetDelay;
const osc1 = ctx.createOscillator();
const osc2 = ctx.createOscillator();
const gain = ctx.createGain();
osc1.type = 'sine';
osc1.frequency.setValueAtTime(idx === 0 ? 2093 : 2349, strikeTime); // C7 / D7
osc2.type = 'triangle';
osc2.frequency.setValueAtTime(idx === 0 ? 4186 : 4698, strikeTime); // overtone
gain.gain.setValueAtTime(0.35, strikeTime);
gain.gain.exponentialRampToValueAtTime(0.001, strikeTime + 0.8);
osc1.connect(gain);
osc2.connect(gain);
gain.connect(ctx.destination);
osc1.start(strikeTime);
osc2.start(strikeTime);
osc1.stop(strikeTime + 0.85);
osc2.stop(strikeTime + 0.85);
});
// Trigger visual bell ring wave
bellRings.setAttribute('opacity', '1');
setTimeout(() => bellRings.setAttribute('opacity', '0'), 400);
}
// 2. Pelican Cartoon Squawk / Quack
function playSquawkSound() {
if (!state.soundEnabled) return;
const ctx = getAudioContext();
if (!ctx) return;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const mod = ctx.createOscillator();
const modGain = ctx.createGain();
const mainGain = ctx.createGain();
const filter = ctx.createBiquadFilter();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(320, now);
osc.frequency.exponentialRampToValueAtTime(180, now + 0.25);
osc.frequency.exponentialRampToValueAtTime(140, now + 0.45);
// FM modulation for funny cartoon vibrato/honk
mod.type = 'sine';
mod.frequency.setValueAtTime(28, now);
modGain.gain.setValueAtTime(60, now);
mod.connect(osc.frequency);
filter.type = 'bandpass';
filter.frequency.setValueAtTime(800, now);
filter.Q.setValueAtTime(3, now);
mainGain.gain.setValueAtTime(0.28, now);
mainGain.gain.exponentialRampToValueAtTime(0.01, now + 0.45);
osc.connect(filter);
filter.connect(mainGain);
mainGain.connect(ctx.destination);
mod.start(now);
osc.start(now);
mod.stop(now + 0.5);
osc.stop(now + 0.5);
// Physical pouch wobble trigger
state.pouchVel += 35;
}
// 3. Jump / Whoosh Sound
function playJumpSound() {
if (!state.soundEnabled) return;
const ctx = getAudioContext();
if (!ctx) return;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(150, now);
osc.frequency.exponentialRampToValueAtTime(550, now + 0.2);
gain.gain.setValueAtTime(0.3, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.35);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.35);
}
// 4. Fish Catch "Pop/Bubble"
function playPopSound() {
if (!state.soundEnabled) return;
const ctx = getAudioContext();
if (!ctx) return;
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(400, now);
osc.frequency.exponentialRampToValueAtTime(900, now + 0.1);
gain.gain.setValueAtTime(0.3, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.15);
}
// ================= INTERACTION POPUPS & EFFECTS =================
function triggerBubbleEffect(x, y, text) {
const bubble = document.createElement('div');
bubble.className = 'bubble-fx';
bubble.textContent = text;
bubble.style.left = `${x}px`;
bubble.style.top = `${y}px`;
canvasWrapper.appendChild(bubble);
setTimeout(() => bubble.remove(), 1200);
}
function feedFish() {
state.fishCount += 1;
fishVal.textContent = `${state.fishCount} 条`;
state.pouchVel += 25;
playPopSound();
// Visual text popup
triggerBubbleEffect(window.innerWidth > 768 ? 680 : 300, 260, '🐟 咕噜! +1 鱼干');
}
function triggerJump() {
if (state.isJumping) return;
state.isJumping = true;
state.jumpVel = -14;
playJumpSound();
triggerBubbleEffect(window.innerWidth > 768 ? 580 : 250, 280, '🐇 飞跃跳跃!');
}
// ================= MAIN ANIMATION & PHYSICS LOOP =================
let lastTime = performance.now();
function animate(currentTime) {
requestAnimationFrame(animate);
const dt = Math.min((currentTime - lastTime) / 1000, 0.05); // Cap delta time
lastTime = currentTime;
if (state.isPaused) return;
// Smooth speed interpolation
state.speed += (state.targetSpeed - state.speed) * 0.08;
speedVal.textContent = `${Math.round(state.speed)} km/h`;
// Update distance
state.distance += (state.speed * dt) / 3600;
distVal.textContent = `${state.distance.toFixed(2)} km`;
// Wheel & Pedal Cadence Calculation
// Circumference = 2 * PI * R_wheel = ~477 px
// Speed in px/sec
const pxSpeed = state.speed * 18;
const wheelAngularVel = pxSpeed / RIG.wheelRadius;
// Gear ratio approx 2.2:1
const pedalAngularVel = wheelAngularVel / 2.2;
state.wheelAngle += wheelAngularVel * dt;
state.pedalAngle += pedalAngularVel * dt;
// 1. Rotate Wheel Spokes & Reflectors
const wheelDeg = (state.wheelAngle * 180) / Math.PI;
rearSpokes.setAttribute('transform', `rotate(${wheelDeg.toFixed(1)})`);
frontSpokes.setAttribute('transform', `rotate(${wheelDeg.toFixed(1)})`);
</html>
最新回复 (3)
-
lkjsdf 08-14 18:051楼这是谁的部将这么勇猛,看起来有点genmini的方式风格
-
lueluelue 08-14 18:112楼

-
诡道疯人 08-14 18:313楼不会是新的GPT?之前有消息快发布了,已经拿去给美国政府审核了
* 帖子来源Linux.do
附近帖子
- ↑IOS端tg客户端的选择
- ↑WINDOWS版本的Codex巨卡无比,鼠标指针卡顿到PPT一样
- ↑业余开发Grok4.6低价渠道哪里找?
- ↑做了个 DSH 插件商店
- ↑准备给家里长辈买红酒(6瓶),佬有没有推荐的品牌和具体款?
- 📍 Arena ai抽卡抽出来了带交互的骑车鹈鹕太逆天了!佬友们看看是什么模型?
- ↓给v4pro挽尊一下,实测tiktokweb纯算逆向
- ↓我宣布Tibo是token世界的神!
- ↓脱裤子放屁系列:我用DeepSeek-Harness给DeepSeek-Harness写了一个桌面应用
- ↓基于Codex的重置有感,可能发现了大秘密
- ↓求推荐好喝的奶茶