鹈鹕

代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>海滨漫游:骑自行车的鹈鹕 | Pelican on a Bicycle</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--anim-speed: 1;
}

body {
font-family: 'Fredoka', 'Noto Sans SC', sans-serif;
user-select: none;
overflow-x: hidden;
}

/ 车轮与曲柄旋转动画 /
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

/ 脚踏板反向旋转,保持踏板水平 /
@keyframes counter-spin {
from { transform: rotate(0deg); }
to { transform: rotate(-360deg); }
}

/ 身体与车架轻微颠簸 /
@keyframes body-bob {
0%, 100% { transform: translateY(0px) rotate(0deg); }
25% { transform: translateY(-5px) rotate(-0.8deg); }
50% { transform: translateY(1px) rotate(0.4deg); }
75% { transform: translateY(-4px) rotate(-0.4deg); }
}

/ 头部与大嘴喉囊弹性晃动(带相位差) /
@keyframes head-bob {
0%, 100% { transform: rotate(0deg) translateY(0); }
25% { transform: rotate(2.5deg) translateY(-2px); }
50% { transform: rotate(-1.5deg) translateY(2px); }
75% { transform: rotate(2deg) translateY(-1px); }
}

@keyframes pouch-jiggle {
0%, 100% { transform: scaleY(1) rotate(0deg); }
30% { transform: scaleY(1.05) rotate(1.8deg); }
60% { transform: scaleY(0.96) rotate(-1.2deg); }
85% { transform: scaleY(1.03) rotate(1deg); }
}

/ 喉囊里的小鱼探头 /
@keyframes fish-bounce {
0%, 100% { transform: translateY(0) rotate(-12deg); }
35% { transform: translateY(-14px) rotate(8deg); }
65% { transform: translateY(3px) rotate(-18deg); }
}

/ 围巾与尾羽随风飘动 /
@keyframes scarf-wave {
0%, 100% { transform: rotate(0deg) skewX(0deg); }
50% { transform: rotate(10deg) skewX(-8deg) scaleY(0.95); }
}

@keyframes tail-wag {
0%, 100% { transform: rotate(0deg); }
50% { transform: rotate(-7deg); }
}

/ 水手帽轻微跳动 /
@keyframes hat-hop {
0%, 100% { transform: translateY(0) rotate(0deg); }
25% { transform: translateY(-6px) rotate(-5deg); }
55% { transform: translateY(0) rotate(2deg); }
}

/ 背景地面滚动 /
@keyframes road-scroll {
from { stroke-dashoffset: 0; }
to { stroke-dashoffset: -240; }
}

/ 云朵缓慢移动 /
@keyframes cloud-drift-slow {
from { transform: translateX(1050px); }
to { transform: translateX(-300px); }
}

/ 海鸥扇动翅膀 /
@keyframes gull-flap {
0%, 100% { transform: translateY(0) scaleY(1); }
50% { transform: translateY(-8px) scaleY(0.6); }
}

/ 地面阴影呼吸缩放 /
@keyframes shadow-pulse {
0%, 100% { transform: scaleX(1); opacity: 0.25; }
25%, 75% { transform: scaleX(0.95); opacity: 0.18; }
}

/ 铃铛摇晃 /
@keyframes bell-ring {
0%, 100% { transform: rotate(0deg); }
20% { transform: rotate(18deg); }
40% { transform: rotate(-18deg); }
60% { transform: rotate(14deg); }
80% { transform: rotate(-14deg); }
}

/ 应用动画类 /
.anim-wheel {
animation: spin calc(1.3s / var(--anim-speed)) linear infinite;
transform-box: fill-box;
transform-origin: center;
}

.anim-body {
animation: body-bob calc(1.3s / var(--anim-speed)) ease-in-out infinite;
}

.anim-head {
animation: head-bob calc(1.3s / var(--anim-speed)) ease-in-out infinite;
transform-origin: 485px 210px;
}

.anim-pouch {
animation: pouch-jiggle calc(1.3s / var(--anim-speed)) ease-in-out infinite;
transform-origin: 515px 195px;
}

.anim-fish {
animation: fish-bounce calc(1.3s / var(--anim-speed)) ease-in-out infinite;
transform-origin: 575px 225px;
}

.anim-scarf {
animation: scarf-wave calc(0.65s / var(--anim-speed)) ease-in-out infinite;
transform-origin: 465px 220px;
}

.anim-tail {
animation: tail-wag calc(0.65s / var(--anim-speed)) ease-in-out infinite;
transform-origin: 335px 315px;
}

.anim-hat {
animation: hat-hop calc(1.3s / var(--anim-speed)) ease-in-out infinite;
transform-origin: 475px 145px;
}

.anim-road {
animation: road-scroll calc(0.9s / var(--anim-speed)) linear infinite;
}

.anim-cloud-1 {
animation: cloud-drift-slow calc(36s / var(--anim-speed)) linear infinite;
}

.anim-cloud-2 {
animation: cloud-drift-slow calc(50s / var(--anim-speed)) linear infinite;
animation-delay: -22s;
}

.anim-gull {
animation: gull-flap 1.6s ease-in-out infinite;
transform-box: fill-box;
transform-origin: center;
}

.anim-shadow {
animation: shadow-pulse calc(1.3s / var(--anim-speed)) ease-in-out infinite;
transform-origin: 495px 535px;
}

.ringing {
animation: bell-ring 0.35s ease-in-out 2;
transform-origin: 572px 262px;
}

