• wenjiu19

    图片

    源代码

    此文本将被隐藏```
    <!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&#039;, '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&#039;, '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&#039;, '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&#039;, '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&#039;, '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&#039;, 'g');
    g.setAttribute('transform', translate(${x}, ${y}) scale(${scale}));
    g.setAttribute('opacity', opacity);

    const path = document.createElementNS('http://www.w3.org/2000/svg&#039;, '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&#039;, '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&#039;, '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&#039;, '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&#039;, '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>

    主楼
  • lkjsdf

    这是谁的部将这么勇猛,看起来有点genmini的方式风格

    #1
  • lueluelue

    图片

    图片

    #2
  • 诡道疯人

    不会是新的GPT?之前有消息快发布了,已经拿去给美国政府审核了

    #3

发表回复

登录后回复