• 此用户已被封禁

    提示词

    画一个醍醐骑自行车的动画, html格式, 不需要测试

    模型是 Gemini 3.7 flash```
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>醍醐兜风记 · Pelican Bicycle Ride</title>
    <link rel="preconnect" href="https://fonts.googleapis.com"&gt;
    <link rel="preconnect" href="https://fonts.gstatic.com&quot; crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&family=Plus+Jakarta+Sans:wght@500;600;700&display=swap&quot; rel="stylesheet">
    <style>
    :root {
    --bg-gradient-day: linear-gradient(180deg, #38bdf8 0%, #7dd3fc 35%, #bae6fd 60%, #e0f2fe 100%);
    --surface-glass: rgba(255, 255, 255, 0.82);
    --surface-glass-border: rgba(255, 255, 255, 0.6);
    --text-main: #0f172a;
    --text-muted: #475569;
    --brand-coral: #f97316;
    --brand-coral-hover: #ea580c;
    --brand-teal: #0d9488;
    --brand-yellow: #facc15;
    --shadow-sm: 0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
    --shadow-lg: 0 16px 32px -4px rgba(15, 23, 42, 0.12), 0 6px 12px -4px rgba(15, 23, 42, 0.08);
    --radius-pill: 9999px;
    --radius-card: 20px;
    }

    • {

    box-sizing: border-box;
    margin: 0;
    padding: 0;
    user-select: none;
    -webkit-user-select: none;
    }

    body {
    font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
    background-color: #0f172a;
    color: var(--text-main);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    overflow-x: hidden;
    padding: 16px;
    }

    .app-container {
    width: 100%;
    max-width: 1100px;
    background: #ffffff;
    border-radius: 28px;
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    border: 1px solid rgba(255, 255, 255, 0.8);
    position: relative;
    }

    / Header Bar /
    .app-header {
    padding: 18px 28px;
    background: #ffffff;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #f1f5f9;
    z-index: 10;
    }

    .brand-title {
    display: flex;
    align-items: center;
    gap: 12px;
    }

    .brand-icon {
    width: 42px;
    height: 42px;
    background: #ffedd5;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    box-shadow: inset 0 2px 4px rgba(249, 115, 22, 0.15);
    }

    .brand-text h1 {
    font-family: 'Fredoka', cursive, sans-serif;
    font-size: 1.35rem;
    font-weight: 700;
    color: #0f172a;
    letter-spacing: -0.01em;
    line-height: 1.2;
    }

    .brand-text p {
    font-size: 0.8rem;
    color: #64748b;
    font-weight: 500;
    }

    .header-badges {
    display: flex;
    align-items: center;
    gap: 10px;
    }

    .speedometer-badge {
    display: flex;
    align-items: baseline;
    gap: 4px;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    font-variant-numeric: tabular-nums;
    }

    .speedometer-num {
    font-family: 'Fredoka', cursive, sans-serif;
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--brand-coral);
    }

    .speedometer-unit {
    font-size: 0.75rem;
    color: #64748b;
    font-weight: 600;
    }

    / Canvas Stage /
    .canvas-stage {
    position: relative;
    width: 100%;
    height: 520px;
    background: #38bdf8;
    overflow: hidden;
    }

    #animCanvas {
    width: 100%;
    height: 100%;
    display: block;
    cursor: grab;
    }

    #animCanvas:active {
    cursor: grabbing;
    }

    / Floating Canvas Overlays /
    .floating-ui-layer {
    position: absolute;
    top: 16px;
    left: 16px;
    right: 16px;
    display: flex;
    justify-content: space-between;
    pointer-events: none;
    z-index: 5;
    }

    .floating-pill-group {
    display: flex;
    gap: 8px;
    pointer-events: auto;
    }

    .glass-btn {
    background: var(--surface-glass);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid var(--surface-glass-border);
    padding: 8px 14px;
    border-radius: var(--radius-pill);
    font-size: 0.82rem;
    font-weight: 600;
    color: #1e293b;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 6px;
    box-shadow: var(--shadow-sm);
    transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
    }

    .glass-btn:hover {
    background: #ffffff;
    transform: translateY(-2px);
    box-shadow: 0 6px 14px rgba(0, 0, 0, 0.1);
    }

    .glass-btn:active {
    transform: translateY(0);
    }

    .glass-btn.active {
    background: #0f172a;
    color: #ffffff;
    border-color: #0f172a;
    }

    / Action Trigger Overlay for Bell & Fish /
    .quick-triggers {
    position: absolute;
    bottom: 20px;
    right: 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    z-index: 5;
    }

    .big-action-btn {
    background: #ffffff;
    border: none;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 26px;
    cursor: pointer;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
    transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
    position: relative;
    }

    .big-action-btn:hover {
    transform: scale(1.1) rotate(5deg);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
    }

    .big-action-btn:active {
    transform: scale(0.95);
    }

    .big-action-btn .tooltip {
    position: absolute;
    right: 68px;
    background: #0f172a;
    color: #fff;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 4px 10px;
    border-radius: 6px;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s;
    }

    .big-action-btn:hover .tooltip {
    opacity: 1;
    }

    / Bottom Control Dashboard /
    .control-dashboard {
    padding: 22px 28px;
    background: #ffffff;
    border-top: 1px solid #f1f5f9;
    display: grid;
    grid-template-columns: 1fr 1.2fr 1fr;
    gap: 24px;
    align-items: center;
    }

    @media (max-width: 860px) {
    .control-dashboard {
    grid-template-columns: 1fr;
    gap: 18px;
    }
    .canvas-stage {
    height: 400px;
    }
    }

    .control-group {
    display: flex;
    flex-direction: column;
    gap: 8px;
    }

    .control-label {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #64748b;
    display: flex;
    align-items: center;
    justify-content: space-between;
    }

    / Gear Shift Segmented Switch /
    .segmented-control {
    display: flex;
    background: #f1f5f9;
    padding: 4px;
    border-radius: var(--radius-pill);
    gap: 2px;
    }

    .seg-btn {
    flex: 1;
    border: none;
    background: transparent;
    padding: 8px 12px;
    font-family: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    color: #475569;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: all 0.2s ease;
    text-align: center;
    white-space: nowrap;
    }

    .seg-btn.active {
    background: #ffffff;
    color: var(--brand-coral);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
    font-weight: 700;
    }

    / Accessory Selector /
    .accessory-buttons {
    display: flex;
    gap: 8px;
    }

    .acc-btn {
    flex: 1;
    padding: 8px 10px;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    font-size: 0.8rem;
    font-weight: 600;
    color: #334155;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    transition: all 0.15s ease;
    }

    .acc-btn:hover {
    background: #f1f5f9;
    border-color: #cbd5e1;
    }

    .acc-btn.active {
    background: #ffedd5;
    border-color: #fdba74;
    color: #c2410c;
    font-weight: 700;
    }

    / Interaction Tips Footer /
    .app-footer {
    padding: 12px 28px;
    background: #fafaf9;
    border-top: 1px solid #f1f5f9;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 0.78rem;
    color: #94a3b8;
    }

    .shortcut-hints {
    display: flex;
    gap: 16px;
    }

    .kbd-shortcut {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    }

    kbd {
    background: #e2e8f0;
    color: #334155;
    padding: 2px 6px;
    border-radius: 4px;
    font-family: inherit;
    font-size: 0.7rem;
    font-weight: 700;
    box-shadow: 0 1px 0 #cbd5e1;
    }
    </style>
    </head>
    <body>

    <div class="app-container">
    <!-- Header -->
    <header class="app-header">
    <div class="brand-title">
    <div class="brand-icon">🚴‍♂️</div>
    <div class="brand-text">
    <h1>醍醐兜风记 · Pelican Cruise</h1>
    <p>海滨公路上的快乐骑手 · Animated Canvas Physics</p>
    </div>
    </div>
    <div class="header-badges">
    <div class="speedometer-badge">
    <span class="speedometer-num" id="speedDisplay">24</span>
    <span class="speedometer-unit">KM/H</span>
    </div>
    </div>
    </header>

    <!-- Canvas Animation Area -->
    <div class="canvas-stage">
    <!-- Floating Canvas UI -->
    <div class="floating-ui-layer">
    <!-- Environment Themes -->
    <div class="floating-pill-group">
    <button class="glass-btn active" id="btnThemeDay" onclick="setTheme('day')">☀️ 晴空碧海</button>
    <button class="glass-btn" id="btnThemeSunset" onclick="setTheme('sunset')">🌅 黄金日落</button>
    <button class="glass-btn" id="btnThemeNight" onclick="setTheme('night')">🌙 萤光星夜</button>
    </div>

    <!-- Audio & Cam Controls -->
    <div class="floating-pill-group">
    <button class="glass-btn" id="btnSound" onclick="toggleSound()">🔇 伴音: 关</button>
    </div>
    </div>

    <!-- Quick Action Buttons -->
    <div class="quick-triggers">
    <button class="big-action-btn" onclick="triggerBell()" title="按空格或点击按铃">
    🔔
    <span class="tooltip">车铃铛 (Space)</span>
    </button>
    <button class="big-action-btn" onclick="feedFish()" title="投喂小鱼">
    🐟
    <span class="tooltip">投喂大鱼 (F)</span>
    </button>
    </div>

    <canvas id="animCanvas"></canvas>
    </div>

    <!-- Bottom Controls -->
    <div class="control-dashboard">
    <!-- Gear / Speed Control -->
    <div class="control-group">
    <div class="control-label">
    <span>骑行档位 / 速度</span>
    <span id="gearLabel">2 档 (匀速)</span>
    </div>
    <div class="segmented-control">
    <button class="seg-btn" onclick="setGear(1)">1 慢悠</button>
    <button class="seg-btn active" onclick="setGear(2)">2 畅骑</button>
    <button class="seg-btn" onclick="setGear(3)">3 冲刺</button>
    <button class="seg-btn" onclick="setGear(4)">4 狂飙 🚀</button>
    </div>
    </div>

    <!-- Outfits & Hats -->
    <div class="control-group">
    <div class="control-label">
    <span>鹈鹕骑行装束</span>
    </div>
    <div class="accessory-buttons">
    <button class="acc-btn active" id="hatGoggles" onclick="setAccessory('goggles')">🥽 护目镜</button>
    <button class="acc-btn" id="hatBeret" onclick="setAccessory('beret')">🎨 法式贝雷帽</button>
    <button class="acc-btn" id="hatParty" onclick="setAccessory('party')">🎉 派对尖帽</button>
    <button class="acc-btn" id="hatNone" onclick="setAccessory('none')">✨ 原生态</button>
    </div>
    </div>

    <!-- Pelican Mood & Actions -->
    <div class="control-group">
    <div class="control-label">
    <span>互动特效</span>
    </div>
    <div class="accessory-buttons">
    <button class="acc-btn" onclick="pelicanWave()">👋 挥动翅膀</button>
    <button class="acc-btn" onclick="pelicanGulp()">😋 抖抖喉囊</button>
    </div>
    </div>
    </div>

    <!-- Footer -->
    <footer class="app-footer">
    <div class="shortcut-hints">
    <span class="kbd-shortcut"><kbd>Space</kbd> 响铃</span>
    <span class="kbd-shortcut"><kbd>F</kbd> 投鱼</span>
    <span class="kbd-shortcut"><kbd>1-4</kbd> 切档</span>
    </div>
    <div>
    <span>纯原生 Canvas 2D + 反向运动学 (IK) 骨骼驱动</span>
    </div>
    </footer>
    </div>

    <script>
    /* ==========================================================================
    1. SOUND & AUDIO SYNTHESIS (Web Audio API - No External Files)
    ========================================================================== */
    let audioCtx = null;
    let soundEnabled = false;

    function initAudio() {
    if (!audioCtx) {
    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    }
    }

    function toggleSound() {
    initAudio();
    soundEnabled = !soundEnabled;
    const btn = document.getElementById('btnSound');
    if (soundEnabled) {
    btn.innerHTML = '🔊 伴音: 开';
    btn.classList.add('active');
    playBellSound();
    } else {
    btn.innerHTML = '🔇 伴音: 关';
    btn.classList.remove('active');
    }
    }

    function playBellSound() {
    if (!soundEnabled || !audioCtx) return;
    if (audioCtx.state === 'suspended') audioCtx.resume();

    const now = audioCtx.currentTime;
    // Dual-tone chime for realistic bicycle brass bell (Ding-Ding!)
    [2400, 3100].forEach((freq, idx) => {
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.type = 'sine';
    osc.frequency.setValueAtTime(freq, now + idx * 0.08);

    gain.gain.setValueAtTime(0, now + idx * 0.08);
    gain.gain.linearRampToValueAtTime(0.25, now + idx * 0.08 + 0.005);
    gain.gain.exponentialRampToValueAtTime(0.0001, now + idx * 0.08 + 0.45);

    osc.connect(gain);
    gain.connect(audioCtx.destination);

    osc.start(now + idx * 0.08);
    osc.stop(now + idx * 0.08 + 0.5);
    });
    }

    function playGulpSound() {
    if (!soundEnabled || !audioCtx) return;
    if (audioCtx.state === 'suspended') audioCtx.resume();
    const now = audioCtx.currentTime;
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.type = 'triangle';
    osc.frequency.setValueAtTime(450, now);
    osc.frequency.exponentialRampToValueAtTime(180, now + 0.22);
    gain.gain.setValueAtTime(0.2, now);
    gain.gain.exponentialRampToValueAtTime(0.001, now + 0.22);
    osc.connect(gain);
    gain.connect(audioCtx.destination);
    osc.start(now);
    osc.stop(now + 0.25);
    }

    /* ==========================================================================
    2. CANVAS SETUP & RESPONSIVE RETINA SCALING
    ========================================================================== */
    const canvas = document.getElementById('animCanvas');
    const ctx = canvas.getContext('2d');

    let width = 0;
    let height = 0;
    let dpr = 1;

    function resize() {
    const rect = canvas.getBoundingClientRect();
    dpr = window.devicePixelRatio || 1;
    width = rect.width;
    height = rect.height;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    ctx.scale(dpr, dpr);
    }
    window.addEventListener('resize', resize);

    /* ==========================================================================
    3. STATE MANAGEMENT & PARAMS
    ========================================================================== */
    const state = {
    theme: 'day', // 'day' | 'sunset' | 'night'
    gear: 2,
    targetSpeed: 24,
    currentSpeed: 24,
    crankAngle: 0,
    wheelAngle: 0,
    worldDist: 0,
    accessory: 'goggles', // 'goggles' | 'beret' | 'party' | 'none'
    waveWing: 0,
    pouchJiggle: 0,
    bellRingAnim: 0,
    beakOpen: 0,
    fish: [],
    particles: [],
    clouds: [],
    seagulls: [],
    trees: [],
    dolphins: []
    };

    // Initialize decorative scenery elements
    function initScenery() {
    // Clouds
    state.clouds = [];
    for (let i = 0; i < 8; i++) {
    state.clouds.push({
    x: Math.random() * 1200,
    y: 30 + Math.random() * 110,
    scale: 0.6 + Math.random() * 0.8,
    speedMult: 0.15 + Math.random() * 0.2
    });
    }

    // Seagulls
    state.seagulls = [];
    for (let i = 0; i < 4; i++) {
    state.seagulls.push({
    x: Math.random() * 1000,
    y: 40 + Math.random() * 90,
    speed: 1.2 + Math.random() * 0.8,
    wingPhase: Math.random() Math.PI 2
    });
    }

    // Coastal palm trees and bushes
    state.trees = [];
    for (let i = 0; i < 10; i++) {
    state.trees.push({
    x: i 140 + Math.random() 40,
    type: Math.random() > 0.4 ? 'palm' : 'pine',
    size: 0.75 + Math.random() * 0.5
    });
    }

    // Jumping dolphin in the sea!
    state.dolphins = [
    { x: 300, y: 0, jumpTimer: 0, isJumping: false }
    ];
    }
    initScenery();

    /* ==========================================================================
    4. PALETTES & THEMES
    ========================================================================== */
    const themes = {
    day: {
    skyTop: '#38bdf8',
    skyMid: '#bae6fd',
    skyBottom: '#e0f2fe',
    seaFar: '#0284c7',
    seaNear: '#38bdf8',
    mountain: '#7dd3fc',
    mountainFront: '#60a5fa',
    road: '#64748b',
    roadLine: '#f8fafc',
    grass: '#10b981',
    sunColor: '#fef08a',
    sunGlow: 'rgba(254, 240, 138, 0.4)',
    ambientLight: 1.0
    },
    sunset: {
    skyTop: '#4c1d95',
    skyMid: '#c026d3',
    skyBottom: '#fdba74',
    seaFar: '#431407',
    seaNear: '#ea580c',
    mountain: '#701a75',
    mountainFront: '#831843',
    road: '#475569',
    roadLine: '#fde047',
    grass: '#b45309',
    sunColor: '#ffedd5',
    sunGlow: 'rgba(249, 115, 22, 0.6)',
    ambientLight: 0.95
    },
    night: {
    skyTop: '#030712',
    skyMid: '#0f172a',
    skyBottom: '#1e1b4b',
    seaFar: '#090d16',
    seaNear: '#1e293b',
    mountain: '#111827',
    mountainFront: '#1f2937',
    road: '#334155',
    roadLine: '#94a3b8',
    grass: '#064e3b',
    sunColor: '#f8fafc',
    sunGlow: 'rgba(248, 250, 252, 0.25)',
    ambientLight: 0.6
    }
    };

    function setTheme(t) {
    state.theme = t;
    ['btnThemeDay', 'btnThemeSunset', 'btnThemeNight'].forEach(id => {
    document.getElementById(id).classList.remove('active');
    });
    if (t === 'day') document.getElementById('btnThemeDay').classList.add('active');
    if (t === 'sunset') document.getElementById('btnThemeSunset').classList.add('active');
    if (t === 'night') document.getElementById('btnThemeNight').classList.add('active');
    }

    function setGear(g) {
    state.gear = g;
    const speeds = { 1: 14, 2: 24, 3: 40, 4: 68 };
    const labels = { 1: '1 档 (慢悠)', 2: '2 档 (畅骑)', 3: '3 档 (冲刺)', 4: '4 档 (狂飙 🚀)' };
    state.targetSpeed = speeds[g];
    document.getElementById('gearLabel').textContent = labels[g];

    document.querySelectorAll('.seg-btn').forEach((b, idx) => {
    b.classList.toggle('active', idx + 1 === g);
    });
    }

    function setAccessory(acc) {
    state.accessory = acc;
    ['hatGoggles', 'hatBeret', 'hatParty', 'hatNone'].forEach(id => {
    const el = document.getElementById(id);
    if (el) el.classList.remove('active');
    });
    const activeId = 'hat' + acc.charAt(0).toUpperCase() + acc.slice(1);
    const activeEl = document.getElementById(activeId);
    if (activeEl) activeEl.classList.add('active');
    }

    function triggerBell() {
    state.bellRingAnim = 1.0;
    playBellSound();
    spawnRingingWaves();
    }

    function feedFish() {
    // Spawn a golden jumping fish from right to left into pelican beak
    state.fish.push({
    x: width + 50,
    y: height * 0.55,
    vx: -6.5 - state.currentSpeed * 0.08,
    vy: -9.5,
    gravity: 0.38,
    rotation: 0,
    swallowed: false,
    scale: 1.0
    });
    }

    function pelicanWave() {
    state.waveWing = 1.0;
    }

    function pelicanGulp() {
    state.pouchJiggle = 1.0;
    playGulpSound();
    }

    // Keyboard shortcuts
    window.addEventListener('keydown', (e) => {
    if (e.code === 'Space') {
    e.preventDefault();
    triggerBell();
    } else if (e.code === 'KeyF') {
    e.preventDefault();
    feedFish();
    } else if (e.key >= '1' && e.key <= '4') {
    setGear(parseInt(e.key));
    }
    });

    /* ==========================================================================
    5. 2-BONE INVERSE KINEMATICS SOLVER (For realistic leg pedaling)
    ========================================================================== */
    function solveIK(hx, hy, px, py, L1, L2, bendForward = true) {
    const dx = px - hx;
    const dy = py - hy;
    let d = Math.hypot(dx, dy);
    const maxReach = L1 + L2 - 0.001;
    if (d > maxReach) d = maxReach;
    if (d < Math.abs(L1 - L2) + 0.001) d = Math.abs(L1 - L2) + 0.001;

    const alpha = Math.atan2(dy, dx);
    const cosAngle = (L1 L1 + d d - L2 L2) / (2 L1 * d);
    const clampedCos = Math.max(-1, Math.min(1, cosAngle));
    const beta = Math.acos(clampedCos);

    const kneeAngle = bendForward ? alpha - beta : alpha + beta;
    const kx = hx + L1 * Math.cos(kneeAngle);
    const ky = hy + L1 * Math.sin(kneeAngle);

    return { kx, ky };
    }

    /* ==========================================================================
    6. PARTICLES & VISUAL EFFECTS
    ========================================================================== */
    function spawnDust(x, y) {
    state.particles.push({
    type: 'dust',
    x: x + (Math.random() - 0.5) * 8,
    y: y + (Math.random() - 0.5) * 4,
    vx: -state.currentSpeed 0.15 - Math.random() 2,
    vy: -0.5 - Math.random() * 1.5,
    radius: 3 + Math.random() * 4,
    alpha: 0.6 + Math.random() * 0.3,
    color: state.theme === 'night' ? 'rgba(148, 163, 184,' : 'rgba(226, 232, 240,'
    });
    }

    function spawnTurboFlame(x, y) {
    for (let i = 0; i < 3; i++) {
    state.particles.push({
    type: 'flame',
    x: x,
    y: y + (Math.random() - 0.5) * 8,
    vx: -state.currentSpeed 0.4 - Math.random() 4,
    vy: (Math.random() - 0.5) * 2,
    radius: 4 + Math.random() * 6,
    alpha: 0.9,
    color: Math.random() > 0.5 ? '#f97316' : '#facc15'
    });
    }
    }

    function spawnRingingWaves() {
    const bikeX = width * 0.42;
    const groundY = height * 0.72;
    for (let i = 0; i < 3; i++) {
    setTimeout(() => {
    state.particles.push({
    type: 'ring',
    x: bikeX + 38,
    y: groundY - 110,
    radius: 8,
    maxRadius: 40 + i * 15,
    alpha: 0.9
    });
    }, i * 70);
    }
    }

    /* ==========================================================================
    7. MAIN DRAWING ROUTINES
    ========================================================================== */
    function drawSkyAndEnvironment(t) {
    const theme = themes[state.theme];

    // Sky Gradient
    const skyGrad = ctx.createLinearGradient(0, 0, 0, height * 0.65);
    skyGrad.addColorStop(0, theme.skyTop);
    skyGrad.addColorStop(0.5, theme.skyMid);
    skyGrad.addColorStop(1, theme.skyBottom);
    ctx.fillStyle = skyGrad;
    ctx.fillRect(0, 0, width, height);

    // Celestial Sun / Moon
    const sunX = width * 0.8;
    const sunY = height * 0.22;

    ctx.save();
    // Glow
    const glowGrad = ctx.createRadialGradient(sunX, sunY, 10, sunX, sunY, 120);
    glowGrad.addColorStop(0, theme.sunGlow);
    glowGrad.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = glowGrad;
    ctx.beginPath();
    ctx.arc(sunX, sunY, 120, 0, Math.PI * 2);
    ctx.fill();

    // Sun/Moon Body
    ctx.fillStyle = theme.sunColor;
    ctx.beginPath();
    ctx.arc(sunX, sunY, state.theme === 'night' ? 24 : 32, 0, Math.PI * 2);
    ctx.fill();

    if (state.theme === 'night') {
    // Moon craters
    ctx.fillStyle = '#cbd5e1';
    ctx.beginPath();
    ctx.arc(sunX - 6, sunY - 4, 4, 0, Math.PI * 2);
    ctx.arc(sunX + 6, sunY + 6, 6, 0, Math.PI * 2);
    ctx.fill();

    // Stars
    ctx.fillStyle = 'rgba(255,255,255,0.85)';
    for (let i = 0; i < 24; i++) {
    const sx = (Math.sin(i 99 + t 0.0005) 0.5 + 0.5) width;
    const sy = (Math.cos(i 33) 0.5 + 0.5) (height 0.4);
    const tw = (Math.sin(t 0.005 + i) 0.5 + 0.5);
    ctx.beginPath();
    ctx.arc(sx, sy, 1 + tw 1.5, 0, Math.PI 2);
    ctx.fill();
    }
    }
    ctx.restore();

    // Distant Clouds
    ctx.save();
    state.clouds.forEach(cloud => {
    cloud.x -= state.currentSpeed 0.03 cloud.speedMult;
    if (cloud.x < -160) cloud.x = width + 100;

    ctx.fillStyle = state.theme === 'night' ? 'rgba(30, 41, 59, 0.45)' : 'rgba(255, 255, 255, 0.7)';
    ctx.beginPath();
    const cx = cloud.x;
    const cy = cloud.y;
    const s = cloud.scale;
    ctx.arc(cx, cy, 24 s, 0, Math.PI 2);
    ctx.arc(cx + 25 s, cy - 10 s, 28 s, 0, Math.PI 2);
    ctx.arc(cx + 55 s, cy, 22 s, 0, Math.PI * 2);
    ctx.arc(cx + 30 s, cy + 10 s, 30 s, 0, Math.PI 2);
    ctx.fill();
    });
    ctx.restore();

    // Distant Mountain Ranges
    const horizonY = height * 0.55;
    ctx.fillStyle = theme.mountain;
    ctx.beginPath();
    ctx.moveTo(0, horizonY);
    for (let x = 0; x <= width; x += 30) {
    const offset = (state.worldDist 0.04 + x 0.004);
    const my = horizonY - 45 - Math.sin(offset) 35 - Math.cos(offset 2.2) * 20;
    ctx.lineTo(x, my);
    }
    ctx.lineTo(width, horizonY);
    ctx.closePath();
    ctx.fill();

    // Foreground Hills
    ctx.fillStyle = theme.mountainFront;
    ctx.beginPath();
    ctx.moveTo(0, horizonY + 10);
    for (let x = 0; x <= width; x += 25) {
    const offset = (state.worldDist 0.08 + x 0.007);
    const my = horizonY - 15 - Math.sin(offset 1.5) 25 - Math.sin(offset 0.5) 15;
    ctx.lineTo(x, my);
    }
    ctx.lineTo(width, horizonY + 10);
    ctx.closePath();
    ctx.fill();

    // Ocean Layer with waves
    const seaGrad = ctx.createLinearGradient(0, horizonY, 0, height * 0.72);
    seaGrad.addColorStop(0, theme.seaFar);
    seaGrad.addColorStop(1, theme.seaNear);
    ctx.fillStyle = seaGrad;
    ctx.fillRect(0, horizonY, width, height * 0.17);

    // Sea wave glints
    ctx.strokeStyle = state.theme === 'sunset' ? 'rgba(253, 186, 116, 0.4)' : 'rgba(255, 255, 255, 0.35)';
    ctx.lineWidth = 1.5;
    for (let i = 0; i < 12; i++) {
    const waveX = ((i 110 - state.worldDist 0.2) % (width + 200)) - 100;
    const waveY = horizonY + 8 + (i % 4) * 8;
    ctx.beginPath();
    ctx.moveTo(waveX, waveY);
    ctx.quadraticCurveTo(waveX + 15, waveY - 3, waveX + 30, waveY);
    ctx.stroke();
    }

    // Coastal Grass/Soil Bank
    const groundY = height * 0.72;
    ctx.fillStyle = theme.grass;
    ctx.fillRect(0, groundY - 18, width, 24);

    // Scrolling Palm / Pine trees along coast
    state.trees.forEach(tree => {
    const screenX = ((tree.x - state.worldDist * 0.4) % (width + 200));
    const tx = screenX < -100 ? screenX + width + 200 : screenX;
    const ty = groundY - 14;

    ctx.save();
    ctx.translate(tx, ty);
    ctx.scale(tree.size, tree.size);

    if (tree.type === 'palm') {
    // Curved trunk
    ctx.strokeStyle = state.theme === 'night' ? '#1e293b' : '#78350f';
    ctx.lineWidth = 6;
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.quadraticCurveTo(-10, -35, -5, -70);
    ctx.stroke();

    // Palm fronds
    ctx.fillStyle = state.theme === 'night' ? '#064e3b' : '#059669';
    for (let f = 0; f < 5; f++) {
    const angle = (f - 2) * 0.55 - 0.2;
    ctx.save();
    ctx.translate(-5, -70);
    ctx.rotate(angle);
    ctx.beginPath();
    ctx.ellipse(22, 0, 24, 7, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
    }
    } else {
    // Pine tree
    ctx.fillStyle = state.theme === 'night' ? '#0f172a' : '#5c3a21';
    ctx.fillRect(-3, -15, 6, 15);
    ctx.fillStyle = state.theme === 'night' ? '#064e3b' : '#047857';
    for (let p = 0; p < 3; p++) {
    ctx.beginPath();
    ctx.moveTo(0, -50 + p * 12);
    ctx.lineTo(-18 + p 3, -25 + p 12);
    ctx.lineTo(18 - p 3, -25 + p 12);
    ctx.closePath();
    ctx.fill();
    }
    }
    ctx.restore();
    });

    // Road Surface
    ctx.fillStyle = theme.road;
    ctx.fillRect(0, groundY, width, height - groundY);

    // Curb edge
    ctx.fillStyle = '#94a3b8';
    ctx.fillRect(0, groundY - 2, width, 4);

    // Dashed Road Line
    ctx.fillStyle = theme.roadLine;
    const dashWidth = 45;
    const dashGap = 35;
    const cycle = dashWidth + dashGap;
    const dashOffset = (state.worldDist * 0.8) % cycle;
    const roadLineY = groundY + (height - groundY) * 0.45;

    for (let dx = -dashOffset; dx < width + cycle; dx += cycle) {
    ctx.fillRect(dx, roadLineY, dashWidth, 6);
    }

    // Flying Seagulls
    ctx.strokeStyle = state.theme === 'night' ? '#94a3b8' : '#ffffff';
    ctx.lineWidth = 2;
    ctx.lineCap = 'round';
    state.seagulls.forEach(sg => {
    sg.x -= (state.currentSpeed * 0.05 + sg.speed);
    if (sg.x < -40) sg.x = width + 60;
    sg.wingPhase += 0.12;

    const flap = Math.sin(sg.wingPhase) * 6;
    ctx.beginPath();
    ctx.moveTo(sg.x - 12, sg.y + flap);
    ctx.quadraticCurveTo(sg.x - 6, sg.y - 4, sg.x, sg.y);
    ctx.quadraticCurveTo(sg.x + 6, sg.y - 4, sg.x + 12, sg.y + flap);
    ctx.stroke();
    });
    }

    /* ==========================================================================
    8. DRAW BICYCLE & MECHANICAL DETAIL
    ========================================================================== */
    function drawBicycle(bx, by, crankAngle, wheelAngle) {
    // Dimensions
    const wheelbase = 160;
    const rearX = bx - wheelbase * 0.5;
    const frontX = bx + wheelbase * 0.5;
    const wheelR = 40;
    const bbX = bx; // Bottom Bracket (crank center)
    const bbY = by - 16;
    const seatX = bx - 26;
    const seatY = by - 70;
    const headX = bx + 48;
    const headY = by - 80;

    ctx.save();

    // Drop shadows under wheels
    ctx.fillStyle = 'rgba(15, 23, 42, 0.28)';
    ctx.beginPath();
    ctx.ellipse(rearX, by + 1, 42, 7, 0, 0, Math.PI * 2);
    ctx.ellipse(frontX, by + 1, 42, 7, 0, 0, Math.PI * 2);
    ctx.fill();

    // Function to draw realistic spoke wheels
    function drawWheel(wx, wy, rot) {
    ctx.save();
    ctx.translate(wx, wy);
    ctx.rotate(rot);

    // Tire Outer Rubber
    ctx.strokeStyle = '#1e293b';
    ctx.lineWidth = 9;
    ctx.beginPath();
    ctx.arc(0, 0, wheelR, 0, Math.PI * 2);
    ctx.stroke();

    // Shiny Chrome Rim
    ctx.strokeStyle = '#cbd5e1';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.arc(0, 0, wheelR - 3, 0, Math.PI * 2);
    ctx.stroke();

    // Spokes
    ctx.strokeStyle = 'rgba(226, 232, 240, 0.85)';
    ctx.lineWidth = 1.2;
    const numSpokes = 12;
    for (let i = 0; i < numSpokes; i++) {
    const a = (i / numSpokes) Math.PI 2;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(Math.cos(a) (wheelR - 4), Math.sin(a) (wheelR - 4));
    ctx.stroke();
    }

    // Center Hub
    ctx.fillStyle = '#475569';
    ctx.beginPath();
    ctx.arc(0, 0, 6, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#e2e8f0';
    ctx.beginPath();
    ctx.arc(0, 0, 3, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
    }

    // Draw Wheels
    drawWheel(rearX, by - wheelR, wheelAngle);
    drawWheel(frontX, by - wheelR, wheelAngle);

    // Bicycle Frame Tubing (Vintage Emerald / Mint Teal)
    const frameColor = '#0d9488';
    const frameAccent = '#14b8a6';
    ctx.lineWidth = 7;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.strokeStyle = frameColor;

    // Rear Triangle (Chainstay & Seatstay)
    ctx.beginPath();
    ctx.moveTo(rearX, by - wheelR);
    ctx.lineTo(bbX, bbY); // Chainstay
    ctx.lineTo(seatX, seatY + 12); // Seat tube
    ctx.lineTo(rearX, by - wheelR); // Seatstay
    ctx.stroke();

    // Front Triangle (Main Frame: Down tube & Top tube)
    ctx.beginPath();
    ctx.moveTo(bbX, bbY);
    ctx.lineTo(headX, headY + 14); // Down tube
    ctx.lineTo(seatX, seatY + 12); // Top tube
    ctx.stroke();

    // Front Fork
    ctx.strokeStyle = frameAccent;
    ctx.lineWidth = 6;
    ctx.beginPath();
    ctx.moveTo(headX, headY + 14);
    ctx.lineTo(frontX, by - wheelR);
    ctx.stroke();

    // Seatpost & Leather Saddle
    ctx.strokeStyle = '#94a3b8';
    ctx.lineWidth = 5;
    ctx.beginPath();
    ctx.moveTo(seatX, seatY + 12);
    ctx.lineTo(seatX - 4, seatY);
    ctx.stroke();

    // Comfy Vintage Saddle
    ctx.fillStyle = '#78350f';
    ctx.beginPath();
    ctx.roundRect(seatX - 22, seatY - 5, 34, 10, [6, 8, 4, 4]);
    ctx.fill();
    ctx.fillStyle = '#92400e';
    ctx.beginPath();
    ctx.arc(seatX - 16, seatY - 1, 4, 0, Math.PI * 2);
    ctx.fill();

    // Stem & Drop / Cruiser Handlebars
    ctx.strokeStyle = '#cbd5e1';
    ctx.lineWidth = 5;
    ctx.beginPath();
    ctx.moveTo(headX, headY + 14);
    ctx.lineTo(headX + 2, headY - 12); // Stem
    ctx.stroke();

    // Handlebar curve
    ctx.strokeStyle = '#e2e8f0';
    ctx.lineWidth = 6;
    ctx.beginPath();
    ctx.moveTo(headX - 14, headY - 14);
    ctx.quadraticCurveTo(headX + 8, headY - 18, headX + 16, headY - 6);
    ctx.stroke();

    // Handlebar Grips
    ctx.fillStyle = '#78350f';
    ctx.beginPath();
    ctx.arc(headX + 16, headY - 6, 5, 0, Math.PI * 2);
    ctx.fill();

    // Front Brass Bell
    ctx.fillStyle = '#facc15';
    ctx.beginPath();
    ctx.arc(headX + 6, headY - 20, 6, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#ca8a04';
    ctx.beginPath();
    ctx.arc(headX + 6, headY - 20, 3, 0, Math.PI * 2);
    ctx.fill();

    // Bell Vibration Ring Waves if ringing
    if (state.bellRingAnim > 0) {
    ctx.strokeStyle = rgba(250, 204, 21, ${state.bellRingAnim});
    ctx.lineWidth = 2.5;
    ctx.beginPath();
    ctx.arc(headX + 6, headY - 20, 10 + (1 - state.bellRingAnim) 18, 0, Math.PI 2);
    ctx.stroke();
    }

    // Front Basket with Flower / Baguette
    ctx.fillStyle = '#d97706';
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#b45309';
    ctx.beginPath();
    ctx.roundRect(headX + 14, headY - 12, 24, 18, 4);
    ctx.fill();
    ctx.stroke();
    // Sunflower in basket
    ctx.fillStyle = '#facc15';
    ctx.beginPath();
    ctx.arc(headX + 26, headY - 14, 7, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#78350f';
    ctx.beginPath();
    ctx.arc(headX + 26, headY - 14, 3, 0, Math.PI * 2);
    ctx.fill();

    // Vintage Chrome Headlight (Glows at night/sunset!)
    ctx.fillStyle = '#e2e8f0';
    ctx.beginPath();
    ctx.arc(headX + 22, headY + 4, 8, -Math.PI 0.5, Math.PI 0.5);
    ctx.fill();

    if (state.theme === 'night' || state.theme === 'sunset') {
    // Headlight Light Beam Cone
    const beamGrad = ctx.createRadialGradient(headX + 24, headY + 4, 5, headX + 160, headY + 30, 180);
    beamGrad.addColorStop(0, 'rgba(254, 240, 138, 0.65)');
    beamGrad.addColorStop(1, 'rgba(254, 240, 138, 0.0)');
    ctx.fillStyle = beamGrad;
    ctx.beginPath();
    ctx.moveTo(headX + 24, headY + 4);
    ctx.lineTo(headX + 260, headY - 40);
    ctx.lineTo(headX + 290, headY + 90);
    ctx.closePath();
    ctx.fill();
    }

    // Sprocket & Chain
    ctx.fillStyle = '#64748b';
    ctx.beginPath();
    ctx.arc(bbX, bbY, 13, 0, Math.PI * 2);
    ctx.fill();

    ctx.strokeStyle = '#94a3b8';
    ctx.lineWidth = 2.5;
    ctx.beginPath();
    ctx.moveTo(bbX, bbY - 13);
    ctx.lineTo(rearX, by - wheelR - 6);
    ctx.lineTo(rearX, by - wheelR + 6);
    ctx.lineTo(bbX, bbY + 13);
    ctx.closePath();
    ctx.stroke();

    ctx.restore();

    return { bbX, bbY, seatX, seatY, headX, headY };
    }

    /* ==========================================================================
    9. DRAW PELICAN CHARACTER (With IK Legs, Pouch Physics, Hat & Expressions)
    ========================================================================== */
    function drawPelican(bikePoints, crankAngle, t) {
    const { bbX, bbY, seatX, seatY, headX, headY } = bikePoints;

    // Cadence bounce
    const bounce = Math.sin(crankAngle 2) 3;
    const pelicanHipX = seatX + 2;
    const pelicanHipY = seatY - 14 + bounce;

    const crankR = 20;
    // Pedal positions
    const rightPedalX = bbX + Math.cos(crankAngle) * crankR;
    const rightPedalY = bbY + Math.sin(crankAngle) * crankR;
    const leftPedalX = bbX + Math.cos(crankAngle + Math.PI) * crankR;
    const leftPedalY = bbY + Math.sin(crankAngle + Math.PI) * crankR;

    const thighL = 34;
    const shinL = 32;

    ctx.save();

    // --- 1. LEFT (FAR) LEG & PEDAL CRANK ---
    // Left Crank Arm
    ctx.strokeStyle = '#94a3b8';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(bbX, bbY);
    ctx.lineTo(leftPedalX, leftPedalY);
    ctx.stroke();

    // Left Pedal Flat
    ctx.fillStyle = '#334155';
    ctx.fillRect(leftPedalX - 7, leftPedalY - 3, 14, 6);

    // Solve IK for Left Leg
    const leftLegIK = solveIK(pelicanHipX - 4, pelicanHipY, leftPedalX, leftPedalY, thighL, shinL, true);

    // Far Leg (Darker orange for depth)
    ctx.strokeStyle = '#ea580c';
    ctx.lineWidth = 8;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.beginPath();
    ctx.moveTo(pelicanHipX - 4, pelicanHipY);
    ctx.lineTo(leftLegIK.kx, leftLegIK.ky);
    ctx.lineTo(leftPedalX, leftPedalY);
    ctx.stroke();

    // Webbed Foot (Far)
    ctx.fillStyle = '#c2410c';
    ctx.beginPath();
    ctx.ellipse(leftPedalX + 4, leftPedalY, 10, 5, 0.1, 0, Math.PI * 2);
    ctx.fill();

    // --- 2. PELICAN BODY, CHEST & TAIL ---
    const bodyBob = bounce * 0.7;
    const bodyCenterY = pelicanHipY - 26 + bodyBob;
    const bodyCenterX = pelicanHipX + 10;

    // Tail feathers (wagging in the wind)
    const tailWag = Math.sin(t 0.015 + state.currentSpeed 0.05) * 6;
    ctx.fillStyle = '#e2e8f0';
    ctx.beginPath();
    ctx.moveTo(bodyCenterX - 22, bodyCenterY);
    ctx.lineTo(bodyCenterX - 45, bodyCenterY - 12 + tailWag);
    ctx.lineTo(bodyCenterX - 40, bodyCenterY + 2 + tailWag);
    ctx.lineTo(bodyCenterX - 46, bodyCenterY + 12 + tailWag);
    ctx.lineTo(bodyCenterX - 18, bodyCenterY + 16);
    ctx.closePath();
    ctx.fill();

    // Main Plump Body (Pure white with soft shading)
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.ellipse(bodyCenterX, bodyCenterY, 28, 22, -0.22, 0, Math.PI * 2);
    ctx.fill();

    // Belly fluff shadow
    ctx.fillStyle = '#f1f5f9';
    ctx.beginPath();
    ctx.ellipse(bodyCenterX + 2, bodyCenterY + 6, 22, 14, -0.2, 0, Math.PI * 2);
    ctx.fill();

    // --- 3. CURVED NECK & HEAD ---
    const neckBaseX = bodyCenterX + 16;
    const neckBaseY = bodyCenterY - 8;
    const headCenterY = neckBaseY - 48 + bodyBob * 0.5;
    const headCenterX = neckBaseX + 16;

    // S-Curved Graceful Pelican Neck
    ctx.strokeStyle = '#ffffff';
    ctx.lineWidth = 22;
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(neckBaseX, neckBaseY);
    ctx.quadraticCurveTo(neckBaseX + 14, neckBaseY - 25, headCenterX, headCenterY);
    ctx.stroke();

    // Cute Fluttering Scarf / Bandana around Neck
    const scarfWind = Math.sin(t 0.02 + state.currentSpeed 0.08) (8 + state.currentSpeed 0.2);
    ctx.fillStyle = '#ef4444';
    ctx.beginPath();
    ctx.arc(neckBaseX + 6, neckBaseY - 24, 12, 0, Math.PI * 2);
    ctx.fill();
    // Scarf tails streaming behind
    ctx.fillStyle = '#dc2626';
    ctx.beginPath();
    ctx.moveTo(neckBaseX + 2, neckBaseY - 22);
    ctx.lineTo(neckBaseX - 28, neckBaseY - 30 + scarfWind);
    ctx.lineTo(neckBaseX - 22, neckBaseY - 20 + scarfWind);
    ctx.lineTo(neckBaseX - 32, neckBaseY - 14 + scarfWind);
    ctx.lineTo(neckBaseX + 2, neckBaseY - 16);
    ctx.closePath();
    ctx.fill();

    // Head Base Ball
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(headCenterX, headCenterY, 18, 0, Math.PI * 2);
    ctx.fill();

    // --- 4. FAMOUS PELICAN BEAK & ELASTIC THROAT POUCH ---
    const beakStartX = headCenterX + 8;
    const beakStartY = headCenterY + 2;
    const beakLength = 62;
    const beakTipX = beakStartX + beakLength;
    const beakTipY = beakStartY - 2;

    // Pouch Stretchy Jiggle Physics
    const pouchWind = Math.sin(t 0.012 + state.currentSpeed 0.06) (4 + state.currentSpeed 0.15);
    const jiggle = Math.sin(state.pouchJiggle Math.PI 4) 14 state.pouchJiggle;
    const pouchDrop = 34 + jiggle + (state.beakOpen * 18);

    // Huge Elastic Throat Pouch (Bright warm orange/peach)
    ctx.fillStyle = '#fb923c';
    ctx.strokeStyle = '#ea580c';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(beakStartX - 8, beakStartY + 6);
    ctx.quadraticCurveTo(
    beakStartX + 18,
    beakStartY + pouchDrop + pouchWind,
    beakTipX - 4,
    beakTipY + (state.beakOpen * 20)
    );
    ctx.lineTo(beakStartX + 8, beakStartY + (state.beakOpen * 8));
    ctx.closePath();
    ctx.fill();
    ctx.stroke();

    // Throat pouch muscle stretch lines
    ctx.strokeStyle = 'rgba(234, 88, 12, 0.4)';
    ctx.lineWidth = 1.5;
    for (let s = 1; s <= 3; s++) {
    ctx.beginPath();
    ctx.moveTo(beakStartX + s * 10, beakStartY + 4);
    ctx.quadraticCurveTo(
    beakStartX + s * 11,
    beakStartY + (pouchDrop 0.7) + pouchWind 0.5,
    beakStartX + s * 14,
    beakStartY + pouchDrop * 0.95
    );
    ctx.stroke();
    }

    // Upper Long Bill (Hard keratin with hooked tip)
    ctx.fillStyle = '#f97316';
    ctx.strokeStyle = '#ea580c';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(beakStartX - 6, beakStartY - 8);
    ctx.lineTo(beakTipX, beakTipY);
    // Hook tip at end of bill
    ctx.quadraticCurveTo(beakTipX + 6, beakTipY + 4, beakTipX + 2, beakTipY + 8);
    ctx.lineTo(beakStartX, beakStartY + 2);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();

    // Nostril ridge
    ctx.fillStyle = '#ea580c';
    ctx.beginPath();
    ctx.arc(beakStartX + 8, beakStartY - 4, 2.5, 0, Math.PI * 2);
    ctx.fill();

    // Pelican Eyes (Big, expressive, cute)
    const eyeX = headCenterX + 4;
    const eyeY = headCenterY - 6;

    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(eyeX, eyeY, 7, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#cbd5e1';
    ctx.lineWidth = 1;
    ctx.stroke();

    // Pupil (Looking forward happily)
    ctx.fillStyle = '#0f172a';
    ctx.beginPath();
    ctx.arc(eyeX + 2, eyeY, 4, 0, Math.PI * 2);
    ctx.fill();
    // Eye sparkle
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(eyeX + 3.5, eyeY - 1.5, 1.5, 0, Math.PI * 2);
    ctx.fill();

    // Cheerful Blush Cheek
    ctx.fillStyle = 'rgba(244, 63, 94, 0.45)';
    ctx.beginPath();
    ctx.arc(headCenterX - 2, headCenterY + 4, 5, 0, Math.PI * 2);
    ctx.fill();

    // --- 5. ACCESSORIES & HATS ---
    if (state.accessory === 'goggles') {
    // Cool Aviator Goggles pushed up on forehead
    ctx.fillStyle = '#475569';
    ctx.strokeStyle = '#1e293b';
    ctx.lineWidth = 2;
    // Strap around head
    ctx.beginPath();
    ctx.moveTo(headCenterX - 14, headCenterY - 10);
    ctx.lineTo(headCenterX + 12, headCenterY - 14);
    ctx.stroke();

    // Dual Lenses
    [-4, 8].forEach(gOff => {
    ctx.fillStyle = '#38bdf8';
    ctx.beginPath();
    ctx.arc(headCenterX + gOff, headCenterY - 15, 8, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();
    // Lens reflection
    ctx.fillStyle = 'rgba(255,255,255,0.7)';
    ctx.beginPath();
    ctx.arc(headCenterX + gOff - 2, headCenterY - 17, 3, 0, Math.PI * 2);
    ctx.fill();
    });
    } else if (state.accessory === 'beret') {
    // French Artist Beret (Navy Blue)
    ctx.fillStyle = '#1e3a8a';
    ctx.beginPath();
    ctx.ellipse(headCenterX - 2, headCenterY - 16, 20, 9, -0.2, 0, Math.PI * 2);
    ctx.fill();
    // Little top stem
    ctx.strokeStyle = '#1e3a8a';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(headCenterX - 4, headCenterY - 24);
    ctx.lineTo(headCenterX - 5, headCenterY - 29);
    ctx.stroke();
    } else if (state.accessory === 'party') {
    // Striped Birthday Party Hat
    ctx.save();
    ctx.translate(headCenterX - 2, headCenterY - 14);
    ctx.rotate(-0.15);
    ctx.fillStyle = '#f43f5e';
    ctx.beginPath();
    ctx.moveTo(0, -32);
    ctx.lineTo(-12, 0);
    ctx.lineTo(12, 0);
    ctx.closePath();
    ctx.fill();
    // Stripes
    ctx.strokeStyle = '#facc15';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(-7, -12);
    ctx.lineTo(7, -12);
    ctx.moveTo(-4, -22);
    ctx.lineTo(4, -22);
    ctx.stroke();
    // Pom pom on top
    ctx.fillStyle = '#38bdf8';
    ctx.beginPath();
    ctx.arc(0, -34, 5, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
    }

    // --- 6. WINGS (Hands steering handlebar or waving!) ---
    const wingRootX = bodyCenterX + 2;
    const wingRootY = bodyCenterY - 8;
    const gripX = headX + 14;
    const gripY = headY - 8;

    if (state.waveWing > 0) {
    // High Wave Animation
    const waveAngle = Math.sin(t 0.02) 0.4 - 0.6;
    ctx.save();
    ctx.translate(wingRootX, wingRootY);
    ctx.rotate(waveAngle);
    ctx.fillStyle = '#ffffff';
    ctx.strokeStyle = '#e2e8f0';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(36, -30);
    ctx.lineTo(48, -20);
    ctx.lineTo(38, 0);
    ctx.lineTo(20, 14);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
    ctx.restore();
    } else {
    // Normal Wing resting firmly on handlebars
    ctx.fillStyle = '#ffffff';
    ctx.strokeStyle = '#e2e8f0';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(wingRootX, wingRootY);
    ctx.quadraticCurveTo(wingRootX + 16, wingRootY + 12, gripX, gripY);
    ctx.lineTo(gripX - 8, gripY + 8);
    ctx.quadraticCurveTo(wingRootX + 8, wingRootY + 18, wingRootX - 10, wingRootY + 10);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();

    // Feather tips
    ctx.strokeStyle = '#cbd5e1';
    ctx.beginPath();
    ctx.moveTo(wingRootX + 4, wingRootY + 8);
    ctx.lineTo(wingRootX + 16, wingRootY + 14);
    ctx.stroke();
    }

    // --- 7. RIGHT (NEAR) LEG & PEDAL CRANK ---
    // Right Crank Arm
    ctx.strokeStyle = '#cbd5e1';
    ctx.lineWidth = 5;
    ctx.beginPath();
    ctx.moveTo(bbX, bbY);
    ctx.lineTo(rightPedalX, rightPedalY);
    ctx.stroke();

    // Right Pedal Flat
    ctx.fillStyle = '#1e293b';
    ctx.fillRect(rightPedalX - 8, rightPedalY - 4, 16, 8);

    // Solve IK for Right Leg
    const rightLegIK = solveIK(pelicanHipX + 4, pelicanHipY, rightPedalX, rightPedalY, thighL, shinL, true);

    // Bright Orange Foreground Leg
    ctx.strokeStyle = '#f97316';
    ctx.lineWidth = 9;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.beginPath();
    ctx.moveTo(pelicanHipX + 4, pelicanHipY);
    ctx.lineTo(rightLegIK.kx, rightLegIK.ky);
    ctx.lineTo(rightPedalX, rightPedalY);
    ctx.stroke();

    // Webbed Foot (Near) gripping the pedal
    ctx.fillStyle = '#ea580c';
    ctx.beginPath();
    ctx.ellipse(rightPedalX + 5, rightPedalY + 1, 12, 6, 0.15, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
    }

    /* ==========================================================================
    10. FLYING FISH FEEDING & PARTICLES UPDATE
    ========================================================================== */
    function updateAndDrawFish(bikePoints) {
    const pelicanMouthX = bikePoints.headX + 50;
    const pelicanMouthY = bikePoints.headY - 42;

    for (let i = state.fish.length - 1; i >= 0; i--) {
    const f = state.fish[i];
    f.x += f.vx;
    f.y += f.vy;
    f.vy += f.gravity;
    f.rotation += 0.08;

    // Check if fish is near mouth
    const distToMouth = Math.hypot(f.x - pelicanMouthX, f.y - pelicanMouthY);
    if (distToMouth < 45 && !f.swallowed) {
    f.swallowed = true;
    state.beakOpen = 1.0;
    state.pouchJiggle = 1.0;
    playGulpSound();
    }

    if (f.swallowed) {
    // Fish enters throat and disappears with a delicious splash
    state.fish.splice(i, 1);
    continue;
    }

    // Draw Jumping Fish
    ctx.save();
    ctx.translate(f.x, f.y);
    ctx.rotate(f.rotation);

    // Shiny Golden Fish Body
    ctx.fillStyle = '#f59e0b';
    ctx.strokeStyle = '#b45309';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.ellipse(0, 0, 16, 9, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();

    // Fish Tail & Fins
    ctx.fillStyle = '#f97316';
    ctx.beginPath();
    ctx.moveTo(-14, 0);
    ctx.lineTo(-24, -8);
    ctx.lineTo(-20, 0);
    ctx.lineTo(-24, 8);
    ctx.closePath();
    ctx.fill();

    // Cute Fish Eye
    ctx.fillStyle = '#ffffff';
    ctx.beginPath();
    ctx.arc(8, -3, 3, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#0f172a';
    ctx.beginPath();
    ctx.arc(9, -3, 1.5, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();

    // Remove if off screen bottom
    if (f.y > height + 60) {
    state.fish.splice(i, 1);
    }
    }
    }

    function updateAndDrawParticles() {
    for (let i = state.particles.length - 1; i >= 0; i--) {
    const p = state.particles[i];
    p.x += p.vx || 0;
    p.y += p.vy || 0;
    p.alpha -= 0.018;

    if (p.type === 'ring') {
    p.radius += 1.8;
    ctx.strokeStyle = rgba(250, 204, 21, ${p.alpha});
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
    ctx.stroke();
    } else if (p.type === 'flame') {
    p.radius *= 0.95;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, Math.max(1, p.radius), 0, Math.PI * 2);
    ctx.fill();
    } else {
    // Dust
    p.radius *= 0.98;
    ctx.fillStyle = ${p.color} ${p.alpha});
    ctx.beginPath();
    ctx.arc(p.x, p.y, Math.max(1, p.radius), 0, Math.PI * 2);
    ctx.fill();
    }

    if (p.alpha <= 0.01 || p.radius < 0.5) {
    state.particles.splice(i, 1);
    }
    }
    }

    /* ==========================================================================
    11. MASTER ANIMATION LOOP (60FPS Smooth Rendering)
    ========================================================================== */
    let lastTime = performance.now();

    function animate(currentTime) {
    const dt = Math.min((currentTime - lastTime) / 1000, 0.1);
    lastTime = currentTime;

    // Smoothly interpolate current speed towards target
    state.currentSpeed += (state.targetSpeed - state.currentSpeed) * 0.08;
    document.getElementById('speedDisplay').textContent = Math.round(state.currentSpeed);

    // Advance physics & angles
    const speedFactor = state.currentSpeed * 0.18;
    state.crankAngle += speedFactor dt 5.2;
    state.wheelAngle += speedFactor dt 6.8;
    state.worldDist += state.currentSpeed dt 32;

    // Decay interactive animations
    if (state.waveWing > 0) state.waveWing = Math.max(0, state.waveWing - dt * 0.8);
    if (state.pouchJiggle > 0) state.pouchJiggle = Math.max(0, state.pouchJiggle - dt * 1.5);
    if (state.bellRingAnim > 0) state.bellRingAnim = Math.max(0, state.bellRingAnim - dt * 1.8);
    if (state.beakOpen > 0) state.beakOpen = Math.max(0, state.beakOpen - dt * 2.5);

    // Clear & Draw
    ctx.clearRect(0, 0, width, height);

    // 1. Environmental Backdrop
    drawSkyAndEnvironment(currentTime);

    // 2. Bicycle & Pelican Coordinate Anchor
    const bikeX = width * 0.42;
    const groundY = height * 0.72;

    // Spawn dust from rear tire
    if (Math.random() < 0.35 * (state.currentSpeed / 20)) {
    spawnDust(bikeX - 80, groundY);
    }

    // Turbo mode flame effects!
    if (state.gear === 4) {
    spawnTurboFlame(bikeX - 82, groundY - 40);
    }

    // 3. Draw Bicycle Mechanics
    const bikePoints = drawBicycle(bikeX, groundY, state.crankAngle, state.wheelAngle);

    // 4. Draw Animated Pelican
    drawPelican(bikePoints, state.crankAngle, currentTime);

    // 5. Update Flying Fish & Particles
    updateAndDrawFish(bikePoints);
    updateAndDrawParticles();

    requestAnimationFrame(animate);
    }

    // Initial setup & start loop
    resize();
    requestAnimationFrame(animate);
    </script>
    </body>
    </html>
    (base) yangzhengkai@yangzhengkaideMac-mini ~ % >....
    }

    // 3. Draw Bicycle Mechanics
    const bikePoints = drawBicycle(bikeX, groundY, state.crankAngle, state.wheelAngle);

    // 4. Draw Animated Pelican
    drawPelican(bikePoints, state.crankAngle, currentTime);

    // 5. Update Flying Fish & Particles
    updateAndDrawFish(bikePoints);
    updateAndDrawParticles();

    requestAnimationFrame(animate);
    }

    // Initial setup & start loop
    resize();
    requestAnimationFrame(animate);
    </script>
    </body>
    </html>

    主楼
  • william-linuxdo

    是我的网络问题吗,为什么加载不出图片? ^-^

    #1
  • Pukii

    本地运行了一下,还蛮有意思的。。

    #2
  • Ler1Shy-linuxdo

    图裂了 换个图床吧 或者重新传一下

    #3
  • 泼痞

    图片

    是个mp4,怎么可能加载出来

    #4
  • 云馨

    图片

    我帮lz截个图

    ,看起来还可以哦,就是背景速度好快

    有问题的地方:

    - 背景速度过快

    - 投喂鱼不进嘴里

    - 挥挥手有点抽象

    #5
  • 泰迪

    图片

    我嘞个60KM/H的自行车 ^-^

    #6
  • 小猫曦月

    很不错了,只比我之前在站内看到的fable的骑车差一点点,性价比无敌啊

    #7
  • wjy

    HTML和svg不是一个难度吧。

    #8
  • ysin

    画一个醍醐骑自行车的动画, html格式, 不需要测试

    用你的提示词让口水4.6跑了一下

    图片

    #9
  • lililihy

    意思是前端很强?那搭配deepseek flash岂不是很完美

    #10
  • sephzl

    gemini 前端有点东西的,我测试的骑摩托车

    图片

    #11
  • Dopawei

    gemini 3.7 flash这么强吗?还是佬用了什么特定的skills

    #12
  • sisSs

    同3.7 ^-^

    图片

    #13
  • 此用户已被封禁

    刚才链接有问题, 我换了个YouTube链接

    #14

发表回复

登录后回复