/ 昼夜平滑过渡 /
.scene-transition {
transition: all 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>
</head>
<body class="bg-slate-900 min-h-screen flex flex-col items-center justify-center p-3 sm:p-6 selection:bg-amber-300 selection:text-slate-900">

<!-- 主容器卡片 -->
<div class="w-full max-w-5xl bg-white/95 dark:bg-slate-800/95 backdrop-blur-md rounded-3xl shadow-2xl overflow-hidden border border-slate-200/80 dark:border-slate-700 flex flex-col">

<!-- 顶部标题与状态栏 -->
<header class="px-6 py-4 bg-gradient-to-r from-sky-50 to-amber-50 dark:from-slate-800 dark:to-slate-800 border-b border-slate-200/70 dark:border-slate-700 flex flex-wrap items-center justify-between gap-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-2xl bg-amber-400 flex items-center justify-center text-slate-900 shadow-sm font-bold text-xl">
🚲
</div>
<div>
<h1 class="text-lg sm:text-xl font-bold text-slate-800 dark:text-white tracking-tight flex items-center gap-2">
皮特船长的海滨骑行
<span class="text-xs font-semibold px-2.5 py-0.5 rounded-full bg-sky-100 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300">SVG 运动学动画</span>
</h1>
<p class="text-xs text-slate-500 dark:text-slate-400">Pelican Riding a Vintage Bicycle — 纯矢量绘制与实时逆向运动学脚踏联动</p>
</div>
</div>

<!-- 快捷提示 -->
<div class="flex items-center gap-2 text-xs font-medium text-slate-600 dark:text-slate-300 bg-white/80 dark:bg-slate-700/70 px-3.5 py-2 rounded-xl border border-slate-200/60 dark:border-slate-600 shadow-sm">
<span class="inline-block w-2 h-2 rounded-full bg-emerald-500 animate-ping"></span>
<span>点击画面中的<b>车铃</b>或<b>大嘴</b>可触发互动!</span>
</div>
</header>

<!-- SVG 舞台画布区域 -->
<div class="relative w-full aspect-[16/10] sm:aspect-[16/9] bg-sky-100 overflow-hidden">
<svg id="pelican-stage" viewBox="0 0 1000 600" class="w-full h-full block" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 白天与黄昏天空渐变 -->
<linearGradient id="skyGradDay" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#38bdf8" />
<stop offset="55%" stop-color="#bae6fd" />
<stop offset="100%" stop-color="#fef3c7" />
</linearGradient>
<linearGradient id="skyGradSunset" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#1e1b4b" />
<stop offset="40%" stop-color="#7e22ce" />
<stop offset="75%" stop-color="#f97316" />
<stop offset="100%" stop-color="#fde047" />
</linearGradient>

<!-- 海洋渐变 -->
<linearGradient id="seaGradDay" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#0284c7" />
<stop offset="100%" stop-color="#38bdf8" />
</linearGradient>
<linearGradient id="seaGradSunset" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#311042" />
<stop offset="40%" stop-color="#9a3412" />
<stop offset="100%" stop-color="#f59e0b" />
</linearGradient>

<!-- 鹈鹕喉囊渐变 -->
<linearGradient id="pouchGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#fbbf24" />
<stop offset="50%" stop-color="#f59e0b" />
<stop offset="100%" stop-color="#ea580c" />
</linearGradient>

<!-- 上喙渐变 -->
<linearGradient id="topBeakGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#fde047" />
<stop offset="75%" stop-color="#f59e0b" />
<stop offset="100%" stop-color="#ef4444" />
</linearGradient>

<!-- 鹈鹕身体羽毛渐变 -->
<linearGradient id="featherGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#ffffff" />
<stop offset="70%" stop-color="#f8fafc" />
<stop offset="100%" stop-color="#cbd5e1" />
</linearGradient>

<!-- 车架薄荷绿渐变 -->
<linearGradient id="frameGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#2dd4bf" />
<stop offset="100%" stop-color="#0d9488" />
</linearGradient>

<!-- 轮胎高光阴影滤镜 -->
<filter id="softShadow" x="-15%" y="-15%" width="130%" height="130%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#0f172a" flood-opacity="0.18"/>
</filter>

<!-- 车轮复用组件 (中心在 0,0,半径 82) -->
<g id="bicycle-wheel">
<!-- 外胎 -->
<circle cx="0" cy="0" r="82" fill="none" stroke="#1e293b" stroke-width="14" />
<!-- 复古白边轮胎装饰线 -->
<circle cx="0" cy="0" r="73" fill="none" stroke="#fef3c7" stroke-width="3.5" />
<!-- 铝合金轮圈 -->
<circle cx="0" cy="0" r="68" fill="none" stroke="#cbd5e1" stroke-width="4" />
<!-- 辐条 (8根交叉) -->
<g stroke="#94a3b8" stroke-width="2" stroke-linecap="round">
<line x1="-67" y1="0" x2="67" y2="0" />
<line x1="0" y1="-67" x2="0" y2="67" />
<line x1="-47" y1="-47" x2="47" y2="47" />
<line x1="-47" y1="47" x2="47" y2="-47" />
<line x1="-62" y1="-26" x2="62" y2="26" />
<line x1="-26" y1="-62" x2="26" y2="62" />
<line x1="26" y1="-62" x2="-26" y2="62" />
<line x1="62" y1="-26" x2="-62" y2="26" />
</g>
<!-- 橙色反光片 (体现旋转感) -->
<rect x="42" y="-5" width="12" height="10" rx="3" fill="#f97316" stroke="#fff" stroke-width="1" />
<rect x="-54" y="-5" width="12" height="10" rx="3" fill="#f97316" stroke="#fff" stroke-width="1" />
<!-- 气门嘴 -->
<rect x="-3" y="59" width="6" height="9" rx="2" fill="#475569" />
<!-- 花鼓中心 -->
<circle cx="0" cy="0" r="10" fill="#e2e8f0" stroke="#475569" stroke-width="3" />
</g>

<!-- 云朵复用组件 -->
<g id="fluffy-cloud" fill="#ffffff" opacity="0.88">
<path d="M 0,40 Q -20,40 -20,25 Q -20,10 -5,10 Q 5,-15 35,-10 Q 60,-20 75,5 Q 100,5 100,25 Q 100,40 80,40 Z" />
</g>
</defs>

<!-- ==================== 1. 背景天空与远景 ==================== -->
<rect id="bg-sky" width="1000" height="440" fill="url(#skyGradDay)" class="scene-transition" />

<!-- 夕阳/太阳 -->
<g id="celestial-body" transform="translate(820, 120)" class="scene-transition">
<circle cx="0" cy="0" r="58" fill="#fde047" opacity="0.25" />
<circle cx="0" cy="0" r="44" fill="#fef08a" opacity="0.5" />
<circle cx="0" cy="0" r="34" fill="#facc15" />
</g>

<!-- 飘动的云朵 -->
<g class="anim-cloud-1" transform="translate(0, 65)">
<use href="#fluffy-cloud" transform="scale(1.3)" />
</g>
<g class="anim-cloud-2" transform="translate(0, 115)">
<use href="#fluffy-cloud" transform="scale(0.85)" opacity="0.7" />
</g>

<!-- 远处的海鸥 -->
<g transform="translate(210, 95)">
<path class="anim-gull" d="M -18,0 Q -9,-10 0,0 Q 9,-10 18,0" fill="none" stroke="#475569" stroke-width="2.5" stroke-linecap="round" />
</g>
<g transform="translate(250, 75) scale(0.75)">
<path class="anim-gull" style="animation-delay: -0.6s" d="M -18,0 Q -9,-10 0,0 Q 9,-10 18,0" fill="none" stroke="#475569" stroke-width="2.5" stroke-linecap="round" />
</g>

<!-- 远景海岛与灯塔 -->
<g transform="translate(80, 345)">
<path d="M -100,45 Q 20,-15 160,45 Z" fill="#64748b" opacity="0.35" />
<path d="M 650,45 Q 780,-25 940,45 Z" fill="#64748b" opacity="0.28" />
<!-- 小灯塔 -->
<polygon points="755,15 765,15 768,45 752,45" fill="#f8fafc" />
<polygon points="756,22 764,22 766,30 754,30" fill="#ef4444" />
<rect x="753" y="10" width="14" height="5" rx="1" fill="#1e293b" />
<circle cx="760" cy="8" r="3" fill="#fde047" />
</g>

<!-- 海洋与波浪层 -->
<rect id="bg-sea" x="0" y="385" width="1000" height="85" fill="url(#seaGradDay)" />
<path class="anim-road" d="M -240,408 Q -180,398 -120,408 T 0,408 T 120,408 T 240,408 T 360,408 T 480,408 T 600,408 T 720,408 T 840,408 T 960,408 T 1080,408 T 1200,408" fill="none" stroke="#bae6fd" stroke-width="2.5" stroke-dasharray="40 80" opacity="0.6" />
<path class="anim-road" d="M -240,432 Q -180,422 -120,432 T 0,432 T 120,432 T 240,432 T 360,432 T 480,432 T 600,432 T 720,432 T 840,432 T 960,432 T 1080,432 T 1200,432" fill="none" stroke="#e0f2fe" stroke-width="2" stroke-dasharray="60 60" opacity="0.5" />

<!-- 海滨木栈道栏杆与路面 -->
<rect x="0" y="460" width="1000" height="140" fill="#e2e8f0" />
<rect x="0" y="460" width="1000" height="14" fill="#cbd5e1" />
<!-- 路面滚动线条 (营造前进速度感) -->
<line class="anim-road" x1="0" y1="467" x2="1000" y2="467" stroke="#94a3b8" stroke-width="4" stroke-dasharray="15 105" />
<!-- 柏油自行车道 -->
<rect x="0" y="474" width="1000" height="126" fill="#334155" />
<!-- 车道中心虚线 -->
<line class="anim-road" x1="0" y1="536" x2="1240" y2="536" stroke="#fbbf24" stroke-width="5" stroke-dasharray="60 60" stroke-linecap="round" opacity="0.7" />
<!-- 路面掠过的小石子 -->
<line class="anim-road" x1="0" y1="575" x2="1240" y2="575" stroke="#475569" stroke-width="3" stroke-dasharray="8 232" stroke-linecap="round" />
<line class="anim-road" x1="120" y1="502" x2="1360" y2="502" stroke="#475569" stroke-width="2.5" stroke-dasharray="12 228" stroke-linecap="round" />

<!-- ==================== 2. 自行车地面投影 ==================== -->
<ellipse class="anim-shadow" cx="495" cy="536" rx="235" ry="14" fill="#0f172a" opacity="0.25" />

<!-- ==================== 3. 远端腿部(右腿,位于车架后方) ==================== -->
<!-- 由 JS 逆运动学实时驱动 d 属性与脚掌坐标 -->
<g id="back-leg-layer">
<!-- 右小腿与大腿折线 -->
<path id="back-leg-limb" d="M 410,330 L 470,380 L 475,445" fill="none" stroke="#d97706" stroke-width="11" stroke-linecap="round" stroke-linejoin="round" />
<!-- 右脚蹼 -->
<path id="back-foot" d="M -14,-4 L 22,-4 C 28,-4 30,6 22,8 L -14,8 C -19,8 -19,-4 -14,-4 Z" fill="#d97706" stroke="#b45309" stroke-width="2" />
<!-- 远端踏板 -->
<rect id="back-pedal" x="-16" y="4" width="32" height="7" rx="3" fill="#1e293b" stroke="#475569" stroke-width="1.5" />
</g>

<!-- ==================== 4. 自行车旋转双轮 ==================== -->
<!-- 后轮 (中心: 340, 445) -->
<g transform="translate(340, 445)">
<g class="anim-wheel">
<use href="#bicycle-wheel" />
</g>
</g>

<!-- 前轮 (中心: 650, 445) -->
<g transform="translate(650, 445)">
<g class="anim-wheel">
<use href="#bicycle-wheel" />
</g>
</g>

<!-- ==================== 5. 自行车架与鹈鹕主体(整体颠簸组) ==================== -->
<g class="anim-body" filter="url(#softShadow)">

<!-- 5.1 后轮挡泥板与货架 -->
<path d="M 248,440 A 92,92 0 0,1 420,395" fill="none" stroke="#0f766e" stroke-width="7" stroke-linecap="round" />
<!-- 前轮挡泥板 -->
<path d="M 565,410 A 92,92 0 0,1 735,415" fill="none" stroke="#0f766e" stroke-width="7" stroke-linecap="round" />

<!-- 5.2 链条与护链罩 -->
<g>
<!-- 下链条 -->
<line x1="340" y1="455" x2="475" y2="467" stroke="#475569" stroke-width="3.5" stroke-dasharray="6 4" class="anim-road" />
<!-- 上链条 -->
<line x1="340" y1="435" x2="475" y2="423" stroke="#475569" stroke-width="3.5" stroke-dasharray="6 4" class="anim-road" />
<!-- 复古半包链条盒 -->
<path d="M 338,430 L 478,416 A 29,29 0 0,1 504,445 L 475,450 L 338,448 Z" fill="#14b8a6" stroke="#0f766e" stroke-width="2.5" />
</g>

<!-- 5.3 经典复古自行车架 -->
<g stroke="url(#frameGrad)" stroke-width="11" stroke-linecap="round" stroke-linejoin="round" fill="none">
<!-- 后下叉 (后轴 -> 五通) -->
<line x1="340" y1="445" x2="475" y2="445" />
<!-- 后上叉 (后轴 -> 座管节点) -->
<line x1="340" y1="445" x2="405" y2="345" />
<!-- 立管/座管 (五通 -> 车座下方) -->
<line x1="475" y1="445" x2="400" y2="335" />
<!-- 下管 (五通 -> 头管下端) -->
<path d="M 475,445 Q 535,400 595,330" />
<!-- 优雅弧形上管 (座管 -> 头管上端) -->
<path d="M 405,350 Q 495,365 588,312" />
<!-- 前叉 (头管 -> 前轮轴) -->
<path d="M 592,320 L 635,425 Q 642,440 650,445" />
</g>

<!-- 车架高光装饰细节 -->
<circle cx="340" cy="445" r="7" fill="#f8fafc" stroke="#0f766e" stroke-width="3" />
<circle cx="650" cy="445" r="7" fill="#f8fafc" stroke="#0f766e" stroke-width="3" />

<!-- 车把立管与弯把 -->
<path d="M 588,312 L 574,268 Q 570,256 556,258 L 535,263" fill="none" stroke="#cbd5e1" stroke-width="8" stroke-linecap="round" />
<!-- 车把皮质握把 -->
<rect x="518" y="257" width="26" height="12" rx="6" transform="rotate(-10 531 263)" fill="#92400e" stroke="#78350f" stroke-width="2" />

<!-- 车铃铛 (可点击触发摇晃) -->
<g id="bike-bell" class="cursor-pointer">
<rect x="566" y="255" width="6" height="8" fill="#64748b" />
<path d="M 558,255 A 11,11 0 0,1 580,255 Z" fill="#fbbf24" stroke="#d97706" stroke-width="2" />
<circle cx="569" cy="245" r="2.5" fill="#f59e0b" />
</g>

<!-- 前车柳条篮子与法国长棍面包/鲜花 -->
<g transform="translate(598, 272) rotate(12)">
<!-- 篮子里的向日葵与法棍 -->
<ellipse cx="14" cy="-4" rx="7" ry="18" transform="rotate(-20 14 -4)" fill="#f59e0b" stroke="#b45309" stroke-width="2" />
<circle cx="32" cy="-2" r="9" fill="#fde047" stroke="#eab308" stroke-width="2" />
<circle cx="32" cy="-2" r="4" fill="#78350f" />
<!-- 柳条车筐 -->
<path d="M 0,0 L 44,0 L 38,38 Q 36,44 30,44 L 14,44 Q 8,44 6,38 Z" fill="#d97706" stroke="#92400e" stroke-width="2.5" />
<!-- 编织纹理 -->
<line x1="4" y1="12" x2="40" y2="12" stroke="#92400e" stroke-width="2" stroke-dasharray="6 4" />
<line x1="6" y1="24" x2="38" y2="24" stroke="#92400e" stroke-width="2" stroke-dasharray="6 4" />
<line x1="8" y1="35" x2="36" y2="35" stroke="#92400e" stroke-width="2" stroke-dasharray="6 4" />
<rect x="-3" y="-3" width="50" height="7" rx="3.5" fill="#b45309" />
</g>

<!-- 复古弹簧皮革车座 -->
<g transform="translate(392, 322)">
<!-- 金属座杆 -->
<line x1="8" y1="14" x2="3" y2="0" stroke="#94a3b8" stroke-width="7" />
<!-- 减震弹簧 -->
<circle cx="-12" cy="4" r="5" fill="none" stroke="#64748b" stroke-width="2.5" />
<circle cx="-12" cy="9" r="5" fill="none" stroke="#64748b" stroke-width="2.5" />
<!-- 皮革鞍座 -->
<path d="M -28,-4 Q -10,-10 26,-2 Q 36,0 32,6 Q 10,10 -22,8 Q -34,6 -28,-4 Z" fill="#78350f" stroke="#451a03" stroke-width="2.5" />
<circle cx="-20" cy="2" r="2" fill="#fbbf24" />
<circle cx="-12" cy="3" r="2" fill="#fbbf24" />
</g>

<!-- ==================== 6. 鹈鹕身体、尾羽与翅膀 ==================== -->
<!-- 摇摆的尾羽 -->
<g class="anim-tail">
<path d="M 345,305 L 302,288 Q 292,295 308,308 L 296,306 Q 290,315 312,320 L 348,322 Z" fill="#e2e8f0" stroke="#94a3b8" stroke-width="2.5" stroke-linejoin="round" />
</g>

<!-- 远端翅膀(左翼,扶在车把方向) -->
<path d="M 465,245 Q 505,255 532,262" fill="none" stroke="#cbd5e1" stroke-width="18" stroke-linecap="round" />

<!-- 鹈鹕圆润的身体躯干 -->
<path d="M 342,308 C 335,260 385,220 445,225 C 488,228 505,265 492,308 C 478,348 425,350 385,340 C 355,332 345,322 342,308 Z" fill="url(#featherGrad)" stroke="#94a3b8" stroke-width="3" />

<!-- 胸前蓬松羽毛纹理 -->
<path d="M 465,265 Q 480,275 470,290" fill="none" stroke="#cbd5e1" stroke-width="2.5" stroke-linecap="round" />
<path d="M 452,285 Q 467,295 457,310" fill="none" stroke="#cbd5e1" stroke-width="2.5" stroke-linecap="round" />

<!-- 随风飘扬的红白条纹海魂围巾(后摆) -->
<g class="anim-scarf">
<path d="M 462,222 C 425,215 385,222 355,205 L 348,225 C 385,240 425,235 462,236 Z" fill="#ef4444" stroke="#b91c1c" stroke-width="2" stroke-linejoin="round" />
<!-- 白色条纹 -->
<line x1="425" y1="220" x2="422" y2="235" stroke="#ffffff" stroke-width="6" />
<line x1="395" y1="217" x2="390" y2="233" stroke="#ffffff" stroke-width="6" />
<line x1="368" y1="211" x2="362" y2="228" stroke="#ffffff" stroke-width="6" />
</g>

<!-- ==================== 7. 鹈鹕长脖子、头部与标志性大喉囊 ==================== -->
<g class="anim-head">
<!-- 弯曲优雅的脖颈与圆头 -->
<path d="M 442,238 C 448,200 445,165 452,140 C 458,118 492,118 502,140 C 510,158 500,195 488,238 Z" fill="url(#featherGrad)" stroke="#94a3b8" stroke-width="3" />

<!-- 后脑勺呆毛 -->
<path d="M 454,132 Q 436,122 440,136 Q 428,132 438,144" fill="#ffffff" stroke="#94a3b8" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />

<!-- 围巾环绕脖子的结 -->
<path d="M 440,222 Q 468,234 494,224 L 492,240 Q 466,250 438,238 Z" fill="#ef4444" stroke="#b91c1c" stroke-width="2" />
<line x1="458" y1="226" x2="456" y2="242" stroke="#ffffff" stroke-width="6" />
<line x1="478" y1="227" x2="476" y2="242" stroke="#ffffff" stroke-width="6" />

<!-- 标志性的巨大弹性喉囊 (可点击互动) -->
<g id="pelican-pouch-group" class="anim-pouch cursor-pointer">
<!-- 喉囊里探头的小青鱼 -->
<g class="anim-fish">
<!-- 鱼尾 -->
<polygon points="555,215 540,202 544,222" fill="#0284c7" stroke="#0369a1" stroke-width="1.5" />
<!-- 鱼身 -->
<ellipse cx="575" cy="212" rx="22" ry="11" transform="rotate(-15 575 212)" fill="#38bdf8" stroke="#0284c7" stroke-width="2" />
<!-- 鱼眼 -->
<circle cx="587" cy="206" r="2.5" fill="#0f172a" />
<circle cx="588" cy="205" r="1" fill="#ffffff" />
</g>

<!-- 大嘴下颚喉囊主体 -->
<path d="M 498,162 C 510,235 555,265 605,248 C 645,235 660,192 652,162 Z" fill="url(#pouchGrad)" stroke="#c2410c" stroke-width="3" stroke-linejoin="round" />
<!-- 喉囊立体褶皱弧线 -->
<path d="M 522,182 Q 565,235 628,185" fill="none" stroke="#d97706" stroke-width="2.5" stroke-linecap="round" opacity="0.65" />
<path d="M 540,208 Q 578,242 618,210" fill="none" stroke="#b45309" stroke-width="2" stroke-linecap="round" opacity="0.45" />
</g>

<!-- 长长的上喙与前端小钩 -->
<path d="M 492,140 C 545,138 615,142 654,152 C 665,155 666,168 656,172 C 652,174 648,167 644,165 C 595,166 538,165 495,164 Z" fill="url(#topBeakGrad)" stroke="#b45309" stroke-width="2.8" stroke-linejoin="round" />
<!-- 喙上的鼻孔线条 -->
<line x1="525" y1="149" x2="542" y2="150" stroke="#b45309" stroke-width="2" stroke-linecap="round" />

<!-- 脸颊红晕与眼睛 -->
<ellipse cx="486" cy="158" rx="7" ry="4.5" fill="#fb7185" opacity="0.55" />
<!-- 黄色眼圈 -->
<circle cx="484" cy="144" r="10.5" fill="#fde047" stroke="#ca8a04" stroke-width="1.5" />
<!-- 眼白与灵动瞳孔 -->
<circle cx="484" cy="144" r="7.5" fill="#ffffff" />
<circle cx="486.5" cy="144" r="4.5" fill="#0f172a" />
<circle cx="488" cy="142" r="1.8" fill="#ffffff" />

<!-- 小巧的水手帽 -->
<g class="anim-hat">
<ellipse cx="472" cy="122" rx="22" ry="6" transform="rotate(-12 472 122)" fill="#f8fafc" stroke="#1e3a8a" stroke-width="2.5" />
<path d="M 454,124 L 456,104 Q 472,96 488,104 L 490,120 Z" transform="rotate(-12 472 115)" fill="#ffffff" stroke="#1e3a8a" stroke-width="2.5" stroke-linejoin="round" />
<!-- 蓝色帽带与金色船锚徽章 -->
<rect x="455" y="114" width="34" height="6" transform="rotate(-12 472 117)" fill="#1d4ed8" />
<circle cx="473" cy="109" r="3" fill="#fbbf24" />
</g>
</g>
</g>

<!-- ==================== 8. 中轴牙盘曲柄(前景齿轮) ==================== -->
<g transform="translate(475, 445)">
<!-- 装饰齿轮盘 -->
<circle cx="0" cy="0" r="24" fill="#e2e8f0" stroke="#475569" stroke-width="3" />
<circle cx="0" cy="0" r="17" fill="none" stroke="#94a3b8" stroke-width="2" stroke-dasharray="6 5" class="anim-wheel" />
<circle cx="0" cy="0" r="7" fill="#475569" />
<!-- 实时旋转的前景曲柄杆 (由 JS 同步驱动角度) -->
<line id="front-crank-arm" x1="0" y1="0" x2="36" y2="0" stroke="#94a3b8" stroke-width="7" stroke-linecap="round" />
<circle cx="0" cy="0" r="4" fill="#f8fafc" />
</g>

<!-- ==================== 9. 近端腿部(左腿,位于最前景) ==================== -->
<!-- 由 JS 逆运动学实时计算膝盖与脚掌位置,实现严丝合缝的踩踏动作 -->
<g id="front-leg-layer" class="anim-body">
<!-- 大腿根部羽毛短裤感 -->
<ellipse cx="412" cy="332" rx="18" ry="14" fill="#f8fafc" stroke="#94a3b8" stroke-width="2.5" />
</g>
<g id="front-leg-ik">
<!-- 橙色竹节腿 (髋 -> 膝 -> 踝) -->
<path id="front-leg-limb" d="M 412,332 L 485,375 L 510,445" fill="none" stroke="#f97316" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" />
<!-- 膝盖关节高光 -->
<circle id="front-knee-joint" cx="485" cy="375" r="6" fill="#fb923c" />
<!-- 宽大的橙色鸭蹼脚掌 -->
<path id="front-foot" d="M -14,-5 L 25,-5 C 32,-5 34,7 24,9 L -14,9 C -20,9 -20,-5 -14,-5 Z" fill="#f97716" stroke="#c2410c" stroke-width="2.2" />
<!-- 近端脚踏板 -->
<rect id="front-pedal" x="-18" y="5" width="36" height="8" rx="3.5" fill="#0f172a" stroke="#cbd5e1" stroke-width="1.5" />
<rect id="front-pedal-reflector" x="-10" y="7" width="20" height="4" rx="1" fill="#f59e0b" />
</g>

<!-- ==================== 10. 近端翅膀(右翼,紧握车把) ==================== -->
<g class="anim-body">
<!-- 肩部与弯曲的前翅 -->
<path d="M 442,258 C 448,295 485,295 534,264" fill="none" stroke="#f8fafc" stroke-width="22" stroke-linecap="round" />
<path d="M 442,258 C 448,295 485,295 534,264" fill="none" stroke="#94a3b8" stroke-width="3" stroke-linecap="round" />
<!-- 翅膀羽毛分叉细节 -->
<path d="M 465,288 Q 492,292 512,276" fill="none" stroke="#cbd5e1" stroke-width="2.5" stroke-linecap="round" />
<!-- 搭在车把上的翅尖羽毛 -->
<ellipse cx="534" cy="263" rx="12" ry="8" transform="rotate(-15 534 263)" fill="#ffffff" stroke="#94a3b8" stroke-width="2.5" />
</g>

<!-- 车铃叮当音波特效 (默认隐藏,点击时闪现) -->
<g id="bell-sound-waves" opacity="0" transform="translate(568, 232)" stroke="#fbbf24" stroke-width="3" fill="none" stroke-linecap="round">
<path d="M -12,-6 Q -18,-16 -8,-22" />
<path d="M 12,-6 Q 18,-16 8,-22" />
<text x="-18" y="-28" fill="#f59e0b" stroke="none" font-size="15" font-weight="bold">叮铃铃~♪</text>
</g>

</svg>

<!-- 浮动徽章:实时踏频指示 -->
<div class="absolute bottom-3 left-3 sm:bottom-5 sm:left-5 bg-slate-900/75 backdrop-blur-md text-white px-3.5 py-2 rounded-2xl text-xs flex items-center gap-3 border border-white/15 shadow-lg">
<div class="flex items-center gap-1.5">
<span class="text-amber-400 text-sm">⚡</span>
<span class="text-slate-300">骑行配速:</span>
<span id="speed-readout" class="font-bold text-amber-300 text-sm">1.0x</span>
</div>
<span class="text-white/20">|</span>
<div class="flex items-center gap-1.5">
<span class="text-sky-400 text-sm">🐟</span>
<span class="text-slate-300">喉囊存鱼:</span>
<span id="fish-count" class="font-bold text-sky-300 text-sm">1 条</span>
</div>
</div>
</div>

<!-- 底部交互控制面板 -->
<footer class="px-6 py-4 bg-slate-50 dark:bg-slate-800/90 border-t border-slate-200/80 dark:border-slate-700 flex flex-wrap items-center justify-between gap-4">

<!-- 速度滑块控制 -->
<div class="flex items-center gap-4 flex-1 min-w-[240px]">
<label for="speed-slider" class="text-xs sm:text-sm font-semibold text-slate-700 dark:text-slate-200 whitespace-nowrap flex items-center gap-1.5">
<span>🚴 踩踏速度</span>
</label>
<input id="speed-slider" type="range" min="0.2" max="2.5" step="0.1" value="1"
class="w-full max-w-xs h-2 bg-slate-200 dark:bg-slate-600 rounded-lg appearance-none cursor-pointer accent-teal-500" />
<button id="pause-btn" class="px-3 py-1.5 rounded-xl text-xs font-semibold bg-slate-200 hover:bg-slate-300 dark:bg-slate-700 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-200 transition">
暂停
</button>
</div>

<!-- 趣味互动按钮组 -->
<div class="flex flex-wrap items-center gap-2.5">
<button id="ring-bell-btn" class="px-3.5 py-2 rounded-xl text-xs font-semibold bg-amber-100 hover:bg-amber-200 text-amber-900 border border-amber-300/80 transition flex items-center gap-1.5 shadow-sm active:scale-95">
<span>🔔</span> 按响车铃
</button>
<button id="feed-fish-btn" class="px-3.5 py-2 rounded-xl text-xs font-semibold bg-sky-100 hover:bg-sky-200 text-sky-900 border border-sky-300/80 transition flex items-center gap-1.5 shadow-sm active:scale-95">
<span>🐟</span> 投喂小鱼
</button>
<button id="theme-toggle-btn" class="px-3.5 py-2 rounded-xl text-xs font-semibold bg-indigo-100 hover:bg-indigo-200 text-indigo-900 border border-indigo-300/80 transition flex items-center gap-1.5 shadow-sm active:scale-95">
<span id="theme-icon">🌅</span> <span id="theme-text">切换落日</span>
</button>
</div>
</footer>
</div>

<!-- 动画与逆运动学 (IK) 逻辑脚本 -->
<script>
(function() {
// 动画状态参数
let speedMultiplier = 1.0;
let isPaused = false;
let crankAngle = 0; // 曲柄当前角度 (弧度)
let lastTime = performance.now();
let isSunset = false;
let fishCount = 1;

// 几何常量:五通中轴与鹈鹕髋关节坐标
const crankCenter = { x: 475, y: 445 };
const crankRadius = 34; // 曲柄长度
const thighLen = 78; // 大腿长度
const calfLen = 76; // 小腿长度

// 获取 DOM 元素
const rootStyle = document.documentElement.style;
const frontCrankArm = document.getElementById('front-crank-arm');

const frontLegLimb = document.getElementById('front-leg-limb');
const frontKneeJoint = document.getElementById('front-knee-joint');
const frontFoot = document.getElementById('front-foot');
const frontPedal = document.getElementById('front-pedal');
const frontReflector = document.getElementById('front-pedal-reflector');

const backLegLimb = document.getElementById('back-leg-limb');
const backFoot = document.getElementById('back-foot');
const backPedal = document.getElementById('back-pedal');

const speedSlider = document.getElementById('speed-slider');
const speedReadout = document.getElementById('speed-readout');
const pauseBtn = document.getElementById('pause-btn');
const ringBellBtn = document.getElementById('ring-bell-btn');
const bikeBell = document.getElementById('bike-bell');
const bellWaves = document.getElementById('bell-sound-waves');
const feedFishBtn = document.getElementById('feed-fish-btn');
const pouchGroup = document.getElementById('pelican-pouch-group');
const fishCountEl = document.getElementById('fish-count');
const themeToggleBtn = document.getElementById('theme-toggle-btn');

/

  • 二连杆逆向运动学 (2-Bone Inverse Kinematics) 求解器
  • 根据髋关节 (hip) 和脚踏板 (pedal) 坐标,精确计算膝盖 (knee) 位置,确保膝盖自然向前弯曲
*/

function solveKneePosition(hipX, hipY, footX, footY, L1, L2) {
const dx = footX - hipX;
const dy = footY - hipY;
let dist = Math.hypot(dx, dy);

// 防止超出最大伸展距离
const maxReach = L1 + L2 - 2;
if (dist > maxReach) {
dist = maxReach;
}

// 余弦定理求髋部夹角
const cosAngle = (L1 L1 + dist dist - L2 L2) / (2 L1 * dist);
const clampedCos = Math.max(-1, Math.min(1, cosAngle));
const baseAngle = Math.atan2(dy, dx);
const hipOffsetAngle = Math.acos(clampedCos);

// 膝盖向车头方向(右侧,角度减小方向)弯曲
const kneeAngle = baseAngle - hipOffsetAngle;
return {
x: hipX + L1 * Math.cos(kneeAngle),
y: hipY + L1 * Math.sin(kneeAngle)
};
}

/

  • 主帧循环:同步更新曲柄、双脚踏板和鹈鹕双腿逆向运动学姿态
*/

function animateLoop(now) {
const dt = (now - lastTime) / 1000;
lastTime = now;

if (!isPaused) {
// 基础周期为 1.3 秒一圈,与 CSS 车轮旋转周期严格同步
const angularVelocity = (Math.PI 2 / 1.3) speedMultiplier;
crankAngle = (crankAngle + angularVelocity dt) % (Math.PI 2);

// 计算身体随踩踏的微小起伏偏移,让髋部与身体融为一体
const bobOffset = Math.sin(crankAngle 2) -2.5;
const hipPos = { x: 412, y: 332 + bobOffset };

// 1. 计算前景左脚踏板位置 (相位 0)
const frontPedalX = crankCenter.x + crankRadius * Math.cos(crankAngle);
const frontPedalY = crankCenter.y + crankRadius * Math.sin(crankAngle);

// 更新前景曲柄杆
frontCrankArm.setAttribute('x2', (crankRadius * Math.cos(crankAngle)).toFixed(1));
frontCrankArm.setAttribute('y2', (crankRadius * Math.sin(crankAngle)).toFixed(1));

// 求解左腿膝盖坐标
const frontKnee = solveKneePosition(hipPos.x, hipPos.y, frontPedalX, frontPedalY, thighLen, calfLen);

// 更新左腿 SVG 路径与关节
frontLegLimb.setAttribute('d', M ${hipPos.x.toFixed(1)},${hipPos.y.toFixed(1)} L ${frontKnee.x.toFixed(1)},${frontKnee.y.toFixed(1)} L ${frontPedalX.toFixed(1)},${frontPedalY.toFixed(1)});
frontKneeJoint.setAttribute('cx', frontKnee.x.toFixed(1));
frontKneeJoint.setAttribute('cy', frontKnee.y.toFixed(1));

// 脚掌随踩踏相位轻微踮脚旋转
const frontFootTilt = Math.sin(crankAngle) * 10;
frontFoot.setAttribute('transform', translate(${frontPedalX.toFixed(1)}, ${frontPedalY.toFixed(1)}) rotate(${frontFootTilt.toFixed(1)}));
frontPedal.setAttribute('transform', translate(${frontPedalX.toFixed(1)}, ${frontPedalY.toFixed(1)}));
frontReflector.setAttribute('transform', translate(${frontPedalX.toFixed(1)}, ${frontPedalY.toFixed(1)}));

// 2. 计算远景右脚踏板位置 (相位相差 180 度 / PI)
const backAngle = crankAngle + Math.PI;
const backPedalX = crankCenter.x + crankRadius * Math.cos(backAngle);
const backPedalY = crankCenter.y + crankRadius * Math.sin(backAngle);

const backKnee = solveKneePosition(hipPos.x - 4, hipPos.y - 2, backPedalX, backPedalY, thighLen, calfLen);
backLegLimb.setAttribute('d', M ${(hipPos.x - 4).toFixed(1)},${(hipPos.y - 2).toFixed(1)} L ${backKnee.x.toFixed(1)},${backKnee.y.toFixed(1)} L ${backPedalX.toFixed(1)},${backPedalY.toFixed(1)});

const backFootTilt = Math.sin(backAngle) * 10;
backFoot.setAttribute('transform', translate(${backPedalX.toFixed(1)}, ${backPedalY.toFixed(1)}) rotate(${backFootTilt.toFixed(1)}));
backPedal.setAttribute('transform', translate(${backPedalX.toFixed(1)}, ${backPedalY.toFixed(1)}));
}

requestAnimationFrame(animateLoop);
}

// 速度滑块监听
speedSlider.addEventListener('input', (e) => {
speedMultiplier = parseFloat(e.target.value);
rootStyle.setProperty('--anim-speed', speedMultiplier);
speedReadout.textContent = speedMultiplier.toFixed(1) + 'x';
if (isPaused) {
togglePause();
}
});

// 暂停/继续切换
function togglePause() {
isPaused = !isPaused;
const playState = isPaused ? 'paused' : 'running';
document.querySelectorAll(
'.anim-wheel, .anim-body, .anim-head, .anim-pouch, .anim-fish, .anim-scarf, .anim-tail, .anim-hat, .anim-road, .anim-cloud-1, .anim-cloud-2, .anim-shadow'
).forEach(el => {
el.style.animationPlayState = playState;
});
pauseBtn.textContent = isPaused ? '继续' : '暂停';
pauseBtn.classList.toggle('bg-teal-500', isPaused);
pauseBtn.classList.toggle('text-white', isPaused);
}

pauseBtn.addEventListener('click', togglePause);

// 车铃互动效果
function triggerBell() {
bikeBell.classList.remove('ringing');
void bikeBell.offsetWidth; // 触发重绘
bikeBell.classList.add('ringing');

bellWaves.style.transition = 'none';
bellWaves.style.opacity = '1';
bellWaves.setAttribute('transform', 'translate(568, 228) scale(1)');

setTimeout(() => {
bellWaves.style.transition = 'all 0.6s ease-out';
bellWaves.style.opacity = '0';
bellWaves.setAttribute('transform', 'translate(568, 212) scale(1.15)');
}, 50);
}

ringBellBtn.addEventListener('click', triggerBell);
bikeBell.addEventListener('click', triggerBell);

// 投喂小鱼/点击大嘴喉囊弹性放大效果
function triggerPouchBounce() {
fishCount = (fishCount % 5) + 1;
fishCountEl.textContent = fishCount + ' 条';
pouchGroup.style.transition = 'transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1)';
pouchGroup.style.transform = 'scale(1.12) rotate(4deg)';
setTimeout(() => {
pouchGroup.style.transform = '';
}, 280);
}

feedFishBtn.addEventListener('click', triggerPouchBounce);
pouchGroup.addEventListener('click', triggerPouchBounce);

// 白天/黄昏海景切换
themeToggleBtn.addEventListener('click', () => {
isSunset = !isSunset;
const bgSky = document.getElementById('bg-sky');
const bgSea = document.getElementById('bg-sea');
const celestial = document.getElementById('celestial-body');
const themeIcon = document.getElementById('theme-icon');
const themeText = document.getElementById('theme-text');

if (isSunset) {
bgSky.setAttribute('fill', 'url(#skyGradSunset)');
bgSea.setAttribute('fill', 'url(#seaGradSunset)');
celestial.setAttribute('transform', 'translate(820, 290) scale(1.35)');
themeIcon.textContent = '☀️';
themeText.textContent = '切换晴空';
} else {
bgSky.setAttribute('fill', 'url(#skyGradDay)');
bgSea.setAttribute('fill', 'url(#seaGradDay)');
celestial.setAttribute('transform', 'translate(820, 120) scale(1)');
themeIcon.textContent = '🌅';
themeText.textContent = '切换落日';
}
});

// 启动主循环
window.onload = function() {
requestAnimationFrame(animateLoop);
};
})();
</script>
</body>
</html>